"""
Tests for LLM config context window and token counting.

Tests cover:
- Context window calculation
- Token counting integration
- Settings integration
"""

from unittest.mock import Mock


class TestContextWindowCalculation:
    """Tests for context window size calculation."""

    def test_context_window_local_provider_detection(self):
        """Local providers are detected correctly."""
        local_providers = ["ollama", "llamacpp", "lmstudio"]
        cloud_providers = ["openai", "anthropic", "google"]

        for provider in local_providers:
            is_local = provider in ["ollama", "llamacpp", "lmstudio"]
            assert is_local, f"{provider} should be detected as local"

        for provider in cloud_providers:
            is_local = provider in ["ollama", "llamacpp", "lmstudio"]
            assert not is_local, f"{provider} should not be detected as local"

    def test_context_window_cloud_provider_detection(self):
        """Cloud providers are detected correctly."""
        cloud_providers = ["openai", "anthropic", "google", "openrouter"]

        for provider in cloud_providers:
            is_cloud = provider not in [
                "ollama",
                "llamacpp",
                "lmstudio",
                "vllm",
            ]
            assert is_cloud, f"{provider} should be detected as cloud"

    def test_context_window_unrestricted_mode(self):
        """Unrestricted mode returns None for cloud providers."""
        use_unrestricted = True
        provider = "openai"

        if use_unrestricted and provider not in [
            "ollama",
            "llamacpp",
            "lmstudio",
        ]:
            context_window = None
        else:
            context_window = 128000

        assert context_window is None

    def test_context_window_restricted_mode(self):
        """Restricted mode uses configured window size."""
        use_unrestricted = False
        configured_size = 32000

        if not use_unrestricted:
            context_window = configured_size
        else:
            context_window = None

        assert context_window == 32000

    def test_context_window_max_tokens_80_percent(self):
        """Max tokens is 80% of context window."""
        context_window_size = 4096
        max_tokens_setting = 100000

        # 80% of context window
        calculated_max_tokens = int(context_window_size * 0.8)

        # Use minimum of setting and 80%
        max_tokens = min(max_tokens_setting, calculated_max_tokens)

        assert max_tokens == 3276  # 80% of 4096

    def test_context_window_context_limit_overflow_detection(self):
        """Context limit is set in research context for overflow detection."""
        research_context = {}
        context_window_size = 8192

        if research_context is not None and context_window_size:
            research_context["context_limit"] = context_window_size

        assert "context_limit" in research_context
        assert research_context["context_limit"] == 8192

    def test_context_window_ollama_specific_handling(self):
        """Ollama uses local context window size."""
        provider = "ollama"
        local_context_window_size = 4096
        cloud_context_window_size = 128000

        if provider in ["ollama", "llamacpp", "lmstudio"]:
            window_size = local_context_window_size
        else:
            window_size = cloud_context_window_size

        assert window_size == 4096

    def test_context_window_anthropic_specific_handling(self):
        """Anthropic uses cloud context handling."""
        provider = "anthropic"
        use_unrestricted = True

        if (
            provider not in ["ollama", "llamacpp", "lmstudio"]
            and use_unrestricted
        ):
            window_size = None  # Let provider auto-handle
        else:
            window_size = 200000

        assert window_size is None

    def test_context_window_openai_specific_handling(self):
        """OpenAI uses cloud context handling."""
        provider = "openai"
        use_unrestricted = False
        configured_size = 128000

        if (
            provider not in ["ollama", "llamacpp", "lmstudio"]
            and not use_unrestricted
        ):
            window_size = configured_size
        else:
            window_size = None

        assert window_size == 128000

    def test_context_window_custom_endpoint_handling(self):
        """Custom OpenAI endpoint uses cloud handling."""
        provider = "openai_endpoint"
        is_local = provider in ["ollama", "llamacpp", "lmstudio"]

        assert not is_local

    def test_context_window_default_fallback(self):
        """Default context window is used when not configured."""
        default_local_window = 4096
        default_cloud_window = 128000

        # Local default
        assert default_local_window == 4096

        # Cloud default
        assert default_cloud_window == 128000

    def test_context_window_model_name_lookup(self):
        """Context window lookup by model name."""
        model_context_windows = {
            "gpt-4": 128000,
            "gpt-3.5-turbo": 16385,
            "claude-3-opus": 200000,
            "mistral": 4096,
        }

        assert model_context_windows.get("gpt-4") == 128000
        assert model_context_windows.get("claude-3-opus") == 200000
        assert model_context_windows.get("unknown", 4096) == 4096


