"""Модели Pydantic для описания `config/plugins.yaml` и его загрузчика."""

from pathlib import Path
from typing import Any, Literal

import yaml
from pydantic import BaseModel, Field


class TranscriberConfig(BaseModel):
    """Конфигурация активного плагина transcriber."""

    enabled: bool = True
    provider: str = Field(default="null", min_length=1)
    model: str | None = None
    language: str = "ru"
    options: dict[str, Any] = Field(default_factory=dict)


class SummarizerConfig(BaseModel):
    """Конфигурация активного плагина summarizer."""

    enabled: bool = True
    provider: str = Field(default="null", min_length=1)
    model: str | None = None
    chunk_minutes: int = 20
    options: dict[str, Any] = Field(default_factory=dict)


class ChatConfig(BaseModel):
    """Конфигурация переключателя функции чата."""

    enabled: bool = True


class HandQueueConfig(BaseModel):
    """Конфигурация переключателя модуля «поднятие руки» (кнопка + очередь целиком)."""

    enabled: bool = True


class PluginsConfig(BaseModel):
    """Корневая модель конфигурации для `config/plugins.yaml`."""

    transcriber: TranscriberConfig = Field(default_factory=TranscriberConfig)
    summarizer: SummarizerConfig = Field(default_factory=SummarizerConfig)
    chat: ChatConfig = Field(default_factory=ChatConfig)
    hand_queue: HandQueueConfig = Field(default_factory=HandQueueConfig)


def load_plugins_config(path: str | Path) -> PluginsConfig:
    """Загрузить и валидировать `PluginsConfig` из YAML файла."""
    raw = yaml.safe_load(Path(path).read_text()) or {}
    return PluginsConfig.model_validate(raw)


# --- Настройки инстанса: БД поверх дефолтов `plugins.yaml`. ---
# Ключи `instance_settings` зеркалят секции ниже (`transcriber`, `summarizer`,
# `chat`, `ai_level`, `summary_recipients`, `display_timezone`) — см.
# `services/instance_settings.py`.

AiLevel = Literal["min", "medium", "max"]
"""Уровень AI-модуля инстанса (модели/требования — ADR-004,
`docs/architecture/adr/004-ai-tier-matrix.md`, `services/ai_tiers.TIERS`).
Доступность каждого уровня на конкретном инстансе зависит от обнаруженного
железа и скачанных моделей — см. `services/ai_levels.py::detect_ai_levels`."""

SummaryRecipientsMode = Literal["all", "owner"]
"""Режим рассылки саммари по умолчанию: всем участникам либо только
владельцу конференции (переопределяется на уровне `conferences.summary_recipients`)."""

PublishQualityCap = Literal["off", "180p", "360p", "720p"]
"""Потолок качества исходящего видео участника (задача «рычаги качества
медиа»). `off` — без ограничения (дефолт, поведение как до появления
настройки). Остальные значения режут `publishDefaults.videoEncoding` и
`videoSimulcastLayers` на клиенте (`frontend/src/lib/publishQualityCap.ts`) —
именно битрейт верхнего слоя симулкаста, а не жёсткое разрешение захвата
камеры; фактическое разрешение WebRTC подстраивает под битрейт сам."""

StageMaxTiles = Literal[4, 9, 16, 25]
"""Потолок числа одновременно видимых плиток на сцене (`StageGrid`) —
режет набор доступных раскладок сетки, что бросает лишних участников на
следующую страницу пагинации вместо подписки на их треки. `25` — дефолт,
совпадает с текущим максимумом сетки (5×5), то есть без ограничения."""


class MediaLimitsConfig(BaseModel):
    """Рычаги нагрузки медиа для администратора инстанса (не логика состояния
    конференции — статичные потолки, применяются на клиенте при входе)."""

    publish_quality_cap: PublishQualityCap = "off"
    stage_max_tiles: StageMaxTiles = 25


class InstanceConfig(BaseModel):
    """Эффективная конфигурация инстанса (значения `instance_settings` поверх дефолтов
    `plugins.yaml`, см. `services/instance_settings.py::load_effective_config`)."""

    transcriber: TranscriberConfig
    summarizer: SummarizerConfig
    chat: ChatConfig
    hand_queue: HandQueueConfig = Field(default_factory=HandQueueConfig)
    ai_level: AiLevel = "min"
    summary_recipients: SummaryRecipientsMode = "all"
    display_timezone: str = "Europe/Moscow"
    # Разрешить выбор команды на форме регистрации (справочник `teams`)
    # — см. `services/instance_settings.py`.
    registration_team_choice: bool = False
    # Верификация регистрирующихся по домену email: при включении
    # `POST /auth/register` принимает только email с доменом из
    # `registration_email_domains` (любым из списка) — см.
    # `services/instance_settings.py`.
    registration_email_domain_enabled: bool = False
    registration_email_domains: list[str] = Field(default_factory=list)
    # Контактный адрес инстанса — подставляется в `Reply-To` исходящих писем
    # (сами письма уходят от `no-reply@`, отвечать на них некуда без этого
    # адреса) — см. `services/instance_settings.py`, `services/email.py`.
    contact_email_enabled: bool = False
    contact_email: str | None = None
    # Потолок качества публикации + максимум плиток сцены — см.
    # `services/instance_settings.py`. Отдаётся участнику ДО входа в
    # LiveKit-комнату (в ответе join, `schemas/conferences.py::JoinOut`), а
    # не только в админке — настройка должна быть на руках у клиента до
    # публикации трека.
    media_limits: MediaLimitsConfig = Field(default_factory=MediaLimitsConfig)
    # Согласие на обработку персональных данных при регистрации: галочка
    # обязательна только при `consent_required=True`, текст/версия — редактируемая
    # администратором настройка (дефолт — типовой шаблон, не юридический документ) —
    # см. `services/instance_settings.py`. `consent_policy_text`/`_version`
    # отдаются публично (`GET /auth/registration-options`) независимо от
    # `consent_required`, чтобы страница регламента была осмысленной и при
    # выключенном модуле.
    consent_required: bool = False
    consent_policy_text: str = ""
    consent_policy_version: int = 1
    # Проверка устройств на входе (сессия 33): запрос доступа к микрофону/камере
    # + превью камеры на странице логина и в карточке «Как вас зовут?» (JoinPage).
    # Дефолт False сохраняет поведение существующих инсталляций — см.
    # `services/instance_settings.py`.
    device_check_enabled: bool = False
    # Замена фона видео на картинку (сессия 35): отключаемый модуль, дефолт
    # False сохраняет поведение существующих инсталляций. Нужен клиенту в двух
    # местах и потому едет двумя путями: на публичные страницы входа — через
    # `GET /public/settings`, участнику комнаты — в ответе join
    # (`schemas/conferences.py::JoinOut`), см. `services/instance_settings.py`.
    virtual_background_enabled: bool = False
