"""Тесты доступности уровней AI (`services.ai_levels.detect_ai_levels`).

Детект без GPU → `max` недоступен с причиной; `medium` доступен или
недоступен по RAM/моделям; ровно три уровня в ответе.
Реальное железо/файлы моделей недетерминированы на машине разработчика —
`HW_*` подставляются через `monkeypatch.setenv` + `get_settings.cache_clear()`
(см. `test_admin_api.py::test_upload_user_avatar_by_admin` за образцом),
факт «модель скачана» — через monkeypatch `services.ai_levels._model_downloaded`.
"""

from collections.abc import Iterator

import pytest

from core.config import Settings, get_settings
from core.plugins.config import ChatConfig, InstanceConfig, SummarizerConfig, TranscriberConfig
from services import ai_levels
from services.ai_levels import detect_ai_levels


def _cfg() -> InstanceConfig:
    return InstanceConfig(
        transcriber=TranscriberConfig(), summarizer=SummarizerConfig(), chat=ChatConfig()
    )


@pytest.fixture(autouse=True)
def _clear_hw_env(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
    """Гарантировать «чистое» железо (без HW_*) на старте каждого теста и сброс кэша
    `get_settings` после — тесты этого модуля не должны зависеть друг от друга
    или от `.env` окружения разработчика."""
    for var in ("HW_CPUS", "HW_RAM_MB", "HW_GPU_NAME", "HW_VRAM_MB"):
        monkeypatch.delenv(var, raising=False)
    get_settings.cache_clear()
    try:
        yield
    finally:
        get_settings.cache_clear()


@pytest.fixture
def _all_models_downloaded(monkeypatch: pytest.MonkeyPatch) -> None:
    """Считать все модели всех уровней скачанными (изолирует тест от реального диска)."""
    monkeypatch.setattr(ai_levels, "_model_downloaded", lambda _path: True)


@pytest.fixture
def _no_models_downloaded(monkeypatch: pytest.MonkeyPatch) -> None:
    """Считать, что ни одна модель не скачана."""
    monkeypatch.setattr(ai_levels, "_model_downloaded", lambda _path: False)


def test_settings_treats_empty_hw_env_strings_as_none(monkeypatch: pytest.MonkeyPatch) -> None:
    """`docker-compose`/`env_file` подставляют `KEY=` из `.env` как пустую строку, не как
    отсутствие переменной: `install.sh` пишет `HW_VRAM_MB=` пустым на любой машине без
    NVIDIA GPU (пресеты 1–4), `.env.example` — все четыре `HW_*` пустыми по умолчанию.
    Без нормализации пустой строки в `None` `Settings()` падает `ValidationError` уже на
    импорте (`main.py`, `workers/celery_app.py`) — регрессионный тест."""
    monkeypatch.setenv("HW_CPUS", "")
    monkeypatch.setenv("HW_RAM_MB", "")
    monkeypatch.setenv("HW_GPU_NAME", "")
    monkeypatch.setenv("HW_VRAM_MB", "")

    settings = Settings()

    assert settings.hw_cpus is None
    assert settings.hw_ram_mb is None
    assert settings.hw_gpu_name is None
    assert settings.hw_vram_mb is None


def test_detect_ai_levels_returns_exactly_three_levels() -> None:
    statuses = detect_ai_levels(_cfg())

    assert {status.level for status in statuses} == {"min", "medium", "max"}


def test_min_available_with_enough_ram_and_models(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "16384")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["min"].available is True
    assert statuses["min"].reason is None


def test_min_unavailable_when_model_not_downloaded(_no_models_downloaded: None) -> None:
    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["min"].available is False
    reason = statuses["min"].reason
    assert reason is not None
    assert "не скачана" in reason
    assert "пресетом 3" in reason


def test_medium_unavailable_with_insufficient_ram(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "16384")  # хватает на min (16 ГБ), мало для medium (32 ГБ)
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["medium"].available is False
    reason = statuses["medium"].reason
    assert reason is not None
    assert "недостаточно RAM: нужно 32 ГБ" in reason


def test_medium_available_with_enough_ram_and_models_without_gpu(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    """`medium` не требует GPU (ADR-004: GPU опционален) — доступен на чистом CPU-железе."""
    monkeypatch.setenv("HW_RAM_MB", "32768")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["medium"].available is True
    assert statuses["medium"].reason is None


def test_medium_unavailable_when_model_not_downloaded(
    monkeypatch: pytest.MonkeyPatch, _no_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "32768")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["medium"].available is False
    reason = statuses["medium"].reason
    assert reason is not None
    assert "не скачана" in reason
    assert "пресетом 4" in reason


def test_max_unavailable_without_gpu(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "65536")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["max"].available is False
    reason = statuses["max"].reason
    assert reason is not None
    assert "требуется GPU NVIDIA ≥16 ГБ VRAM, не обнаружен" in reason


def test_max_unavailable_with_insufficient_vram(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "65536")
    monkeypatch.setenv("HW_GPU_NAME", "NVIDIA RTX 3060")
    monkeypatch.setenv("HW_VRAM_MB", "12288")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["max"].available is False
    reason = statuses["max"].reason
    assert reason is not None
    assert "обнаружено только 12 ГБ" in reason


def test_max_available_with_gpu_ram_and_models(
    monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
    monkeypatch.setenv("HW_RAM_MB", "65536")
    monkeypatch.setenv("HW_GPU_NAME", "NVIDIA RTX 4090")
    monkeypatch.setenv("HW_VRAM_MB", "24576")
    get_settings.cache_clear()

    statuses = {status.level: status for status in detect_ai_levels(_cfg())}

    assert statuses["max"].available is True
    assert statuses["max"].reason is None
