"""Tests for the HonestSavings calculator (F-019).

Validates:
- Savings never exceeds 100%
- No-baseline and with-baseline modes
- Local requests always cost $0
- Cloud requests use real pricing
- Monthly projection confidence tiers
- Validation catches dishonest claims
- Per-request savings breakdown
- Methodology string is always present
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from llmhosts.onboarding.manager import SpendingBaseline
from llmhosts.router.cost import CostTracker
from llmhosts.router.models import RoutingDecision
from llmhosts.savings.calculator import HonestSavings

if TYPE_CHECKING:
    from pathlib import Path


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


def _make_decision(
    *,
    backend_type: str = "ollama",
    model: str = "llama3.2:8b",
    tier: str = "rule",
    reasoning: str = "Local model available",
) -> RoutingDecision:
    """Build a minimal RoutingDecision for testing."""
    return RoutingDecision(
        backend_type=backend_type,
        backend_url="http://localhost:11434" if backend_type == "ollama" else "https://api.openai.com",
        model=model,
        tier=tier,
        confidence=0.95,
        reasoning=reasoning,
    )


@pytest.fixture()
async def tracker(tmp_path: Path) -> CostTracker:
    """A fresh CostTracker backed by a temporary SQLite database."""
    db_path = tmp_path / "cost.db"
    ct = CostTracker(db_path=db_path)
    await ct.initialize()
    yield ct
    await ct.close()


@pytest.fixture()
async def savings(tracker: CostTracker) -> HonestSavings:
    """HonestSavings calculator without a baseline."""
    return HonestSavings(cost_tracker=tracker, baseline=None)


@pytest.fixture()
async def savings_with_baseline(tracker: CostTracker) -> HonestSavings:
    """HonestSavings calculator with a $150/mo baseline."""
    baseline = SpendingBaseline(
        monthly_range="$100-200",
        monthly_estimate=150.0,
        providers_used=["openai", "anthropic"],
    )
    return HonestSavings(cost_tracker=tracker, baseline=baseline)


# ---------------------------------------------------------------------------
# Helpers to record requests
# ---------------------------------------------------------------------------


async def _record_local(tracker: CostTracker, *, model: str = "gpt-4o-mini", n: int = 1) -> list[str]:
    """Record *n* local (Ollama) requests.  Actual cost = $0, cloud equiv > 0."""
    decision = _make_decision(backend_type="ollama", model="llama3.2:8b")
    ids: list[str] = []
    for _i in range(n):
        rid = await tracker.record_request(
            decision=decision,
            model_requested=model,
            input_tokens=500,
            output_tokens=200,
            actual_cost=0.0,
            latency_ms=45.0,
            cached=False,
        )
        ids.append(rid)
    return ids


async def _record_cloud(tracker: CostTracker, *, model: str = "gpt-4o-mini", n: int = 1) -> list[str]:
    """Record *n* cloud requests.  Actual cost = cloud pricing."""
    from llmhosts.router.cost import cloud_equivalent_cost

    decision = _make_decision(backend_type="openai", model=model)
    equiv = cloud_equivalent_cost(model, 500, 200)
    ids: list[str] = []
    for _i in range(n):
        rid = await tracker.record_request(
            decision=decision,
            model_requested=model,
            input_tokens=500,
            output_tokens=200,
            actual_cost=equiv,
            latency_ms=320.0,
            cached=False,
        )
        ids.append(rid)
    return ids


async def _record_cached(tracker: CostTracker, *, model: str = "gpt-4o-mini", n: int = 1) -> list[str]:
    """Record *n* cache-hit requests.  Actual cost = $0, cloud equiv > 0."""
    decision = _make_decision(backend_type="ollama", model="llama3.2:8b", reasoning="Cache hit")
    ids: list[str] = []
    for _i in range(n):
        rid = await tracker.record_request(
            decision=decision,
            model_requested=model,
            input_tokens=500,
            output_tokens=200,
            actual_cost=0.0,
            latency_ms=2.0,
            cached=True,
        )
        ids.append(rid)
    return ids


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
async def test_savings_never_exceeds_100_percent(tracker: CostTracker, savings: HonestSavings) -> None:
    """Even with 100% local routing, savings_percent must be <= 100."""
    await _record_local(tracker, n=50)

    report = await savings.calculate("month")

    assert report.savings_percent <= 100.0
    assert report.savings_percent >= 0.0


@pytest.mark.unit
async def test_savings_with_no_baseline(tracker: CostTracker, savings: HonestSavings) -> None:
    """Without a baseline, baseline_monthly and vs_baseline_savings are None."""
    await _record_local(tracker, n=10)
    await _record_cloud(tracker, n=5)

    report = await savings.calculate("month")

    assert report.baseline_monthly is None
    assert report.vs_baseline_savings is None
    assert report.savings_amount >= 0.0
    assert report.cloud_equivalent > 0.0
    assert report.total_requests == 15


@pytest.mark.unit
async def test_savings_with_baseline(tracker: CostTracker, savings_with_baseline: HonestSavings) -> None:
    """With a $150/mo baseline, vs_baseline_savings should be populated."""
    await _record_local(tracker, n=20)
    await _record_cloud(tracker, n=3)

    report = await savings_with_baseline.calculate("month")

    assert report.baseline_monthly == 150.0
    assert report.vs_baseline_savings is not None
    assert report.vs_baseline_savings >= 0.0
    # vs_baseline should be baseline - actual_cost (clamped to >= 0), rounded to 2dp
    expected = round(max(150.0 - report.actual_cost, 0.0), 2)
    assert report.vs_baseline_savings == expected


@pytest.mark.unit
async def test_local_requests_cost_zero(tracker: CostTracker, savings: HonestSavings) -> None:
    """Local (Ollama) requests must have actual_cost = $0."""
    await _record_local(tracker, n=10)

    report = await savings.calculate("month")

    assert report.actual_cost == 0.0
    assert report.local_requests == 10
    assert report.cloud_requests == 0
    # All savings come from cloud equivalent
    assert report.savings_amount == report.cloud_equivalent


@pytest.mark.unit
async def test_cloud_requests_use_real_pricing(tracker: CostTracker, savings: HonestSavings) -> None:
    """Cloud requests should have actual_cost > 0 based on real pricing."""
    from llmhosts.router.cost import cloud_equivalent_cost

    await _record_cloud(tracker, model="gpt-4o-mini", n=5)

    report = await savings.calculate("month")

    # Cloud-only: actual_cost should equal cloud_equivalent (no savings)
    assert report.actual_cost > 0.0
    assert report.cloud_equivalent > 0.0
    # With all-cloud routing, savings should be $0 (or very close due to rounding)
    assert abs(report.savings_amount) < 1e-6
    assert report.savings_percent < 1.0  # essentially 0%

    # Verify the pricing is realistic (gpt-4o-mini: $0.15/M input, $0.60/M output)
    expected_per_request = cloud_equivalent_cost("gpt-4o-mini", 500, 200)
    assert expected_per_request > 0.0
    assert abs(report.actual_cost - expected_per_request * 5) < 1e-6


@pytest.mark.unit
async def test_monthly_projection_low_confidence(tracker: CostTracker, savings: HonestSavings) -> None:
    """With minimal data (< 7 requests), confidence should be 'low'."""
    await _record_local(tracker, n=3)

    projection = await savings.monthly_projection()

    assert projection.confidence == "low"
    assert projection.data_points == 3
    assert projection.projected_requests > 0
    assert projection.projected_savings >= 0.0
    assert projection.projected_savings_percent <= 100.0


@pytest.mark.unit
async def test_monthly_projection_high_confidence(tracker: CostTracker, savings: HonestSavings) -> None:
    """With 30+ data points, confidence should be 'high'."""
    await _record_local(tracker, n=20)
    await _record_cloud(tracker, n=15)

    projection = await savings.monthly_projection()

    assert projection.confidence == "high"
    assert projection.data_points == 35
    assert projection.projected_requests >= 35
    assert projection.projected_savings >= 0.0
    assert projection.projected_savings_percent <= 100.0


@pytest.mark.unit
async def test_validation_catches_over_100_percent(savings: HonestSavings) -> None:
    """validate_claim must reject savings > 100%."""
    result = savings.validate_claim(claimed_savings=150.0, period="month")

    assert result.valid is False
    assert len(result.issues) > 0
    assert any("100%" in issue for issue in result.issues)
    assert result.corrected_savings == 100.0


@pytest.mark.unit
async def test_validation_accepts_legitimate_claim(savings: HonestSavings) -> None:
    """validate_claim should accept a reasonable savings percentage."""
    result = savings.validate_claim(claimed_savings=72.5, period="month")

    assert result.valid is True
    assert len(result.issues) == 0
    assert result.corrected_savings is None


@pytest.mark.unit
async def test_per_request_savings(tracker: CostTracker, savings: HonestSavings) -> None:
    """per_request_savings should return correct breakdown for a single request."""
    ids = await _record_local(tracker, n=1)
    rid = ids[0]

    req_savings = await savings.per_request_savings(rid)

    assert req_savings.request_id == rid
    assert req_savings.actual_cost == 0.0
    assert req_savings.cloud_equivalent_cost > 0.0
    assert req_savings.saved > 0.0
    assert req_savings.routed_to == "local"
    assert req_savings.model_used == "llama3.2:8b"
    assert req_savings.cumulative_today >= 0.0


@pytest.mark.unit
async def test_per_request_savings_cloud(tracker: CostTracker, savings: HonestSavings) -> None:
    """per_request_savings for a cloud request should have actual_cost > 0."""
    ids = await _record_cloud(tracker, model="gpt-4o-mini", n=1)
    rid = ids[0]

    req_savings = await savings.per_request_savings(rid)

    assert req_savings.request_id == rid
    assert req_savings.actual_cost > 0.0
    assert req_savings.routed_to == "cloud"
    assert req_savings.saved == 0.0  # cloud cost == cloud equiv, no savings


@pytest.mark.unit
async def test_per_request_savings_cached(tracker: CostTracker, savings: HonestSavings) -> None:
    """per_request_savings for a cached request should show routed_to='cache'."""
    ids = await _record_cached(tracker, n=1)
    rid = ids[0]

    req_savings = await savings.per_request_savings(rid)

    assert req_savings.request_id == rid
    assert req_savings.routed_to == "cache"
    assert req_savings.actual_cost == 0.0
    assert req_savings.saved > 0.0


@pytest.mark.unit
async def test_methodology_string_present(tracker: CostTracker, savings: HonestSavings) -> None:
    """Every SavingsReport must include the methodology explanation."""
    await _record_local(tracker, n=5)

    report = await savings.calculate("month")

    assert report.methodology
    assert len(report.methodology) > 20
    assert "cloud equivalent" in report.methodology.lower()
    assert "local" in report.methodology.lower()


@pytest.mark.unit
async def test_honesty_score_is_populated(tracker: CostTracker, savings: HonestSavings) -> None:
    """honesty_score should be > 0 when we have real data."""
    await _record_local(tracker, n=10)

    report = await savings.calculate("month")

    assert report.honesty_score > 0.0
    assert report.honesty_score <= 1.0


@pytest.mark.unit
async def test_honesty_score_with_baseline(tracker: CostTracker, savings_with_baseline: HonestSavings) -> None:
    """honesty_score should be higher when a baseline is available."""
    await _record_local(tracker, n=10)

    report_with = await savings_with_baseline.calculate("month")

    # Change to no-baseline version
    no_baseline = HonestSavings(cost_tracker=tracker, baseline=None)
    report_without = await no_baseline.calculate("month")

    assert report_with.honesty_score >= report_without.honesty_score


@pytest.mark.unit
async def test_empty_database_returns_zero_report(savings: HonestSavings) -> None:
    """With no requests at all, everything should be zero / safe defaults."""
    report = await savings.calculate("month")

    assert report.total_requests == 0
    assert report.savings_amount == 0.0
    assert report.savings_percent == 0.0
    assert report.honesty_score == 0.0
    assert report.methodology  # methodology is always present


@pytest.mark.unit
async def test_per_request_savings_unknown_id(savings: HonestSavings) -> None:
    """per_request_savings for an unknown ID should return safe defaults."""
    result = await savings.per_request_savings("nonexistent-id")

    assert result.request_id == "nonexistent-id"
    assert result.actual_cost == 0.0
    assert result.saved == 0.0


@pytest.mark.unit
async def test_validation_rejects_negative_savings(savings: HonestSavings) -> None:
    """validate_claim should reject negative savings percentages."""
    result = savings.validate_claim(claimed_savings=-10.0, period="month")

    assert result.valid is False
    assert any("negative" in issue.lower() for issue in result.issues)
    assert result.corrected_savings == 0.0


@pytest.mark.unit
async def test_mixed_local_cloud_savings(tracker: CostTracker, savings: HonestSavings) -> None:
    """A mix of local and cloud requests should show partial savings."""
    await _record_local(tracker, n=8)
    await _record_cloud(tracker, n=2)

    report = await savings.calculate("month")

    assert report.total_requests == 10
    assert report.local_requests == 8
    assert report.cloud_requests == 2
    assert report.savings_amount > 0.0
    assert report.savings_percent > 0.0
    assert report.savings_percent < 100.0  # not all local, so < 100%
    assert report.actual_cost > 0.0  # some cloud requests
