#!/usr/bin/env python3
"""utils/model_router.py 테스트 스위트 (TDD)"""

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

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

from utils.model_router import MODEL_HAIKU, MODEL_OPUS, MODEL_SONNET, route_model


class TestConstants:
    """모델명 상수 테스트"""

    def test_model_haiku_constant(self) -> None:
        """MODEL_HAIKU 상수 값 확인"""
        assert MODEL_HAIKU == "claude-haiku-4-5-20251001"

    def test_model_sonnet_constant(self) -> None:
        """MODEL_SONNET 상수 값 확인"""
        assert MODEL_SONNET == "claude-sonnet-4-6"

    def test_model_opus_constant(self) -> None:
        """MODEL_OPUS 상수 값 확인"""
        assert MODEL_OPUS == "claude-opus-4-6"


class TestSimpleTaskRouting:
    """단순 작업 → haiku 라우팅 테스트"""

    def test_short_simple_text_returns_haiku(self) -> None:
        """짧은 단순 텍스트는 haiku 반환"""
        assert route_model("안녕하세요") == MODEL_HAIKU

    def test_hello_world_returns_haiku(self) -> None:
        """'Hello' 같은 단어 하나는 haiku"""
        assert route_model("Hello") == MODEL_HAIKU

    def test_short_question_returns_haiku(self) -> None:
        """짧은 질문 haiku 반환"""
        assert route_model("오늘 날씨가 어때요?") == MODEL_HAIKU

    def test_exactly_160_chars_returns_haiku(self) -> None:
        """정확히 160자 텍스트는 haiku (경계값)"""
        task = "a" * 160
        assert route_model(task) == MODEL_HAIKU

    def test_exactly_28_words_returns_haiku(self) -> None:
        """정확히 28단어 텍스트는 haiku (경계값)"""
        task = " ".join(["word"] * 28)
        assert route_model(task) == MODEL_HAIKU


class TestComplexTaskRouting:
    """복잡 키워드 → opus 라우팅 테스트"""

    def test_analyze_keyword_returns_opus(self) -> None:
        """'analyze' 키워드 → opus"""
        assert route_model("Please analyze this code") == MODEL_OPUS

    def test_implement_keyword_returns_opus(self) -> None:
        """'implement' 키워드 → opus"""
        assert route_model("implement a new feature") == MODEL_OPUS

    def test_architecture_keyword_returns_opus(self) -> None:
        """'architecture' 키워드 → opus"""
        assert route_model("design the architecture for this system") == MODEL_OPUS

    def test_security_keyword_returns_opus(self) -> None:
        """'security' 키워드 → opus"""
        assert route_model("Check the security of this API") == MODEL_OPUS

    def test_performance_keyword_returns_opus(self) -> None:
        """'performance' 키워드 → opus"""
        assert route_model("Improve performance of the database query") == MODEL_OPUS

    def test_debug_keyword_returns_opus(self) -> None:
        """'debug' 키워드 → opus"""
        assert route_model("debug this error") == MODEL_OPUS

    def test_refactor_keyword_returns_opus(self) -> None:
        """'refactor' 키워드 → opus"""
        assert route_model("refactor the module") == MODEL_OPUS

    def test_keyword_case_insensitive(self) -> None:
        """키워드 대소문자 무관"""
        assert route_model("ANALYZE this code") == MODEL_OPUS
        assert route_model("Implement the feature") == MODEL_OPUS

    def test_optimize_keyword_returns_opus(self) -> None:
        """'optimize' 키워드 → opus"""
        assert route_model("optimize the algorithm") == MODEL_OPUS

    def test_migrate_keyword_returns_opus(self) -> None:
        """'migrate' 키워드 → opus"""
        assert route_model("migrate the database schema") == MODEL_OPUS


