"""Tests for context management configuration."""

import os
from unittest.mock import patch

import pytest

from app.services.context.config import (
    ContextSettings,
    get_context_settings,
    get_default_settings,
    reset_context_settings,
)


class TestContextSettings:
    """Tests for ContextSettings."""

    def test_default_values(self) -> None:
        """Test default settings values."""
        settings = ContextSettings()

        # Budget defaults should sum to 1.0
        total = (
            settings.budget_system
            + settings.budget_task
            + settings.budget_knowledge
            + settings.budget_conversation
            + settings.budget_tools
            + settings.budget_response
            + settings.budget_buffer
        )
        assert abs(total - 1.0) < 0.001

        # Scoring weights should sum to 1.0
        weights_total = (
            settings.scoring_relevance_weight
            + settings.scoring_recency_weight
            + settings.scoring_priority_weight
        )
        assert abs(weights_total - 1.0) < 0.001

    def test_budget_allocation_values(self) -> None:
        """Test specific budget allocation values."""
        settings = ContextSettings()

        assert settings.budget_system == 0.05
        assert settings.budget_task == 0.10
        assert settings.budget_knowledge == 0.40
        assert settings.budget_conversation == 0.20
        assert settings.budget_tools == 0.05
        assert settings.budget_response == 0.15
        assert settings.budget_buffer == 0.05

    def test_scoring_weights(self) -> None:
        """Test scoring weights."""
        settings = ContextSettings()

        assert settings.scoring_relevance_weight == 0.5
        assert settings.scoring_recency_weight == 0.3
        assert settings.scoring_priority_weight == 0.2

    def test_cache_settings(self) -> None:
        """Test cache settings."""
        settings = ContextSettings()

        assert settings.cache_enabled is True
        assert settings.cache_ttl_seconds == 3600
        assert settings.cache_prefix == "ctx"

    def test_performance_settings(self) -> None:
        """Test performance settings."""
        settings = ContextSettings()

        assert settings.max_assembly_time_ms == 2000
        assert settings.parallel_scoring is True
        assert settings.max_parallel_scores == 10

    def test_get_budget_allocation(self) -> None:
        """Test get_budget_allocation method."""
        settings = ContextSettings()
        allocation = settings.get_budget_allocation()

        assert isinstance(allocation, dict)
        assert "system" in allocation
        assert "knowledge" in allocation
        assert allocation["system"] == 0.05
        assert allocation["knowledge"] == 0.40

    def test_get_scoring_weights(self) -> None:
        """Test get_scoring_weights method."""
        settings = ContextSettings()
        weights = settings.get_scoring_weights()

        assert isinstance(weights, dict)
        assert "relevance" in weights
        assert "recency" in weights
        assert "priority" in weights
        assert weights["relevance"] == 0.5

    def test_to_dict(self) -> None:
        """Test to_dict method."""
        settings = ContextSettings()
        result = settings.to_dict()

        assert "budget" in result
        assert "scoring" in result
        assert "compression" in result
        assert "cache" in result
        assert "performance" in result
        assert "knowledge" in result
        assert "conversation" in result

    def test_budget_validation_fails_on_wrong_sum(self) -> None:
        """Test that budget validation fails when sum != 1.0."""
        with pytest.raises(ValueError) as exc_info:
            ContextSettings(
                budget_system=0.5,
                budget_task=0.5,
                # Other budgets default to non-zero, so total > 1.0
            )

        assert "sum to 1.0" in str(exc_info.value)

    def test_scoring_validation_fails_on_wrong_sum(self) -> None:
        """Test that scoring validation fails when sum != 1.0."""
        with pytest.raises(ValueError) as exc_info:
            ContextSettings(
                scoring_relevance_weight=0.8,
                scoring_recency_weight=0.8,
                scoring_priority_weight=0.8,
            )

        assert "sum to 1.0" in str(exc_info.value)

    def test_search_type_validation(self) -> None:
        """Test search type validation."""
        # Valid types should work
        ContextSettings(knowledge_search_type="semantic")
        ContextSettings(knowledge_search_type="keyword")
        ContextSettings(knowledge_search_type="hybrid")

        # Invalid type should fail
        with pytest.raises(ValueError):
            ContextSettings(knowledge_search_type="invalid")

    def test_custom_budget_allocation(self) -> None:
        """Test custom budget allocation that sums to 1.0."""
        settings = ContextSettings(
            budget_system=0.10,
            budget_task=0.10,
            budget_knowledge=0.30,
            budget_conversation=0.25,
            budget_tools=0.05,
            budget_response=0.15,
            budget_buffer=0.05,
        )

        total = (
            settings.budget_system
            + settings.budget_task
            + settings.budget_knowledge
            + settings.budget_conversation
            + settings.budget_tools
            + settings.budget_response
            + settings.budget_buffer
        )
        assert abs(total - 1.0) < 0.001


class TestSettingsSingleton:
    """Tests for settings singleton pattern."""

    def setup_method(self) -> None:
        """Reset settings before each test."""
        reset_context_settings()

    def teardown_method(self) -> None:
        """Clean up after each test."""
        reset_context_settings()

    def test_get_context_settings_returns_instance(self) -> None:
        """Test that get_context_settings returns a settings instance."""
        settings = get_context_settings()
        assert isinstance(settings, ContextSettings)

    def test_get_context_settings_returns_same_instance(self) -> None:
        """Test that get_context_settings returns the same instance."""
        settings1 = get_context_settings()
        settings2 = get_context_settings()
        assert settings1 is settings2

    def test_reset_creates_new_instance(self) -> None:
        """Test that reset creates a new instance."""
        settings1 = get_context_settings()
        reset_context_settings()
        settings2 = get_context_settings()

        # Should be different instances
        assert settings1 is not settings2

    def test_get_default_settings_cached(self) -> None:
        """Test that get_default_settings is cached."""
        settings1 = get_default_settings()
        settings2 = get_default_settings()
        assert settings1 is settings2


class TestEnvironmentOverrides:
    """Tests for environment variable overrides."""

    def setup_method(self) -> None:
        """Reset settings before each test."""
        reset_context_settings()

    def teardown_method(self) -> None:
        """Clean up after each test."""
        reset_context_settings()
        # Clean up any env vars we set
        for key in list(os.environ.keys()):
            if key.startswith("CTX_"):
                del os.environ[key]

    def test_env_override_cache_enabled(self) -> None:
        """Test that CTX_CACHE_ENABLED env var works."""
        with patch.dict(os.environ, {"CTX_CACHE_ENABLED": "false"}):
            reset_context_settings()
            settings = ContextSettings()
            assert settings.cache_enabled is False

    def test_env_override_cache_ttl(self) -> None:
        """Test that CTX_CACHE_TTL_SECONDS env var works."""
        with patch.dict(os.environ, {"CTX_CACHE_TTL_SECONDS": "7200"}):
            reset_context_settings()
            settings = ContextSettings()
            assert settings.cache_ttl_seconds == 7200

    def test_env_override_max_assembly_time(self) -> None:
        """Test that CTX_MAX_ASSEMBLY_TIME_MS env var works."""
        with patch.dict(os.environ, {"CTX_MAX_ASSEMBLY_TIME_MS": "200"}):
            reset_context_settings()
            settings = ContextSettings()
            assert settings.max_assembly_time_ms == 200
