"""
Tests for LLMFactory
"""

import pytest
from unittest.mock import MagicMock, patch

from app.services.llm.factory import LLMFactory, NATIVE_ONLY_PROVIDERS
from app.services.llm.types import LLMConfig, LLMProvider


class TestNativeOnlyProviders:
    """Tests for NATIVE_ONLY_PROVIDERS constant"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_contains_baidu(self):
        assert LLMProvider.BAIDU in NATIVE_ONLY_PROVIDERS

    def test_contains_minimax(self):
        assert LLMProvider.MINIMAX in NATIVE_ONLY_PROVIDERS

    def test_contains_doubao(self):
        assert LLMProvider.DOUBAO in NATIVE_ONLY_PROVIDERS

    def test_does_not_contain_openai(self):
        assert LLMProvider.OPENAI not in NATIVE_ONLY_PROVIDERS


class TestGetCacheKey:
    """Tests for LLMFactory._get_cache_key"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_returns_provider_model_key_prefix(self):
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="sk-1234567890abcdef",
            model="gpt-4o",
        )
        result = LLMFactory._get_cache_key(config)
        assert result == "openai:gpt-4o:sk-12345"

    def test_returns_no_key_when_empty(self):
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="",
            model="gpt-4o",
        )
        result = LLMFactory._get_cache_key(config)
        assert result == "openai:gpt-4o:no-key"


class TestClearCache:
    """Tests for LLMFactory.clear_cache"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_clears_adapters(self):
        # Manually inject a dummy entry
        LLMFactory._adapters["dummy_key"] = MagicMock()
        assert len(LLMFactory._adapters) > 0

        LLMFactory.clear_cache()
        assert len(LLMFactory._adapters) == 0


class TestGetSupportedProviders:
    """Tests for LLMFactory.get_supported_providers"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_returns_all_providers(self):
        providers = LLMFactory.get_supported_providers()
        assert len(providers) == len(LLMProvider)
        for p in LLMProvider:
            assert p in providers


class TestGetDefaultModel:
    """Tests for LLMFactory.get_default_model"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_known_provider_returns_default(self):
        model = LLMFactory.get_default_model(LLMProvider.OPENAI)
        assert model == "gpt-5"

    def test_unknown_provider_returns_fallback(self):
        # All current providers have defaults, but the fallback is tested
        # by calling get_default_model with a valid provider and verifying
        # the fallback logic. Since all providers have defaults, we patch.
        from app.services.llm.types import DEFAULT_MODELS

        with patch.dict(DEFAULT_MODELS, {}, clear=True):
            model = LLMFactory.get_default_model(LLMProvider.OPENAI)
            assert model == "gpt-4o-mini"


class TestGetAvailableModels:
    """Tests for LLMFactory.get_available_models"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    def test_known_provider_returns_nonempty_list(self):
        models = LLMFactory.get_available_models(LLMProvider.OPENAI)
        assert isinstance(models, list)
        assert len(models) > 0
        assert "gpt-5" in models

    def test_unknown_provider_returns_empty(self):
        # Use a mocked scenario where provider is not in the models dict
        with patch.object(
            LLMFactory,
            "get_available_models",
            side_effect=lambda p: [] if p == "nonexistent" else LLMFactory.get_available_models.__wrapped__(LLMFactory, p),
        ):
            # Directly test the fallback by calling the internal logic
            pass
        # Simpler: just verify an unknown string-based provider returns []
        # The method takes LLMProvider enum, but the dict lookup returns [] for missing keys
        # We can test by patching the internal models dict
        models = LLMFactory.get_available_models(LLMProvider.OPENAI)
        assert models  # known provider has models

        # Test with an enum value that isn't in the dict by patching
        original_method = LLMFactory.get_available_models

        def patched_get_available_models(provider):
            models_dict = {}  # empty dict forces fallback
            return models_dict.get(provider, [])

        with patch.object(LLMFactory, "get_available_models", patched_get_available_models):
            result = LLMFactory.get_available_models(LLMProvider.OPENAI)
            assert result == []


