"""Tests for cost envelope evaluation."""
from __future__ import annotations

import json

from abi_evals.cost_envelope_eval import (
    CostEnvelopeSpec,
    SuiteCostReport,
    validate_suite_cost_envelope,
)


def _make_suite_results(case_tokens: dict[str, int], suite_id: str = "test-suite") -> dict:
    """Build a minimal eval-result/1.0 with explicit token counts."""
    results = []
    for case_id, tokens in sorted(case_tokens.items()):
        results.append({
            "case_id": case_id,
            "status": "pass",
            "actual": {"mock": True},
            "message": "OK",
            "diff": "",
            "tokens_used": tokens,
        })
    return {
        "schema_version": "eval-result/1.0",
        "suite_id": suite_id,
        "run_id": "test0001",
        "results": results,
        "summary": {
            "total": len(results),
            "passed": len(results),
            "failed": 0,
            "errors": 0,
            "skipped": 0,
        },
    }


class TestValidateSuiteCostEnvelope:
    def test_within_budget_passes(self):
        """300 tokens against a 500 max -> PASS."""
        suite = _make_suite_results({"a": 100, "b": 100, "c": 100})
        envelope = CostEnvelopeSpec(max_total_tokens=500)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 100, "b": 100, "c": 100})
        assert report.status == "PASS"
        assert report.total_tokens == 300

    def test_over_budget_fails(self):
        """700 tokens against a 500 max -> FAIL."""
        suite = _make_suite_results({"a": 300, "b": 400})
        envelope = CostEnvelopeSpec(max_total_tokens=500)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 300, "b": 400})
        assert report.status == "FAIL"
        assert report.total_tokens == 700
        assert len(report.violations) >= 1

    def test_warn_at_threshold(self):
        """420 tokens against a 500 max, 0.80 warn -> WARN (84% > 80%)."""
        suite = _make_suite_results({"a": 200, "b": 220})
        envelope = CostEnvelopeSpec(max_total_tokens=500, warn_threshold_pct=0.80)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 200, "b": 220})
        assert report.status == "WARN"
        assert report.total_tokens == 420

    def test_per_case_limit_exceeded(self):
        """One case at 200 tokens exceeds per-case limit of 100 -> FAIL."""
        suite = _make_suite_results({"a": 50, "b": 200})
        envelope = CostEnvelopeSpec(max_total_tokens=1000, max_tokens_per_case=100)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 50, "b": 200})
        assert report.status == "FAIL"
        over = [c for c in report.case_costs if c.over_case_limit]
        assert len(over) == 1
        assert over[0].case_id == "b"

    def test_per_case_within_limit(self):
        """All cases under per-case limit -> PASS."""
        suite = _make_suite_results({"a": 50, "b": 80})
        envelope = CostEnvelopeSpec(max_total_tokens=1000, max_tokens_per_case=100)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 50, "b": 80})
        assert report.status == "PASS"
        over = [c for c in report.case_costs if c.over_case_limit]
        assert len(over) == 0

    def test_deterministic_json(self):
        """Same input produces identical JSON output; schema_version present."""
        suite = _make_suite_results({"x": 10, "y": 20})
        envelope = CostEnvelopeSpec(max_total_tokens=100)
        r1 = validate_suite_cost_envelope(suite, envelope, case_tokens={"x": 10, "y": 20})
        r2 = validate_suite_cost_envelope(suite, envelope, case_tokens={"x": 10, "y": 20})
        assert r1.to_json() == r2.to_json()
        parsed = json.loads(r1.to_json())
        assert parsed["schema_version"] == "suite-cost-report/1.0"

    def test_report_includes_utilization(self):
        """250 tokens / 500 max = 0.5 utilization_pct."""
        suite = _make_suite_results({"a": 100, "b": 150})
        envelope = CostEnvelopeSpec(max_total_tokens=500)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 100, "b": 150})
        d = report.to_dict()
        assert d["utilization_pct"] == 0.5

    def test_empty_suite(self):
        """Empty suite with 0 tokens -> PASS."""
        suite = _make_suite_results({})
        envelope = CostEnvelopeSpec(max_total_tokens=500)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={})
        assert report.status == "PASS"
        assert report.total_tokens == 0

    def test_zero_max_tokens(self):
        """1 token with 0 max -> FAIL."""
        suite = _make_suite_results({"a": 1})
        envelope = CostEnvelopeSpec(max_total_tokens=0)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 1})
        assert report.status == "FAIL"

    def test_exactly_at_budget(self):
        """500 tokens / 500 max = 100% utilization -> WARN (100% >= 80% threshold)."""
        suite = _make_suite_results({"a": 250, "b": 250})
        envelope = CostEnvelopeSpec(max_total_tokens=500, warn_threshold_pct=0.80)
        report = validate_suite_cost_envelope(suite, envelope, case_tokens={"a": 250, "b": 250})
        assert report.status == "WARN"
        assert report.total_tokens == 500


class TestSuiteCostReportSerialization:
    def test_to_dict_keys(self):
        """All expected keys are present in to_dict output."""
        report = SuiteCostReport(suite_id="s1", total_tokens=100, max_total_tokens=200)
        d = report.to_dict()
        expected_keys = {
            "schema_version", "suite_id", "total_tokens", "max_total_tokens",
            "utilization_pct", "case_costs", "status", "violations",
        }
        assert set(d.keys()) == expected_keys

    def test_to_json_valid(self):
        """to_json produces valid parseable JSON."""
        report = SuiteCostReport(suite_id="s2", total_tokens=50, max_total_tokens=100)
        parsed = json.loads(report.to_json())
        assert isinstance(parsed, dict)
        assert parsed["suite_id"] == "s2"
