"""Tests for metrics pricing_fetcher module."""

import pytest

from local_deep_research.metrics.pricing.pricing_fetcher import PricingFetcher


class TestPricingFetcherInit:
    """Tests for PricingFetcher initialization."""

    def test_initializes_with_no_session(self):
        """Should initialize with session as None."""
        fetcher = PricingFetcher()
        assert fetcher.session is None

    def test_loads_static_pricing_on_init(self):
        """Should load static pricing on initialization."""
        fetcher = PricingFetcher()
        assert fetcher.static_pricing is not None
        assert isinstance(fetcher.static_pricing, dict)


class TestPricingFetcherStaticPricing:
    """Tests for static pricing data."""

    def test_static_pricing_includes_openai_models(self):
        """Should include OpenAI models."""
        fetcher = PricingFetcher()
        assert "gpt-4" in fetcher.static_pricing
        assert "gpt-4-turbo" in fetcher.static_pricing
        assert "gpt-4o" in fetcher.static_pricing
        assert "gpt-4o-mini" in fetcher.static_pricing
        assert "gpt-3.5-turbo" in fetcher.static_pricing

    def test_static_pricing_includes_anthropic_models(self):
        """Should include Anthropic models."""
        fetcher = PricingFetcher()
        assert "claude-3-opus" in fetcher.static_pricing
        assert "claude-3-sonnet" in fetcher.static_pricing
        assert "claude-3-haiku" in fetcher.static_pricing
        assert "claude-3-5-sonnet" in fetcher.static_pricing

    def test_static_pricing_includes_google_models(self):
        """Should include Google models."""
        fetcher = PricingFetcher()
        assert "gemini-pro" in fetcher.static_pricing
        assert "gemini-1.5-pro" in fetcher.static_pricing
        assert "gemini-1.5-flash" in fetcher.static_pricing

    def test_static_pricing_includes_local_models(self):
        """Should include local/free models with zero pricing."""
        fetcher = PricingFetcher()
        local_models = ["ollama", "llama", "mistral", "gemma", "qwen"]

        for model in local_models:
            assert model in fetcher.static_pricing
            assert fetcher.static_pricing[model]["prompt"] == 0.0
            assert fetcher.static_pricing[model]["completion"] == 0.0

    def test_static_pricing_has_correct_structure(self):
        """Each model should have prompt and completion prices."""
        fetcher = PricingFetcher()

        for model, pricing in fetcher.static_pricing.items():
            assert "prompt" in pricing, f"Model {model} missing prompt price"
            assert "completion" in pricing, (
                f"Model {model} missing completion price"
            )
            assert isinstance(pricing["prompt"], (int, float))
            assert isinstance(pricing["completion"], (int, float))


class TestPricingFetcherAsyncContext:
    """Tests for async context manager."""

    @pytest.mark.asyncio
    async def test_async_context_manager_creates_session(self):
        """Should create aiohttp session on enter."""
        async with PricingFetcher() as fetcher:
            assert fetcher.session is not None

    @pytest.mark.asyncio
    async def test_async_context_manager_closes_session(self):
        """Should close session on exit."""
        fetcher = PricingFetcher()
        async with fetcher:
            session = fetcher.session

        assert fetcher.session is None or session.closed


class TestPricingFetcherGetModelPricing:
    """Tests for get_model_pricing method."""

    @pytest.mark.asyncio
    async def test_returns_zero_cost_for_local_providers(self):
        """Should return zero cost for local providers like ollama."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.get_model_pricing("any-model", "ollama")

            assert result == {"prompt": 0.0, "completion": 0.0}

    @pytest.mark.asyncio
    async def test_returns_pricing_for_exact_model_match(self):
        """Should return pricing for exact model name match."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.get_model_pricing("gpt-4")

            assert result is not None
            assert result["prompt"] == 0.03
            assert result["completion"] == 0.06

    @pytest.mark.asyncio
    async def test_returns_pricing_with_provider_prefix_stripped(self):
        """Should handle model names with provider prefix."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.get_model_pricing("openai/gpt-4o-mini")

            assert result is not None
            assert "prompt" in result
            assert "completion" in result

    @pytest.mark.asyncio
    async def test_returns_none_for_unknown_model(self):
        """Should return None for completely unknown models."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.get_model_pricing("unknown-model-xyz")

            assert result is None

    @pytest.mark.asyncio
    async def test_local_providers_list(self):
        """Should recognize all local providers."""
        local_providers = ["ollama", "lmstudio", "llamacpp"]

        async with PricingFetcher() as fetcher:
            for provider in local_providers:
                result = await fetcher.get_model_pricing("any-model", provider)
                assert result == {"prompt": 0.0, "completion": 0.0}


