"""
Tests for providers module.
"""

import os
from unittest.mock import patch

import pytest

from config import Settings
from models import MODEL_CONFIGS, ModelGroup, Provider
from providers import (
    LLMProvider,
    build_fallback_config,
    build_model_list,
    configure_litellm,
    get_available_model_groups,
    get_available_models,
    get_provider,
    reset_provider,
)


@pytest.fixture
def full_settings() -> Settings:
    """Settings with all providers configured."""
    return Settings(
        anthropic_api_key="test-anthropic-key",
        openai_api_key="test-openai-key",
        google_api_key="test-google-key",
        alibaba_api_key="test-alibaba-key",
        deepseek_api_key="test-deepseek-key",
        litellm_timeout=60,
        litellm_cache_enabled=False,
    )


@pytest.fixture
def partial_settings() -> Settings:
    """Settings with only some providers configured."""
    return Settings(
        anthropic_api_key="test-anthropic-key",
        openai_api_key=None,
        google_api_key=None,
        alibaba_api_key=None,
        deepseek_api_key=None,
    )


@pytest.fixture
def empty_settings() -> Settings:
    """Settings with no providers configured."""
    return Settings(
        anthropic_api_key=None,
        openai_api_key=None,
        google_api_key=None,
        alibaba_api_key=None,
        deepseek_api_key=None,
    )


class TestConfigureLiteLLM:
    """Tests for configure_litellm function."""

    def test_sets_api_keys(self, full_settings: Settings) -> None:
        """Test that API keys are set in environment."""
        with patch.dict(os.environ, {}, clear=True):
            configure_litellm(full_settings)

            assert os.environ.get("ANTHROPIC_API_KEY") == "test-anthropic-key"
            assert os.environ.get("OPENAI_API_KEY") == "test-openai-key"
            assert os.environ.get("GEMINI_API_KEY") == "test-google-key"

    def test_skips_none_keys(self, partial_settings: Settings) -> None:
        """Test that None keys are not set."""
        with patch.dict(os.environ, {}, clear=True):
            configure_litellm(partial_settings)

            assert os.environ.get("ANTHROPIC_API_KEY") == "test-anthropic-key"
            assert "OPENAI_API_KEY" not in os.environ


class TestBuildModelList:
    """Tests for build_model_list function."""

    def test_build_with_all_providers(self, full_settings: Settings) -> None:
        """Test building model list with all providers."""
        model_list = build_model_list(full_settings)

        assert len(model_list) > 0

        # Check structure
        for entry in model_list:
            assert "model_name" in entry
            assert "litellm_params" in entry
            assert "model" in entry["litellm_params"]
            assert "timeout" in entry["litellm_params"]

    def test_build_with_partial_providers(self, partial_settings: Settings) -> None:
        """Test building model list with partial providers."""
        model_list = build_model_list(partial_settings)

        # Should only include Anthropic models
        providers = set()
        for entry in model_list:
            model_name = entry["model_name"]
            config = MODEL_CONFIGS.get(model_name)
            if config:
                providers.add(config.provider)

        assert Provider.ANTHROPIC in providers
        assert Provider.OPENAI not in providers

    def test_build_with_no_providers(self, empty_settings: Settings) -> None:
        """Test building model list with no providers."""
        model_list = build_model_list(empty_settings)

        assert len(model_list) == 0

    def test_build_includes_timeout(self, full_settings: Settings) -> None:
        """Test that model entries include timeout."""
        model_list = build_model_list(full_settings)

        for entry in model_list:
            assert entry["litellm_params"]["timeout"] == 60


class TestBuildFallbackConfig:
    """Tests for build_fallback_config function."""

    def test_build_fallbacks_full(self, full_settings: Settings) -> None:
        """Test building fallback config with all providers."""
        fallbacks = build_fallback_config(full_settings)

        assert len(fallbacks) > 0

        # Primary models should have fallbacks
        for _primary, chain in fallbacks.items():
            assert isinstance(chain, list)
            assert len(chain) > 0

    def test_build_fallbacks_partial(self, partial_settings: Settings) -> None:
        """Test building fallback config with partial providers."""
        fallbacks = build_fallback_config(partial_settings)

        # With only Anthropic, there should be no fallbacks
        # (fallbacks require at least 2 available models)
        for primary, chain in fallbacks.items():
            # All models in chain should be from Anthropic
            for model in [primary] + chain:
                config = MODEL_CONFIGS.get(model)
                if config:
                    assert config.provider == Provider.ANTHROPIC


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

    def test_get_available_full(self, full_settings: Settings) -> None:
        """Test getting available models with all providers."""
        models = get_available_models(full_settings)

        assert len(models) > 0
        assert "claude-opus-4" in models
        assert "gpt-4.1" in models

    def test_get_available_partial(self, partial_settings: Settings) -> None:
        """Test getting available models with partial providers."""
        models = get_available_models(partial_settings)

        assert "claude-opus-4" in models
        assert "gpt-4.1" not in models

    def test_get_available_empty(self, empty_settings: Settings) -> None:
        """Test getting available models with no providers."""
        models = get_available_models(empty_settings)

        assert len(models) == 0


