"""Свои картинки пользователя для замены фона видео: лимит, файлы на диске, URL.

Как и аватары (`services/avatars.py`), картинки лежат **файлами на диске**
(`MEDIA_ROOT/backgrounds/{user_id}/{background_id}.{ext}`), а в БД — только
путь (`user_backgrounds.path`): требование оператора «чтобы не грузили БД».
Раздаёт их nginx напрямую (`location /media/`), в обход backend.

Картинку ужимает КЛИЕНТ (canvas → WebP, см. `frontend/src/lib/imageResize.ts`):
фон всё равно рендерится в браузере, и ставить Pillow на сервер ради одной
операции не нужно. Но валидация здесь остаётся полноценной — запрос может
прийти и мимо интерфейса.

Имя файла — id самой записи, а не порядковый номер: запись никогда не
перезаписывается (загрузка всегда создаёт новую), поэтому URL картинки
неизменен и его можно кэшировать браузером без cache-busting-параметра,
в отличие от аватара.
"""

import uuid
from pathlib import Path

from fastapi import UploadFile
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from models.user import User
from models.user_background import UserBackground
from services.images import (
    ImageInvalidTypeError,
    ImageTooLargeError,
    read_and_validate_image,
)

# Сколько своих картинок разрешено одному пользователю — прямое требование
# задачи («но не более 10»). Проверяется здесь, на сервере: ограничение только
# в интерфейсе обходится curl'ом.
MAX_BACKGROUNDS_PER_USER = 10

# Лимит размера загружаемого файла — 2 МБ, как у аватара. Клиент присылает
# ужатый WebP (обычно 100–300 КБ), так что до лимита доходит только тот, кто
# шлёт запрос в обход интерфейса.
MAX_BACKGROUND_SIZE_BYTES = 2 * 1024 * 1024


class BackgroundTooLargeError(ImageTooLargeError):
    """Загружаемый файл превышает `MAX_BACKGROUND_SIZE_BYTES` (413)."""


class BackgroundInvalidTypeError(ImageInvalidTypeError):
    """`Content-Type` не входит в список допустимых либо не совпадает с содержимым (415)."""


class BackgroundLimitReachedError(Exception):
    """У пользователя уже `MAX_BACKGROUNDS_PER_USER` картинок (409)."""


async def list_backgrounds(session: AsyncSession, user_id: uuid.UUID) -> list[UserBackground]:
    """Все картинки пользователя, свежие сверху."""
    result = await session.execute(
        select(UserBackground)
        .where(UserBackground.user_id == user_id)
        .order_by(UserBackground.created_at.desc(), UserBackground.id.desc())
    )
    return list(result.scalars().all())


async def add_background(
    session: AsyncSession, media_root: Path, user_id: uuid.UUID, file: UploadFile
) -> UserBackground:
    """Провалидировать, сохранить на диск и завести запись о новой картинке.

    Бросает `BackgroundTooLargeError`/`BackgroundInvalidTypeError`/
    `BackgroundLimitReachedError`. Коммит — за вызывающим (роутером), как и в
    остальных эндпоинтах профиля.

    Строка пользователя блокируется (`FOR UPDATE`) на время проверки лимита:
    без этого две одновременные загрузки (двойной клик по кнопке) обе
    увидели бы «уже 9» и обе прошли бы — лимит, проверяемый на сервере,
    обязан держаться и в этом случае.
    """
    await session.execute(select(User.id).where(User.id == user_id).with_for_update())
    count = await session.scalar(
        select(func.count()).select_from(UserBackground).where(UserBackground.user_id == user_id)
    )
    if (count or 0) >= MAX_BACKGROUNDS_PER_USER:
        raise BackgroundLimitReachedError(f"limit is {MAX_BACKGROUNDS_PER_USER}")

    try:
        content, ext = await read_and_validate_image(file, MAX_BACKGROUND_SIZE_BYTES)
    except ImageTooLargeError as exc:
        raise BackgroundTooLargeError(str(exc)) from exc
    except ImageInvalidTypeError as exc:
        raise BackgroundInvalidTypeError(str(exc)) from exc

    # id генерируем здесь, а не полагаемся на `server_default`: он нужен ДО
    # вставки, чтобы собрать имя файла на диске.
    background_id = uuid.uuid4()
    relative_path = f"backgrounds/{user_id}/{background_id}.{ext}"
    background = UserBackground(id=background_id, user_id=user_id, path=relative_path)
    session.add(background)
    # Запись сначала, файл потом: если вставка не пройдёт (лимит, гонка,
    # отвалившаяся БД), на диске не останется мусора.
    await session.flush()

    file_path = media_root / relative_path
    file_path.parent.mkdir(parents=True, exist_ok=True)
    file_path.write_bytes(content)
    return background


async def delete_background(
    session: AsyncSession, media_root: Path, user_id: uuid.UUID, background_id: uuid.UUID
) -> bool:
    """Удалить картинку пользователя вместе с файлом. `False` — записи нет (404).

    `user_id` в условии обязателен: без него владелец записи не проверялся бы
    и любой аутентифицированный пользователь мог бы удалить чужую картинку,
    зная её id.
    """
    background = await session.scalar(
        select(UserBackground).where(
            UserBackground.id == background_id, UserBackground.user_id == user_id
        )
    )
    if background is None:
        return False
    file_path = media_root / background.path
    file_path.unlink(missing_ok=True)
    await session.delete(background)
    return True


def background_url(path: str) -> str:
    """Публичный URL картинки фона (раздаётся nginx из тома `media`)."""
    return f"/media/{path}"
