"""Tests for metrics cost_calculator module."""

import pytest

from local_deep_research.metrics.pricing.cost_calculator import CostCalculator


class TestCostCalculatorInit:
    """Tests for CostCalculator initialization."""

    def test_initializes_cache(self):
        """Should initialize with a PricingCache."""
        calculator = CostCalculator()
        assert calculator.cache is not None

    def test_initializes_fetcher_as_none(self):
        """Should initialize pricing_fetcher as None."""
        calculator = CostCalculator()
        assert calculator.pricing_fetcher is None

    def test_accepts_cache_dir_parameter(self):
        """Should accept cache_dir parameter."""
        calculator = CostCalculator(cache_dir="/some/path")
        # Should not raise and cache should still work
        assert calculator.cache is not None


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

    @pytest.mark.asyncio
    async def test_creates_pricing_fetcher_on_enter(self):
        """Should create and enter pricing fetcher."""
        async with CostCalculator() as calculator:
            assert calculator.pricing_fetcher is not None

    @pytest.mark.asyncio
    async def test_closes_pricing_fetcher_on_exit(self):
        """Should close pricing fetcher on exit."""
        calculator = CostCalculator()
        async with calculator:
            _fetcher = calculator.pricing_fetcher  # noqa: F841

        # Fetcher should have been exited


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

    @pytest.mark.asyncio
    async def test_returns_cached_pricing(self):
        """Should return pricing from cache if available."""
        calculator = CostCalculator()
        cached_pricing = {"prompt": 0.01, "completion": 0.02}
        calculator.cache.set("model:gpt-4", cached_pricing)

        async with calculator:
            result = await calculator.get_model_pricing("gpt-4")

        assert result == cached_pricing

    @pytest.mark.asyncio
    async def test_fetches_pricing_when_not_cached(self):
        """Should fetch pricing when not in cache."""
        async with CostCalculator() as calculator:
            result = await calculator.get_model_pricing("gpt-4")

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

    @pytest.mark.asyncio
    async def test_caches_fetched_pricing(self):
        """Should cache pricing after fetching."""
        async with CostCalculator() as calculator:
            await calculator.get_model_pricing("gpt-4")

            # Should now be in cache
            cached = calculator.cache.get("model:gpt-4")
            assert cached is not None

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

            assert result is None


class TestCostCalculatorCalculateCost:
    """Tests for calculate_cost method."""

    @pytest.mark.asyncio
    async def test_calculates_cost_correctly(self):
        """Should calculate cost correctly based on pricing."""
        async with CostCalculator() as calculator:
            # gpt-4 has pricing: prompt=0.03, completion=0.06 per 1K tokens
            result = await calculator.calculate_cost(
                "gpt-4",
                prompt_tokens=1000,
                completion_tokens=500,
            )

            # 1000/1000 * 0.03 = 0.03 for prompt
            # 500/1000 * 0.06 = 0.03 for completion
            # Total = 0.06
            assert result["prompt_cost"] == pytest.approx(0.03, rel=1e-4)
            assert result["completion_cost"] == pytest.approx(0.03, rel=1e-4)
            assert result["total_cost"] == pytest.approx(0.06, rel=1e-4)

    @pytest.mark.asyncio
    async def test_returns_zero_cost_for_unknown_model(self):
        """Should return zero cost when no pricing available."""
        async with CostCalculator() as calculator:
            result = await calculator.calculate_cost(
                "unknown-model",
                prompt_tokens=1000,
                completion_tokens=500,
            )

            assert result["prompt_cost"] == 0.0
            assert result["completion_cost"] == 0.0
            assert result["total_cost"] == 0.0
            assert "error" in result

    @pytest.mark.asyncio
    async def test_returns_pricing_used(self):
        """Should include pricing_used in result."""
        async with CostCalculator() as calculator:
            result = await calculator.calculate_cost(
                "gpt-4",
                prompt_tokens=100,
                completion_tokens=50,
            )

            assert "pricing_used" in result
            assert result["pricing_used"] is not None

    @pytest.mark.asyncio
    async def test_handles_zero_tokens(self):
        """Should handle zero tokens correctly."""
        async with CostCalculator() as calculator:
            result = await calculator.calculate_cost(
                "gpt-4",
                prompt_tokens=0,
                completion_tokens=0,
            )

            assert result["prompt_cost"] == 0.0
            assert result["completion_cost"] == 0.0
            assert result["total_cost"] == 0.0

    @pytest.mark.asyncio
    async def test_uses_provider_for_pricing(self):
        """Should use provider to look up pricing."""
        async with CostCalculator() as calculator:
            # Local providers should have zero cost
            result = await calculator.calculate_cost(
                "any-model",
                prompt_tokens=1000,
                completion_tokens=500,
                provider="ollama",
            )

            assert result["total_cost"] == 0.0


