"""
Tests for LLM config fallback chain activation.

Tests cover:
- Fallback chain activation
- Custom LLM registration
"""

from unittest.mock import Mock
import pytest


class TestFallbackChainActivation:
    """Tests for fallback chain activation."""

    def test_fallback_llm_env_var_true(self):
        """Fallback LLM activated by env var."""
        env_value = "true"

        use_fallback = bool(env_value)

        assert use_fallback

    def test_fallback_llm_env_var_false(self):
        """Fallback LLM not activated when env var is empty."""
        env_value = ""

        use_fallback = bool(env_value)

        assert not use_fallback

    def test_fallback_llm_env_var_missing(self):
        """Fallback LLM not activated when env var is missing."""
        env_value = None

        use_fallback = bool(env_value) if env_value else False

        assert not use_fallback

    def test_fallback_chain_missing_config_level_1(self):
        """Missing API key triggers fallback."""
        api_key = None
        provider = "openai"

        if provider in ["openai", "anthropic"] and not api_key:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_chain_missing_config_level_2(self):
        """Missing endpoint URL triggers fallback."""
        endpoint_url = None
        provider = "openai_endpoint"

        if provider == "openai_endpoint" and not endpoint_url:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_chain_all_providers_unavailable(self):
        """All providers unavailable triggers fallback."""
        available_providers = {}

        if not available_providers:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_chain_provider_config_validation(self):
        """Provider configuration is validated."""
        config = {
            "provider": "openai",
            "api_key": None,  # Missing
            "model": "gpt-4",
        }

        required_fields = ["provider", "api_key", "model"]
        is_valid = all(config.get(field) for field in required_fields)

        assert not is_valid

    def test_fallback_chain_cascading_execution(self):
        """Fallback cascades through providers."""
        providers_tried = []
        providers = ["openai", "anthropic", "ollama"]

        for provider in providers:
            providers_tried.append(provider)
            # Simulate failure
            if provider == "ollama":
                success = True
                break
        else:
            success = False

        assert success
        assert providers_tried == ["openai", "anthropic", "ollama"]

    def test_fallback_model_returns_fake_list_chat_model(self):
        """Fallback model returns FakeListChatModel."""
        from local_deep_research.config.llm_config import get_fallback_model

        model = get_fallback_model(temperature=0.7)

        assert model is not None
        assert hasattr(model, "invoke")

    def test_fallback_model_message_content(self):
        """Fallback model returns helpful message."""
        from local_deep_research.config.llm_config import get_fallback_model

        model = get_fallback_model()

        # FakeListChatModel has responses attribute
        assert hasattr(model, "responses")
        assert len(model.responses) > 0
        assert "No language models are available" in model.responses[0]

    def test_fallback_model_invocation(self):
        """Fallback model can be invoked."""
        from local_deep_research.config.llm_config import get_fallback_model

        model = get_fallback_model()
        response = model.invoke("test query")

        assert response is not None

    def test_fallback_registration_cleanup(self):
        """Fallback registration is cleaned up properly."""
        registry = {"custom_provider": Mock()}

        # Cleanup
        del registry["custom_provider"]

        assert "custom_provider" not in registry


class TestCustomLLMRegistration:
    """Tests for custom LLM registration."""

    def test_custom_llm_factory_function_detection(self):
        """Factory function is detected correctly."""

        def factory_func(model_name, temperature, settings_snapshot):
            return Mock()

        is_callable = callable(factory_func)
        is_instance = isinstance(factory_func, type)

        assert is_callable
        assert not is_instance

    def test_custom_llm_instance_detection(self):
        """LLM instance is detected correctly."""
        mock_llm = Mock()

        callable(mock_llm)  # Mock is callable
        has_invoke = hasattr(mock_llm, "invoke")

        # Mock has invoke
        assert has_invoke

    def test_custom_llm_bad_signature_error(self):
        """Bad factory signature raises error."""

        def bad_factory(only_one_param):
            return Mock()

        with pytest.raises(TypeError):
            # Simulate calling with expected params
            bad_factory(
                model_name="test",
                temperature=0.7,
                settings_snapshot={},
            )

    def test_custom_llm_returned_type_validation(self):
        """Factory must return correct type."""

        def factory_func(model_name, temperature, settings_snapshot):
            return "not a model"  # Wrong type

        result = factory_func("test", 0.7, {})

        # Should be validated as not a model
        is_valid = hasattr(result, "invoke")

        assert not is_valid

    def test_custom_llm_non_base_chat_model_error(self):
        """Non-BaseChatModel raises error."""

        class NotAChatModel:
            pass

        result = NotAChatModel()

        # Check if it would pass validation
        from langchain_core.language_models import BaseChatModel

        is_valid = isinstance(result, BaseChatModel)

        assert not is_valid

    def test_custom_llm_registration_persistence(self):
        """Custom LLM registration persists."""
        registry = {}

        # Register
        registry["custom"] = Mock()

        # Check persistence
        assert "custom" in registry

        # Still there
        assert registry.get("custom") is not None

    def test_custom_llm_override_existing(self):
        """Custom LLM can override existing."""
        registry = {"custom": Mock(name="original")}

        # Override
        registry["custom"] = Mock(name="override")

        assert registry["custom"]._mock_name == "override"

    def test_custom_llm_thread_safety(self):
        """Custom LLM registration is thread-safe."""
        import threading

        registry = {}
        lock = threading.Lock()

        def register(name, llm):
            with lock:
                registry[name] = llm

        threads = []
        for i in range(10):
            t = threading.Thread(target=register, args=(f"llm_{i}", Mock()))
            threads.append(t)
            t.start()

        for t in threads:
            t.join()

        assert len(registry) == 10


class TestFallbackConditions:
    """Tests for various fallback conditions."""

    def test_fallback_on_import_error(self):
        """Import error triggers fallback."""
        import_error = True

        if import_error:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_on_initialization_error(self):
        """Initialization error triggers fallback."""
        init_error = True

        if init_error:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_on_network_error(self):
        """Network error triggers fallback."""
        network_error = True

        if network_error:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback

    def test_fallback_on_authentication_error(self):
        """Authentication error triggers fallback."""
        auth_error = True
        error_code = 401

        if auth_error or error_code in [401, 403]:
            use_fallback = True
        else:
            use_fallback = False

        assert use_fallback


class TestFallbackMessages:
    """Tests for fallback message generation."""

    def test_fallback_message_no_providers(self):
        """No providers available message."""
        available_providers = {}

        if not available_providers:
            message = "No language models are available. Please install Ollama or set up API keys."
        else:
            message = "Model ready"

        assert "No language models" in message

    def test_fallback_message_provider_specific(self):
        """Provider-specific fallback message."""
        provider = "openai"
        error = "API key missing"

        message = f"Failed to initialize {provider}: {error}"

        assert "openai" in message.lower()
        assert "API key" in message

    def test_fallback_message_with_suggestions(self):
        """Fallback message includes suggestions."""
        message = "No language models are available. Please install Ollama or set up API keys."

        has_suggestion = (
            "install Ollama" in message or "set up API keys" in message
        )

        assert has_suggestion
