"""Управление комнатой LiveKit от имени организатора (задача B2): принудительный мьют.

Тонкая обёртка над `RoomServiceClient` — тот же паттерн, что и
`services/egress.py` (единственная точка мокирования в тестах, свой
`api.LiveKitAPI` на вызов, аутентификация СЕРВЕРНЫМИ `api_key`/`api_secret`,
а не токеном организатора). Именно поэтому организатору не нужен отдельный
LiveKit-грант в собственном access-токене под это действие — мьютит backend
от своего имени, клиент лишь инициирует вызов, а право на это проверяется
по владельцу конференции в БД (`services/conferences.py::mute_participant`),
ДО обращения сюда.
"""

import logging

from livekit import api
from livekit.protocol.models import TrackSource

from core.config import get_settings
from schemas.room_events import ForcedMuteSource

logger = logging.getLogger(__name__)

# Переэкспорт под более общим именем — этот модуль не завязан на протокол WS
# (`schemas/room_events.py`), которому концептуально принадлежит `ForcedMuteSource`.
MuteSource = ForcedMuteSource

_TRACK_SOURCE_BY_NAME: dict[MuteSource, int] = {
    "microphone": TrackSource.MICROPHONE,
    "camera": TrackSource.CAMERA,
}


class ParticipantNotInRoomError(Exception):
    """Участника с таким identity сейчас нет в комнате LiveKit (уже вышел/не заходил)."""


async def mute_participant_track(room_name: str, *, identity: str, source: MuteSource) -> bool:
    """Принудительно замьютить опубликованный трек участника; `True` — трек реально замьючен.

    Если трек данного `source` сейчас не опубликован — не ошибка, а no-op:
    искомое состояние («трек не идёт») уже достигнуто. Обычный случай с
    0.0.15 — участники заходят с выключенными микрофоном/камерой (задача
    A1), трек попросту не существует, пока человек не включит его сам;
    мьютить в этот момент нечего, и это НЕ повод отвечать клиенту ошибкой.
    """
    settings = get_settings()
    lkapi = api.LiveKitAPI(
        settings.livekit_url,
        api_key=settings.livekit_api_key,
        api_secret=settings.livekit_api_secret,
    )
    try:
        try:
            participant = await lkapi.room.get_participant(
                api.RoomParticipantIdentity(room=room_name, identity=identity)
            )
        except api.TwirpError as exc:
            if exc.status == 404:
                raise ParticipantNotInRoomError from exc
            raise

        target_source = _TRACK_SOURCE_BY_NAME[source]
        track = next((t for t in participant.tracks if t.source == target_source), None)
        if track is None or track.muted:
            return False

        await lkapi.room.mute_published_track(
            api.MuteRoomTrackRequest(
                room=room_name, identity=identity, track_sid=track.sid, muted=True
            )
        )
        logger.info(
            "room_control: принудительный мьют — комната=%s identity=%s source=%s трек=%s",
            room_name,
            identity,
            source,
            track.sid,
        )
        return True
    finally:
        await lkapi.aclose()