class TestCostCalculatorCalculateBatchCosts:
    """Tests for calculate_batch_costs method."""

    @pytest.mark.asyncio
    async def test_processes_all_records(self):
        """Should process all records in batch."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 100,
                "completion_tokens": 50,
            },
            {
                "model_name": "gpt-3.5-turbo",
                "prompt_tokens": 200,
                "completion_tokens": 100,
            },
        ]

        async with CostCalculator() as calculator:
            results = await calculator.calculate_batch_costs(records)

            assert len(results) == 2

    @pytest.mark.asyncio
    async def test_preserves_original_record_data(self):
        """Should preserve original record data in results."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 100,
                "completion_tokens": 50,
                "research_id": "test-123",
            },
        ]

        async with CostCalculator() as calculator:
            results = await calculator.calculate_batch_costs(records)

            assert results[0]["research_id"] == "test-123"
            assert results[0]["model_name"] == "gpt-4"

    @pytest.mark.asyncio
    async def test_handles_errors_gracefully(self):
        """Should handle errors in individual records."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 100,
                "completion_tokens": 50,
            },
            {
                "model_name": None,
                "prompt_tokens": 100,
                "completion_tokens": 50,
            },  # Invalid
        ]

        async with CostCalculator() as calculator:
            results = await calculator.calculate_batch_costs(records)

            assert len(results) == 2
            # Second record should have zero cost due to error
            assert "error" in results[1] or results[1]["total_cost"] == 0.0


class TestCostCalculatorCalculateCostSync:
    """Tests for calculate_cost_sync method."""

    def test_uses_cached_pricing(self):
        """Should use cached pricing."""
        calculator = CostCalculator()
        calculator.cache.set_model_pricing(
            "gpt-4", {"prompt": 0.03, "completion": 0.06}
        )

        result = calculator.calculate_cost_sync("gpt-4", 1000, 500)

        assert result["prompt_cost"] == pytest.approx(0.03, rel=1e-4)
        assert result["completion_cost"] == pytest.approx(0.03, rel=1e-4)

    def test_uses_static_fallback(self):
        """Should use static pricing as fallback."""
        calculator = CostCalculator()

        result = calculator.calculate_cost_sync("gpt-4", 1000, 500)

        assert result["total_cost"] > 0
        assert result["pricing_used"] is not None

    def test_returns_zero_for_unknown_model(self):
        """Should return zero cost for unknown model."""
        calculator = CostCalculator()

        result = calculator.calculate_cost_sync("unknown-model-xyz", 1000, 500)

        assert result["total_cost"] == 0.0
        assert "error" in result

    def test_handles_model_with_provider_prefix(self):
        """Should handle model names with provider prefix."""
        calculator = CostCalculator()

        result = calculator.calculate_cost_sync("openai/gpt-4o-mini", 1000, 500)

        # Should find pricing for gpt-4o-mini
        assert result["pricing_used"] is not None


class TestCostCalculatorGetResearchCostSummary:
    """Tests for get_research_cost_summary method."""

    @pytest.mark.asyncio
    async def test_calculates_totals(self):
        """Should calculate correct totals."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
            {
                "model_name": "gpt-4",
                "prompt_tokens": 2000,
                "completion_tokens": 1000,
            },
        ]

        async with CostCalculator() as calculator:
            summary = await calculator.get_research_cost_summary(records)

            assert summary["total_tokens"] == 4500
            assert summary["prompt_tokens"] == 3000
            assert summary["completion_tokens"] == 1500
            assert summary["total_calls"] == 2

    @pytest.mark.asyncio
    async def test_calculates_model_breakdown(self):
        """Should provide breakdown by model."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
            {
                "model_name": "gpt-3.5-turbo",
                "prompt_tokens": 2000,
                "completion_tokens": 1000,
            },
        ]

        async with CostCalculator() as calculator:
            summary = await calculator.get_research_cost_summary(records)

            assert "model_breakdown" in summary
            assert "gpt-4" in summary["model_breakdown"]
            assert "gpt-3.5-turbo" in summary["model_breakdown"]

    @pytest.mark.asyncio
    async def test_calculates_average_cost(self):
        """Should calculate average cost per call."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
        ]

        async with CostCalculator() as calculator:
            summary = await calculator.get_research_cost_summary(records)

            assert "avg_cost_per_call" in summary
            if summary["total_cost"] > 0:
                assert summary["avg_cost_per_call"] == summary["total_cost"] / 2

    @pytest.mark.asyncio
    async def test_handles_empty_records(self):
        """Should handle empty records list."""
        async with CostCalculator() as calculator:
            summary = await calculator.get_research_cost_summary([])

            assert summary["total_cost"] == 0.0
            assert summary["total_calls"] == 0
            assert summary["avg_cost_per_call"] == 0.0

    @pytest.mark.asyncio
    async def test_calculates_cost_per_token(self):
        """Should calculate cost per token."""
        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
        ]

        async with CostCalculator() as calculator:
            summary = await calculator.get_research_cost_summary(records)

            assert "cost_per_token" in summary
            if summary["total_tokens"] > 0 and summary["total_cost"] > 0:
                expected = summary["total_cost"] / summary["total_tokens"]
                assert summary["cost_per_token"] == pytest.approx(
                    expected, rel=1e-6
                )
