from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from app.generation.llm_client import ModelInfo
from app.services.models_service import (
    ModelsService,
    build_models_response,
    is_chat_model,
)


def test_is_chat_model_filters_non_chat():
    assert is_chat_model("gpt-4o") is True
    assert is_chat_model("o1-preview") is True
    assert is_chat_model("o3-mini") is True
    assert is_chat_model("claude-sonnet-4-6") is True
    assert is_chat_model("gemini-2.5-flash") is True
    assert is_chat_model("deepseek-chat") is True
    assert is_chat_model("text-embedding-3-small") is False
    assert is_chat_model("tts-1") is False
    assert is_chat_model("dall-e-3") is False


def test_build_models_response_filters_and_marks_defaults():
    catalog = [
        ModelInfo(id="gpt-4o", owned_by="openai", created=1),
        ModelInfo(id="gpt-4o-mini", owned_by="openai", created=2),
        ModelInfo(id="text-embedding-3-small", owned_by="openai", created=3),
    ]

    response = build_models_response(
        catalog,
        default_model="gpt-4o",
        default_intent_model="gpt-4o-mini",
    )

    assert response.default_model == "gpt-4o"
    assert response.default_intent_model == "gpt-4o-mini"
    assert [m.id for m in response.models] == ["gpt-4o", "gpt-4o-mini"]
    assert response.models[0].default is True
    assert response.models[0].available is True
    assert response.models[1].default_intent is True


def test_build_models_response_injects_unavailable_env_defaults():
    catalog = [ModelInfo(id="gpt-4o-mini", owned_by="openai", created=2)]

    response = build_models_response(
        catalog,
        default_model="custom-rag-model",
        default_intent_model="gpt-4o-mini",
    )

    by_id = {m.id: m for m in response.models}
    assert by_id["custom-rag-model"].default is True
    assert by_id["custom-rag-model"].available is False
    assert by_id["gpt-4o-mini"].default_intent is True
    assert response.default_model == "custom-rag-model"


def test_build_models_response_env_only_when_catalog_empty():
    response = build_models_response(
        [],
        default_model="gpt-4o",
        default_intent_model="gpt-4o-mini",
    )

    assert len(response.models) == 2
    assert all(not m.available for m in response.models)
    assert response.default_model == "gpt-4o"


@pytest.mark.asyncio
async def test_models_service_uses_llm_client(monkeypatch):
    async def fake_list_models():
        return [ModelInfo(id="gpt-4o", owned_by="openai", created=1)]

    monkeypatch.setattr("app.services.models_service.settings.OPENAI_API_KEY", "sk-test")
    monkeypatch.setattr("app.services.models_service.settings.LLM_MODEL", "gpt-4o")
    monkeypatch.setattr(
        "app.services.models_service.settings.LLM_INTENT_MODEL",
        "gpt-4o-mini",
    )

    service = ModelsService(llm_client=MagicMock())
    service._llm_client.list_models = fake_list_models  # type: ignore[method-assign]

    response = await service.list_models()
    by_id = {m.id: m for m in response.models}
    assert by_id["gpt-4o"].default is True
    assert by_id["gpt-4o"].available is True
    assert by_id["gpt-4o-mini"].default_intent is True
    assert by_id["gpt-4o-mini"].available is False
    assert by_id["claude-sonnet-4-6"].available is True
    assert by_id["gemini-2.5-flash"].available is True
    assert by_id["deepseek-chat"].available is True


@pytest.mark.asyncio
async def test_models_service_local_fallback_without_api_key(monkeypatch):
    monkeypatch.setattr("app.services.models_service.settings.OPENAI_API_KEY", "")
    monkeypatch.setattr("app.services.models_service.settings.ENVIRONMENT", "local")
    monkeypatch.setattr("app.services.models_service.settings.LLM_MODEL", "gpt-4o")
    monkeypatch.setattr(
        "app.services.models_service.settings.LLM_INTENT_MODEL",
        "gpt-4o-mini",
    )

    async def fail_list_models():
        raise AssertionError("OpenAI should not be called without API key in local dev")

    service = ModelsService(llm_client=MagicMock())
    service._llm_client.list_models = fail_list_models  # type: ignore[method-assign]

    response = await service.list_models()
    by_id = {m.id: m for m in response.models}
    assert response.default_model == "gpt-4o"
    assert by_id["gpt-4o"].available is False
    assert by_id["gpt-4o-mini"].available is False
    assert by_id["claude-sonnet-4-6"].available is True
    assert by_id["gemini-2.5-flash"].available is True
    assert by_id["deepseek-chat"].available is True