class TestTokenCountingIntegration:
    """Tests for token counting callback integration."""

    def test_token_counting_callback_attachment(self):
        """Token counting callback is attached to LLM."""
        research_id = 123
        callbacks = []

        if research_id is not None:
            # Create mock callback
            mock_callback = Mock()
            callbacks.append(mock_callback)

        assert len(callbacks) == 1

    def test_token_counting_provider_preset(self):
        """Provider is preset on token callback."""
        provider = "openai"
        token_callback = Mock()

        if provider:
            token_callback.preset_provider = provider

        assert token_callback.preset_provider == "openai"

    def test_token_counting_model_preset(self):
        """Model name is preset on token callback."""
        model_name = "gpt-4"
        token_callback = Mock()

        token_callback.preset_model = model_name

        assert token_callback.preset_model == "gpt-4"

    def test_token_counting_research_context_mutation(self):
        """Research context is updated with token counts."""
        research_context = {"context_limit": 4096}
        token_count = {"prompt_tokens": 100, "completion_tokens": 200}

        research_context.update(token_count)

        assert research_context["prompt_tokens"] == 100
        assert research_context["completion_tokens"] == 200

    def test_token_counting_prompt_tokens(self):
        """Prompt tokens are counted correctly."""
        prompt_tokens = 150

        assert prompt_tokens > 0
        assert isinstance(prompt_tokens, int)

    def test_token_counting_completion_tokens(self):
        """Completion tokens are counted correctly."""
        completion_tokens = 250

        assert completion_tokens > 0
        assert isinstance(completion_tokens, int)

    def test_token_counting_total_accumulation(self):
        """Total tokens accumulate correctly."""
        calls = [
            {"prompt": 100, "completion": 200},
            {"prompt": 150, "completion": 300},
            {"prompt": 50, "completion": 100},
        ]

        total_prompt = sum(c["prompt"] for c in calls)
        total_completion = sum(c["completion"] for c in calls)

        assert total_prompt == 300
        assert total_completion == 600

    def test_token_counting_error_handling(self):
        """Token counting handles errors gracefully."""
        error_occurred = False

        try:
            # Simulate token counting
            pass
        except Exception:
            error_occurred = True

        assert not error_occurred


class TestSettingsIntegration:
    """Tests for settings snapshot integration."""

    def test_settings_snapshot_provider_selection(self):
        """Provider is selected from settings snapshot."""
        snapshot = {"llm.provider": "anthropic"}

        provider = snapshot.get("llm.provider", "ollama")

        assert provider == "anthropic"

    def test_settings_snapshot_model_override(self):
        """Model can be overridden via parameter."""
        snapshot = {"llm.model": "default-model"}
        override_model = "custom-model"

        model = override_model if override_model else snapshot.get("llm.model")

        assert model == "custom-model"

    def test_settings_snapshot_temperature_override(self):
        """Temperature can be overridden via parameter."""
        snapshot = {"llm.temperature": 0.7}
        override_temperature = 0.3

        temperature = (
            override_temperature
            if override_temperature is not None
            else snapshot.get("llm.temperature", 0.7)
        )

        assert temperature == 0.3

    def test_settings_snapshot_missing_key_defaults(self):
        """Missing keys use default values."""
        snapshot = {}

        provider = snapshot.get("llm.provider", "ollama")
        model = snapshot.get("llm.model", "gemma:latest")
        temperature = snapshot.get("llm.temperature", 0.7)

        assert provider == "ollama"
        assert model == "gemma:latest"
        assert temperature == 0.7

    def test_settings_snapshot_invalid_type_handling(self):
        """Invalid setting types are handled."""
        snapshot = {
            "llm.temperature": "not_a_number",
            "llm.max_tokens": "invalid",
        }

        # Temperature should be converted or default used
        try:
            temperature = float(snapshot.get("llm.temperature", 0.7))
        except (ValueError, TypeError):
            temperature = 0.7

        assert temperature == 0.7


class TestContextWindowEdgeCases:
    """Tests for context window edge cases."""

    def test_context_window_zero_value(self):
        """Zero context window uses default."""
        configured_size = 0
        default_size = 4096

        window_size = configured_size if configured_size > 0 else default_size

        assert window_size == 4096

    def test_context_window_negative_value(self):
        """Negative context window uses default."""
        configured_size = -1000
        default_size = 4096

        window_size = configured_size if configured_size > 0 else default_size

        assert window_size == 4096

    def test_context_window_very_large_value(self):
        """Very large context window is capped."""
        configured_size = 10000000  # 10M tokens
        max_allowed = 1000000  # 1M tokens

        window_size = min(configured_size, max_allowed)

        assert window_size == max_allowed

    def test_context_window_float_conversion(self):
        """Float context window is converted to int."""
        configured_size = 4096.5

        window_size = int(configured_size) if configured_size else 4096

        assert window_size == 4096
        assert isinstance(window_size, int)
