#!/usr/bin/env python3
"""utils/aux_llm_router.py 테스트 스위트

TDD: 테스트 먼저 작성 후 구현.
"""

import os
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from utils.aux_llm_router import AuxRouter, ProviderConfig


# ---------------------------------------------------------------------------
# ProviderConfig dataclass
# ---------------------------------------------------------------------------


class TestProviderConfig:
    """ProviderConfig dataclass 테스트"""

    def test_required_fields(self) -> None:
        """필수 필드 생성 확인"""
        cfg = ProviderConfig(
            name="anthropic",
            model="claude-haiku-4-5",
            api_key_env="ANTHROPIC_API_KEY",
        )
        assert cfg.name == "anthropic"
        assert cfg.model == "claude-haiku-4-5"
        assert cfg.api_key_env == "ANTHROPIC_API_KEY"

    def test_optional_defaults(self) -> None:
        """base_url, priority 기본값 확인"""
        cfg = ProviderConfig(
            name="openai",
            model="gpt-4o-mini",
            api_key_env="OPENAI_API_KEY",
        )
        assert cfg.base_url is None
        assert cfg.priority == 0

    def test_custom_base_url_and_priority(self) -> None:
        """base_url, priority 커스텀 설정"""
        cfg = ProviderConfig(
            name="deepseek",
            model="deepseek-chat",
            api_key_env="DEEPSEEK_API_KEY",
            base_url="https://api.deepseek.com/v1",
            priority=5,
        )
        assert cfg.base_url == "https://api.deepseek.com/v1"
        assert cfg.priority == 5

    def test_dataclass_is_mutable_or_frozen_does_not_crash(self) -> None:
        """dataclass 기본 동작 확인 (frozen 여부와 무관하게 생성 가능)"""
        cfg = ProviderConfig(
            name="google",
            model="gemini-2.5-flash",
            api_key_env="GOOGLE_API_KEY",
        )
        assert cfg.name == "google"


# ---------------------------------------------------------------------------
# AuxRouter.__init__
# ---------------------------------------------------------------------------


class TestAuxRouterInit:
    """AuxRouter 초기화 테스트"""

    def test_default_providers_loaded(self) -> None:
        """기본 프로바이더 4개(anthropic/openai/google/deepseek) 등록"""
        router = AuxRouter()
        names = [p.name for p in router.providers]
        assert "anthropic" in names
        assert "openai" in names
        assert "google" in names
        assert "deepseek" in names

    def test_custom_providers(self) -> None:
        """커스텀 프로바이더 목록 주입"""
        custom = [
            ProviderConfig(name="my-provider", model="my-model", api_key_env="MY_KEY")
        ]
        router = AuxRouter(providers=custom)
        assert len(router.providers) == 1
        assert router.providers[0].name == "my-provider"

    def test_providers_none_uses_defaults(self) -> None:
        """providers=None이면 기본 프로바이더 사용"""
        router = AuxRouter(providers=None)
        assert len(router.providers) >= 4


# ---------------------------------------------------------------------------
# AuxRouter.get_available_providers
# ---------------------------------------------------------------------------


class TestGetAvailableProviders:
    """get_available_providers() 테스트"""

    def test_returns_only_providers_with_api_key(self, monkeypatch) -> None:
        """API 키가 환경변수에 있는 프로바이더만 반환"""
        monkeypatch.setenv("MY_KEY_A", "sk-test-a")
        monkeypatch.delenv("MY_KEY_B", raising=False)

        providers = [
            ProviderConfig(name="has-key", model="m1", api_key_env="MY_KEY_A"),
            ProviderConfig(name="no-key", model="m2", api_key_env="MY_KEY_B"),
        ]
        router = AuxRouter(providers=providers)
        available = router.get_available_providers()

        assert len(available) == 1
        assert available[0].name == "has-key"

    def test_empty_when_no_keys_set(self, monkeypatch) -> None:
        """모든 키 미설정 시 빈 리스트"""
        monkeypatch.delenv("MY_KEY_X", raising=False)
        monkeypatch.delenv("MY_KEY_Y", raising=False)

        providers = [
            ProviderConfig(name="p1", model="m1", api_key_env="MY_KEY_X"),
            ProviderConfig(name="p2", model="m2", api_key_env="MY_KEY_Y"),
        ]
        router = AuxRouter(providers=providers)
        assert router.get_available_providers() == []

    def test_returns_list_type(self, monkeypatch) -> None:
        """반환형이 list인지 확인"""
        monkeypatch.setenv("MY_KEY_C", "val")
        providers = [
            ProviderConfig(name="p", model="m", api_key_env="MY_KEY_C")
        ]
        router = AuxRouter(providers=providers)
        result = router.get_available_providers()
        assert isinstance(result, list)

    def test_empty_string_key_not_considered_available(self, monkeypatch) -> None:
        """빈 문자열 API 키는 미설정으로 간주"""
        monkeypatch.setenv("MY_KEY_EMPTY", "")
        providers = [
            ProviderConfig(name="empty-key", model="m", api_key_env="MY_KEY_EMPTY")
        ]
        router = AuxRouter(providers=providers)
        available = router.get_available_providers()
        assert len(available) == 0