class TestPricingFetcherGetModelsByProvider:
    """Tests for _get_models_by_provider method."""

    def test_returns_openai_models(self):
        """Should return GPT models for openai provider."""
        fetcher = PricingFetcher()
        models = fetcher._get_models_by_provider("openai")

        assert len(models) > 0
        for model_name in models:
            assert model_name.startswith("gpt")

    def test_returns_anthropic_models(self):
        """Should return Claude models for anthropic provider."""
        fetcher = PricingFetcher()
        models = fetcher._get_models_by_provider("anthropic")

        assert len(models) > 0
        for model_name in models:
            assert model_name.startswith("claude")

    def test_returns_google_models(self):
        """Should return Gemini models for google provider."""
        fetcher = PricingFetcher()
        models = fetcher._get_models_by_provider("google")

        assert len(models) > 0
        for model_name in models:
            assert model_name.startswith("gemini")

    def test_returns_free_models_for_local_providers(self):
        """Should return zero-cost models for local providers."""
        fetcher = PricingFetcher()

        for provider in ["ollama", "lmstudio", "llamacpp"]:
            models = fetcher._get_models_by_provider(provider)
            for pricing in models.values():
                assert pricing["prompt"] == 0.0
                assert pricing["completion"] == 0.0

    def test_returns_empty_for_unknown_provider(self):
        """Should return empty dict for unknown provider."""
        fetcher = PricingFetcher()
        models = fetcher._get_models_by_provider("unknown_provider")

        assert models == {}


class TestPricingFetcherGetProviderFromModel:
    """Tests for get_provider_from_model method."""

    def test_detects_openai_from_gpt(self):
        """Should detect OpenAI from GPT model names."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("gpt-4") == "openai"
        assert fetcher.get_provider_from_model("gpt-3.5-turbo") == "openai"
        assert fetcher.get_provider_from_model("openai/gpt-4") == "openai"

    def test_detects_anthropic_from_claude(self):
        """Should detect Anthropic from Claude model names."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("claude-3-opus") == "anthropic"
        assert (
            fetcher.get_provider_from_model("claude-3-5-sonnet") == "anthropic"
        )

    def test_detects_google_from_gemini(self):
        """Should detect Google from Gemini model names."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("gemini-pro") == "google"
        assert fetcher.get_provider_from_model("gemini-1.5-flash") == "google"

    def test_detects_meta_from_llama(self):
        """Should detect Meta from Llama model names."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("llama-2-70b") == "meta"
        assert fetcher.get_provider_from_model("meta-llama") == "meta"

    def test_detects_mistral(self):
        """Should detect Mistral provider."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("mistral-7b") == "mistral"

    def test_ollama_detection_edge_case(self):
        """Document current behavior: 'llama' check runs before 'ollama'.

        Note: Since 'ollama' contains 'llama' as a substring, any model name
        containing 'ollama' will match 'llama' first and return 'meta'.
        This is the documented current behavior of the implementation.
        """
        fetcher = PricingFetcher()

        # "ollama" contains "llama" so these all match "llama" -> "meta"
        assert fetcher.get_provider_from_model("ollama:llama2") == "meta"
        assert fetcher.get_provider_from_model("my-ollama-server") == "meta"

        # The ollama branch would only be reachable with names that don't contain "llama"
        # but that's not possible if the name contains "ollama"

    def test_returns_unknown_for_unrecognized(self):
        """Should return 'unknown' for unrecognized models."""
        fetcher = PricingFetcher()

        assert fetcher.get_provider_from_model("random-model") == "unknown"


class TestPricingFetcherGetAllPricing:
    """Tests for get_all_pricing method."""

    @pytest.mark.asyncio
    async def test_returns_copy_of_static_pricing(self):
        """Should return a copy of static pricing."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.get_all_pricing()

            assert result == fetcher.static_pricing
            # Verify it's a copy
            result["new_key"] = "test"
            assert "new_key" not in fetcher.static_pricing


class TestPricingFetcherFetchMethods:
    """Tests for individual fetch methods."""

    @pytest.mark.asyncio
    async def test_fetch_openai_pricing_returns_none(self):
        """OpenAI pricing fetch returns None (no public API)."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.fetch_openai_pricing()
            assert result is None

    @pytest.mark.asyncio
    async def test_fetch_anthropic_pricing_returns_none(self):
        """Anthropic pricing fetch returns None (no public API)."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.fetch_anthropic_pricing()
            assert result is None

    @pytest.mark.asyncio
    async def test_fetch_google_pricing_returns_none(self):
        """Google pricing fetch returns None (no public API)."""
        async with PricingFetcher() as fetcher:
            result = await fetcher.fetch_google_pricing()
            assert result is None