class TestCreateAdapter:
    """Tests for LLMFactory.create_adapter caching behavior"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    @patch("app.services.llm.factory.LiteLLMAdapter")
    def test_returns_cached_instance_for_same_config(self, mock_litellm):
        mock_litellm.supports_provider.return_value = True
        mock_instance = MagicMock()
        mock_litellm.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="sk-1234567890abcdef",
            model="gpt-4o",
        )
        adapter1 = LLMFactory.create_adapter(config)
        adapter2 = LLMFactory.create_adapter(config)

        assert adapter1 is adapter2
        # Only instantiated once despite two calls
        mock_litellm.assert_called_once_with(config)

    @patch("app.services.llm.factory.LiteLLMAdapter")
    def test_creates_new_instance_for_different_config(self, mock_litellm):
        mock_litellm.supports_provider.return_value = True
        mock_instance1 = MagicMock()
        mock_instance2 = MagicMock()
        mock_litellm.side_effect = [mock_instance1, mock_instance2]

        config1 = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="sk-1234567890abcdef",
            model="gpt-4o",
        )
        config2 = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="sk-abcdefgh12345678",
            model="gpt-4o",
        )
        adapter1 = LLMFactory.create_adapter(config1)
        adapter2 = LLMFactory.create_adapter(config2)

        assert adapter1 is not adapter2


class TestInstantiateAdapter:
    """Tests for LLMFactory._instantiate_adapter"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    @patch("app.services.llm.factory.BaiduAdapter")
    def test_native_only_provider_creates_native_adapter(self, mock_baidu):
        mock_instance = MagicMock()
        mock_baidu.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.BAIDU,
            api_key="test-key",
            model="ernie-4.5",
        )
        result = LLMFactory._instantiate_adapter(config)
        assert result is mock_instance
        mock_baidu.assert_called_once_with(config)

    @patch("app.services.llm.factory.LiteLLMAdapter")
    def test_non_native_provider_uses_litellm(self, mock_litellm):
        mock_litellm.supports_provider.return_value = True
        mock_instance = MagicMock()
        mock_litellm.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="test-key",
            model="gpt-4o",
        )
        result = LLMFactory._instantiate_adapter(config)
        assert result is mock_instance

    @patch("app.services.llm.factory.LiteLLMAdapter")
    def test_unsupported_provider_raises_value_error(self, mock_litellm):
        mock_litellm.supports_provider.return_value = False

        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="test-key",
            model="gpt-4o",
        )
        with pytest.raises(ValueError, match="不支持的LLM提供商"):
            LLMFactory._instantiate_adapter(config)

    def test_empty_model_uses_default(self):
        """When model is empty, _instantiate_adapter assigns the default model."""
        with patch("app.services.llm.factory.LiteLLMAdapter") as mock_litellm:
            mock_litellm.supports_provider.return_value = True
            mock_litellm.return_value = MagicMock()

            config = LLMConfig(
                provider=LLMProvider.OPENAI,
                api_key="test-key",
                model="",
            )
            LLMFactory._instantiate_adapter(config)
            assert config.model == "gpt-5"


class TestCreateNativeAdapter:
    """Tests for LLMFactory._create_native_adapter"""

    @pytest.fixture(autouse=True)
    def setup(self):
        LLMFactory.clear_cache()

    @patch("app.services.llm.factory.BaiduAdapter")
    def test_baidu_creates_baidu_adapter(self, mock_baidu):
        mock_instance = MagicMock()
        mock_baidu.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.BAIDU,
            api_key="test-key",
            model="ernie-4.5",
        )
        result = LLMFactory._create_native_adapter(config)
        assert result is mock_instance

    @patch("app.services.llm.factory.MinimaxAdapter")
    def test_minimax_creates_minimax_adapter(self, mock_minimax):
        mock_instance = MagicMock()
        mock_minimax.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.MINIMAX,
            api_key="test-key",
            model="minimax-m2",
        )
        result = LLMFactory._create_native_adapter(config)
        assert result is mock_instance

    @patch("app.services.llm.factory.DoubaoAdapter")
    def test_doubao_creates_doubao_adapter(self, mock_doubao):
        mock_instance = MagicMock()
        mock_doubao.return_value = mock_instance

        config = LLMConfig(
            provider=LLMProvider.DOUBAO,
            api_key="test-key",
            model="doubao-1.6-pro",
        )
        result = LLMFactory._create_native_adapter(config)
        assert result is mock_instance

    def test_unsupported_provider_raises_value_error(self):
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            api_key="test-key",
            model="gpt-4o",
        )
        with pytest.raises(ValueError, match="不支持的原生适配器提供商"):
            LLMFactory._create_native_adapter(config)
