"""High-value tests for metrics/pricing/cost_calculator.py.

Covers sync cost calculation, batch cost edge cases, model name resolution,
zero/negative tokens, and research cost summary aggregation.
"""

import pytest

from local_deep_research.metrics.pricing.cost_calculator import CostCalculator


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

    def test_init_creates_cache(self):
        """CostCalculator creates a PricingCache on init."""
        calc = CostCalculator()
        assert calc.cache is not None

    def test_init_pricing_fetcher_is_none(self):
        """Pricing fetcher is None until async context is entered."""
        calc = CostCalculator()
        assert calc.pricing_fetcher is None


class TestCalculateCostSync:
    """Tests for synchronous cost calculation."""

    def test_known_model_returns_cost(self):
        """Known model (gpt-4) returns correct cost."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync("gpt-4", 1000, 500)
        assert result["prompt_cost"] > 0
        assert result["completion_cost"] > 0
        assert (
            result["total_cost"]
            == result["prompt_cost"] + result["completion_cost"]
        )
        assert result["pricing_used"] is not None

    def test_unknown_model_returns_zero_cost(self):
        """Unknown model returns zero cost with error message."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync(
            "totally-unknown-model-xyz", 1000, 500
        )
        assert result["prompt_cost"] == 0.0
        assert result["completion_cost"] == 0.0
        assert result["total_cost"] == 0.0
        assert "error" in result

    def test_zero_tokens_returns_zero_cost(self):
        """Zero tokens should return zero cost."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync("gpt-4", 0, 0)
        assert result["total_cost"] == 0.0

    def test_large_token_count(self):
        """Large token counts produce proportional costs."""
        calc = CostCalculator()
        small = calc.calculate_cost_sync("gpt-4", 1000, 1000)
        large = calc.calculate_cost_sync("gpt-4", 10000, 10000)
        if small["pricing_used"]:
            assert large["total_cost"] == pytest.approx(
                small["total_cost"] * 10, rel=1e-4
            )

    def test_cost_precision_is_six_decimals(self):
        """Costs are rounded to 6 decimal places."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync("gpt-4", 1, 1)
        if result["pricing_used"]:
            # Check the string representation has at most 6 decimal places
            prompt_str = f"{result['prompt_cost']:.10f}"
            parts = prompt_str.split(".")
            # The value after rounding should have trailing zeros after 6th decimal
            assert len(parts[1].rstrip("0")) <= 6

    def test_model_with_provider_prefix(self):
        """Model name with provider prefix like 'openai/gpt-4' strips prefix."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync("openai/gpt-4", 1000, 500)
        # Should find pricing via the stripped model name
        assert result["pricing_used"] is not None

    def test_local_model_returns_zero(self):
        """Local models (ollama) should return zero cost."""
        calc = CostCalculator()
        result = calc.calculate_cost_sync("ollama", 5000, 3000)
        assert result["total_cost"] == 0.0
        assert result["pricing_used"] is not None

    def test_cached_pricing_used_first(self):
        """Cached pricing is used before static fallback."""
        calc = CostCalculator()
        calc.cache.set_model_pricing(
            "custom-model", {"prompt": 0.01, "completion": 0.02}
        )
        result = calc.calculate_cost_sync("custom-model", 1000, 1000)
        assert result["prompt_cost"] == pytest.approx(0.01, rel=1e-4)
        assert result["completion_cost"] == pytest.approx(0.02, rel=1e-4)


class TestCalculateCostAsync:
    """Tests for async cost calculation."""

    @pytest.mark.asyncio
    async def test_calculate_cost_no_pricing_returns_zero(self):
        """When no pricing found, returns zero with error message."""
        calc = CostCalculator()
        result = await calc.calculate_cost("nonexistent-model", 1000, 500)
        assert result["total_cost"] == 0.0
        assert "error" in result

    @pytest.mark.asyncio
    async def test_calculate_cost_with_cached_pricing(self):
        """Uses cached pricing when available."""
        calc = CostCalculator()
        calc.cache.set(
            "model:test-model", {"prompt": 0.005, "completion": 0.01}
        )
        result = await calc.calculate_cost("test-model", 2000, 1000)
        assert result["prompt_cost"] == pytest.approx(0.01, rel=1e-4)
        assert result["completion_cost"] == pytest.approx(0.01, rel=1e-4)

    @pytest.mark.asyncio
    async def test_calculate_cost_with_provider(self):
        """Provider-prefixed cache key is used."""
        calc = CostCalculator()
        calc.cache.set(
            "model:openai:gpt-4", {"prompt": 0.03, "completion": 0.06}
        )
        result = await calc.calculate_cost(
            "gpt-4", 1000, 1000, provider="openai"
        )
        assert result["prompt_cost"] == pytest.approx(0.03, rel=1e-4)


class TestCalculateBatchCosts:
    """Tests for batch cost calculation."""

    @pytest.mark.asyncio
    async def test_empty_batch_returns_empty(self):
        """Empty usage records returns empty list."""
        calc = CostCalculator()
        result = await calc.calculate_batch_costs([])
        assert result == []

    @pytest.mark.asyncio
    async def test_batch_with_error_records_still_returns_all(self):
        """Records that fail still get zero-cost entries."""
        calc = CostCalculator()
        records = [
            {
                "model_name": "nonexistent",
                "prompt_tokens": 100,
                "completion_tokens": 50,
            },
        ]
        result = await calc.calculate_batch_costs(records)
        assert len(result) == 1
        assert result[0]["total_cost"] == 0.0


class TestGetResearchCostSummary:
    """Tests for research cost summary aggregation."""

    @pytest.mark.asyncio
    async def test_empty_records_returns_zero_summary(self):
        """Empty records return zero-value summary."""
        calc = CostCalculator()
        result = await calc.get_research_cost_summary([])
        assert result["total_cost"] == 0.0
        assert result["total_calls"] == 0
        assert result["avg_cost_per_call"] == 0.0

    @pytest.mark.asyncio
    async def test_summary_aggregates_multiple_models(self):
        """Summary aggregates costs across different models."""
        calc = CostCalculator()
        calc.cache.set("model:gpt-4", {"prompt": 0.03, "completion": 0.06})
        calc.cache.set(
            "model:gpt-3.5-turbo", {"prompt": 0.001, "completion": 0.002}
        )

        records = [
            {
                "model_name": "gpt-4",
                "prompt_tokens": 1000,
                "completion_tokens": 500,
            },
            {
                "model_name": "gpt-3.5-turbo",
                "prompt_tokens": 2000,
                "completion_tokens": 1000,
            },
        ]
        result = await calc.get_research_cost_summary(records)

        assert result["total_calls"] == 2
        assert result["total_tokens"] == 4500
        assert "model_breakdown" in result
        assert "gpt-4" in result["model_breakdown"]
        assert "gpt-3.5-turbo" in result["model_breakdown"]