class TestMediumTaskRouting:
    """보통 길이 텍스트 → sonnet 라우팅 테스트"""

    def test_161_chars_no_complex_keyword_returns_sonnet(self) -> None:
        """161자 단순 텍스트는 sonnet"""
        task = "a" * 161
        assert route_model(task) == MODEL_SONNET

    def test_29_words_no_complex_keyword_returns_sonnet(self) -> None:
        """29단어 단순 텍스트는 sonnet"""
        task = " ".join(["word"] * 29)
        assert route_model(task) == MODEL_SONNET

    def test_medium_paragraph_returns_sonnet(self) -> None:
        """복잡 키워드 없는 중간 길이 문단은 sonnet (161자 초과)"""
        # 29단어 이상, 복잡 키워드 없는 텍스트 (단어수 기준 초과)
        task = " ".join(["word"] * 29)  # 29단어 > 28 임계값
        # 테스트 전제 확인
        assert len(task.split()) > 28
        result = route_model(task)
        assert result == MODEL_SONNET


class TestBoundaryConditions:
    """경계값 테스트"""

    def test_161_chars_with_complex_keyword_returns_opus(self) -> None:
        """161자 + 복잡 키워드 조합 → opus"""
        task = "analyze " + "a" * 153  # 161자 이상 + analyze 포함
        assert route_model(task) == MODEL_OPUS

    def test_empty_string_returns_haiku(self) -> None:
        """빈 문자열은 haiku"""
        assert route_model("") == MODEL_HAIKU

    def test_whitespace_only_returns_haiku(self) -> None:
        """공백만 있는 경우 haiku"""
        assert route_model("   ") == MODEL_HAIKU

    def test_complex_keyword_in_long_text(self) -> None:
        """긴 텍스트에 복잡 키워드 포함 → opus"""
        task = "Please " + "help me " * 30 + "to diagnose this issue"
        assert route_model(task) == MODEL_OPUS


class TestCfgFallback:
    """cfg=None fallback 테스트"""

    def test_cfg_none_uses_defaults(self) -> None:
        """cfg=None 일 때 기본 모델 사용"""
        result = route_model("Hello", cfg=None)
        assert result == MODEL_HAIKU

    def test_cfg_none_complex_uses_opus(self) -> None:
        """cfg=None 복잡 작업은 기본 opus 반환"""
        result = route_model("analyze this code", cfg=None)
        assert result == MODEL_OPUS


class TestConfigOverride:
    """config에서 모델명 읽기 테스트"""

    def test_config_overrides_haiku_model(self) -> None:
        """config에서 haiku 모델명 오버라이드"""
        mock_cfg = MagicMock()
        mock_cfg.get.side_effect = lambda key, default=None: {
            "models.router.haiku": "custom-haiku-model",
            "models.router.sonnet": MODEL_SONNET,
            "models.router.opus": MODEL_OPUS,
        }.get(key, default)

        result = route_model("Hello", cfg=mock_cfg)
        assert result == "custom-haiku-model"

    def test_config_overrides_opus_model(self) -> None:
        """config에서 opus 모델명 오버라이드"""
        mock_cfg = MagicMock()
        mock_cfg.get.side_effect = lambda key, default=None: {
            "models.router.haiku": MODEL_HAIKU,
            "models.router.sonnet": MODEL_SONNET,
            "models.router.opus": "custom-opus-model",
        }.get(key, default)

        result = route_model("analyze this architecture", cfg=mock_cfg)
        assert result == "custom-opus-model"

    def test_config_partial_fallback(self) -> None:
        """config에 일부 키만 있으면 나머지는 기본값 사용"""
        mock_cfg = MagicMock()
        # haiku만 오버라이드, 나머지는 None 반환
        mock_cfg.get.side_effect = lambda key, default=None: default

        result = route_model("Hello", cfg=mock_cfg)
        assert result == MODEL_HAIKU

    def test_config_called_with_correct_keys(self) -> None:
        """config.get()이 올바른 키로 호출되는지 확인"""
        mock_cfg = MagicMock()
        mock_cfg.get.return_value = None

        route_model("Hello", cfg=mock_cfg)

        called_keys = [call.args[0] for call in mock_cfg.get.call_args_list]
        assert "models.router.haiku" in called_keys