# ---------------------------------------------------------------------------
# AuxRouter.route — task_type 매핑
# ---------------------------------------------------------------------------


class TestRoute:
    """route() task_type 매핑 테스트"""

    def _router_with_all_keys(self, monkeypatch) -> AuxRouter:
        """모든 기본 프로바이더 키를 설정한 AuxRouter 반환"""
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test")
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test")
        monkeypatch.setenv("GOOGLE_API_KEY", "google-test")
        monkeypatch.setenv("DEEPSEEK_API_KEY", "deepseek-test")
        return AuxRouter()

    def test_summarize_prefers_haiku(self, monkeypatch) -> None:
        """summarize → Haiku(anthropic) 우선"""
        router = self._router_with_all_keys(monkeypatch)
        result = router.route("summarize")
        assert result is not None
        assert result.name == "anthropic"

    def test_analyze_prefers_sonnet(self, monkeypatch) -> None:
        """analyze → Sonnet(anthropic) 우선"""
        router = self._router_with_all_keys(monkeypatch)
        result = router.route("analyze")
        assert result is not None
        assert result.name == "anthropic"

    def test_classify_prefers_haiku(self, monkeypatch) -> None:
        """classify → Haiku(anthropic) 우선"""
        router = self._router_with_all_keys(monkeypatch)
        result = router.route("classify")
        assert result is not None
        assert result.name == "anthropic"

    def test_default_returns_first_available(self, monkeypatch) -> None:
        """default → 첫 번째 사용 가능한 프로바이더"""
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test")
        monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("default")
        assert result is not None
        assert result.name == "openai"

    def test_unknown_task_type_falls_back_to_default(self, monkeypatch) -> None:
        """알 수 없는 task_type → default 동작"""
        monkeypatch.setenv("OPENAI_API_KEY", "sk-test")
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("completely_unknown_task")
        assert result is not None

    def test_route_returns_none_when_no_providers_available(self, monkeypatch) -> None:
        """사용 가능한 프로바이더 없으면 None 반환"""
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.delenv("OPENAI_API_KEY", raising=False)
        monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("summarize")
        assert result is None

    def test_summarize_fallback_to_openai_when_anthropic_missing(
        self, monkeypatch
    ) -> None:
        """summarize: anthropic 없으면 openai(gpt-4o-mini) fallback"""
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test")
        monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("summarize")
        assert result is not None
        assert result.name == "openai"

    def test_analyze_fallback_when_anthropic_missing(self, monkeypatch) -> None:
        """analyze: anthropic 없으면 openai fallback"""
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test")
        monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("analyze")
        assert result is not None
        assert result.name == "openai"

    def test_route_returns_provider_config_type(self, monkeypatch) -> None:
        """route() 반환값이 ProviderConfig 타입"""
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-test")
        router = AuxRouter()
        result = router.route("summarize")
        assert result is not None
        assert isinstance(result, ProviderConfig)

    def test_classify_fallback_chain(self, monkeypatch) -> None:
        """classify: anthropic 없으면 openai, 없으면 google 순"""
        monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
        monkeypatch.delenv("OPENAI_API_KEY", raising=False)
        monkeypatch.setenv("GOOGLE_API_KEY", "google-test")
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        router = AuxRouter()
        result = router.route("classify")
        assert result is not None
        assert result.name == "google"


# ---------------------------------------------------------------------------
# 통합: 커스텀 프로바이더 + route
# ---------------------------------------------------------------------------


class TestCustomProviderRouting:
    """커스텀 프로바이더와 route() 통합 테스트"""

    def test_custom_provider_single_available(self, monkeypatch) -> None:
        """커스텀 단일 프로바이더 - 모든 task_type에서 해당 프로바이더 반환"""
        monkeypatch.setenv("CUSTOM_KEY", "sk-custom")
        custom = [
            ProviderConfig(
                name="custom",
                model="custom-model",
                api_key_env="CUSTOM_KEY",
            )
        ]
        router = AuxRouter(providers=custom)
        for task in ("summarize", "analyze", "classify", "default"):
            result = router.route(task)
            assert result is not None
            assert result.name == "custom"

    def test_priority_ordering_in_get_available(self, monkeypatch) -> None:
        """priority 높은 프로바이더가 get_available_providers 결과에 포함"""
        monkeypatch.setenv("KEY_LOW", "val1")
        monkeypatch.setenv("KEY_HIGH", "val2")
        providers = [
            ProviderConfig(name="low", model="m", api_key_env="KEY_LOW", priority=1),
            ProviderConfig(name="high", model="m", api_key_env="KEY_HIGH", priority=10),
        ]
        router = AuxRouter(providers=providers)
        available = router.get_available_providers()
        assert len(available) == 2
        names = [p.name for p in available]
        assert "low" in names
        assert "high" in names
