"""Проверка права входа в конференцию (статус/пароль) и генерация ответа join.

Заменяет `services/room_access.py`: комнаты больше не конкурируют за
время («правило часа» удалено вместе с бронированием, ADR-001, п.5), но
пароль закрытой конференции по-прежнему проверяется здесь же — для обычного
пользователя и гостя одинаково.
"""

import json

from core.config import get_settings
from core.plugins.config import PublishQualityCap, StageMaxTiles
from core.security import verify_password
from models.conference import Conference
from schemas.conferences import JoinOut
from services.livekit_tokens import create_room_access_token


class ConferenceEndedError(Exception):
    """Конференция завершена (терминальный статус) — повторный вход невозможен."""


class PasswordRequiredError(Exception):
    """Конференция закрыта паролем; пароль не передан."""


class InvalidPasswordError(Exception):
    """Указанный пароль не совпадает с паролем закрытой конференции."""


async def ensure_joinable(conference: Conference, *, password: str | None) -> None:
    """Проверить, что в конференцию можно войти прямо сейчас.

    Бросает `ConferenceEndedError` для терминального статуса `ended`
    (история и саммари остаются, но повторный вход невозможен — ADR-001,
    п.2), либо `PasswordRequiredError`/`InvalidPasswordError` для закрытой
    паролем конференции. Ничего не бросает для открытой конференции в
    статусе `scheduled`/`active`.
    """
    if conference.status == "ended":
        raise ConferenceEndedError
    if not conference.is_closed:
        return
    if conference.password_hash is None or password is None:
        raise PasswordRequiredError
    if not await verify_password(password, conference.password_hash):
        raise InvalidPasswordError


def build_join(
    conference: Conference,
    *,
    identity: str,
    name: str,
    chat_enabled: bool,
    hand_queue_enabled: bool,
    publish_quality_cap: PublishQualityCap,
    stage_max_tiles: StageMaxTiles,
    virtual_background_enabled: bool,
    avatar_url: str | None = None,
    is_organizer: bool = False,
) -> JoinOut:
    """Построить ответ join: LiveKit access-токен для входа в комнату конференции.

    Имя LiveKit-комнаты всегда равно `conference.slug` (ADR-001, п.4).
    `chat_enabled`/`hand_queue_enabled`/`publish_quality_cap`/`stage_max_tiles`/
    `virtual_background_enabled`
    — снятые вызывающей стороной значения `instance_settings`: читаются здесь
    параметрами, а не заново из БД, чтобы не плодить отдельный запрос настроек
    на каждый join.
    `avatar_url`/`is_organizer`
    прокидываются в метаданные токена как JSON `{"avatar_url": ..., "is_organizer": true}`
    — поля добавляются, только если заданы (гость без аватара и не-организатор
    получают токен вовсе без метаданных, как и раньше).

    ⚠️ `is_organizer` в метаданных — только подсказка для UI клиента (показать/
    скрыть кнопки организатора). Метаданным токена доверять для АВТОРИЗАЦИИ
    нельзя — участник технически может их подделать на своей стороне. Любое
    серверное действие организатора (например, принудительный мьют) обязано
    заново проверяться по `conference.owner_id` в БД, а не по этому полю.
    """
    settings = get_settings()
    metadata_payload: dict[str, object] = {}
    if avatar_url:
        metadata_payload["avatar_url"] = avatar_url
    if is_organizer:
        metadata_payload["is_organizer"] = True
    metadata = json.dumps(metadata_payload) if metadata_payload else None
    token = create_room_access_token(
        room_name=conference.slug, identity=identity, name=name, metadata=metadata
    )
    return JoinOut(
        livekit_url=settings.livekit_public_url,
        token=token,
        room_name=conference.slug,
        conference_id=conference.id,
        chat_enabled=chat_enabled,
        hand_queue_enabled=hand_queue_enabled,
        publish_quality_cap=publish_quality_cap,
        stage_max_tiles=stage_max_tiles,
        virtual_background_enabled=virtual_background_enabled,
    )