class TestGetAvailableModelGroups:
    """Tests for get_available_model_groups function."""

    def test_get_groups_full(self, full_settings: Settings) -> None:
        """Test getting groups with all providers."""
        groups = get_available_model_groups(full_settings)

        assert len(groups) == len(ModelGroup)
        assert ModelGroup.REASONING in groups
        assert len(groups[ModelGroup.REASONING]) > 0

    def test_get_groups_partial(self, partial_settings: Settings) -> None:
        """Test getting groups with partial providers."""
        groups = get_available_model_groups(partial_settings)

        # Only Anthropic models should be available
        for _group, models in groups.items():
            for model in models:
                config = MODEL_CONFIGS.get(model)
                if config:
                    assert config.provider == Provider.ANTHROPIC


class TestLLMProvider:
    """Tests for LLMProvider class."""

    def test_initialization(self, full_settings: Settings) -> None:
        """Test provider initialization."""
        provider = LLMProvider(settings=full_settings)

        assert provider._initialized is False
        assert provider._router is None

    def test_initialize(self, full_settings: Settings) -> None:
        """Test provider initialize."""
        with patch("providers.Router") as mock_router:
            provider = LLMProvider(settings=full_settings)
            provider.initialize()

            assert provider._initialized is True
            mock_router.assert_called_once()

    def test_initialize_idempotent(self, full_settings: Settings) -> None:
        """Test that initialize is idempotent."""
        with patch("providers.Router") as mock_router:
            provider = LLMProvider(settings=full_settings)
            provider.initialize()
            provider.initialize()

            # Should only be called once
            assert mock_router.call_count == 1

    def test_initialize_no_providers(self, empty_settings: Settings) -> None:
        """Test initialization with no providers."""
        provider = LLMProvider(settings=empty_settings)
        provider.initialize()

        assert provider._initialized is True
        assert provider._router is None

    def test_router_property(self, full_settings: Settings) -> None:
        """Test router property triggers initialization."""
        with patch("providers.Router"):
            provider = LLMProvider(settings=full_settings)
            _ = provider.router

            assert provider._initialized is True

    def test_is_available(self, full_settings: Settings) -> None:
        """Test is_available property."""
        with patch("providers.Router"):
            provider = LLMProvider(settings=full_settings)
            assert provider.is_available is True

    def test_is_not_available(self, empty_settings: Settings) -> None:
        """Test is_available when no providers."""
        provider = LLMProvider(settings=empty_settings)
        assert provider.is_available is False

    def test_get_model_config(self, full_settings: Settings) -> None:
        """Test getting model config."""
        provider = LLMProvider(settings=full_settings)

        config = provider.get_model_config("claude-opus-4")
        assert config is not None
        assert config.name == "claude-opus-4"

        assert provider.get_model_config("nonexistent") is None

    def test_get_available_models(self, full_settings: Settings) -> None:
        """Test getting available models."""
        provider = LLMProvider(settings=full_settings)
        models = provider.get_available_models()

        assert "claude-opus-4" in models
        assert "gpt-4.1" in models

    def test_is_model_available(self, full_settings: Settings) -> None:
        """Test checking model availability."""
        provider = LLMProvider(settings=full_settings)

        assert provider.is_model_available("claude-opus-4") is True
        assert provider.is_model_available("nonexistent") is False


class TestGlobalProvider:
    """Tests for global provider functions."""

    def test_get_provider(self) -> None:
        """Test getting global provider."""
        reset_provider()
        provider = get_provider()
        assert isinstance(provider, LLMProvider)

    def test_get_provider_singleton(self) -> None:
        """Test provider is singleton."""
        reset_provider()
        provider1 = get_provider()
        provider2 = get_provider()
        assert provider1 is provider2

    def test_reset_provider(self) -> None:
        """Test resetting global provider."""
        reset_provider()
        provider1 = get_provider()
        reset_provider()
        provider2 = get_provider()
        assert provider1 is not provider2
