"""Tests for review aggregator."""

from __future__ import annotations

from bandito.review.aggregator import aggregate, _compute_distribution


class TestComputeDistribution:
    def test_empty(self):
        d = _compute_distribution([])
        assert d.count == 0
        assert d.total == 0.0

    def test_single_value(self):
        d = _compute_distribution([5.0])
        assert d.min == 5.0
        assert d.max == 5.0
        assert d.mean == 5.0
        assert d.median == 5.0
        assert d.count == 1
        assert d.total == 5.0

    def test_multiple_values(self):
        d = _compute_distribution([1.0, 2.0, 3.0, 4.0, 5.0])
        assert d.min == 1.0
        assert d.max == 5.0
        assert d.mean == 3.0
        assert d.median == 3.0
        assert d.count == 5
        assert d.total == 15.0

    def test_even_count_median(self):
        d = _compute_distribution([1.0, 2.0, 3.0, 4.0])
        assert d.median == 2.5

    def test_percentiles(self):
        values = [float(i) for i in range(100)]
        d = _compute_distribution(values)
        assert d.p95 == 95.0
        assert d.p99 == 99.0


class TestAggregate:
    def test_empty_traces(self):
        stats = aggregate([])
        assert stats.trace_count == 0
        assert stats.span_count == 0

    def test_basic_aggregation(self, make_trace):
        traces = [make_trace(cost=0.01, latency=1.0) for _ in range(5)]
        stats = aggregate(traces)
        assert stats.trace_count == 5
        assert stats.span_count == 5
        assert stats.cost.count == 5
        assert stats.error_count == 0

    def test_cost_distribution(self, make_trace):
        traces = [
            make_trace(cost=0.001),
            make_trace(cost=0.010),
            make_trace(cost=0.100),
        ]
        stats = aggregate(traces)
        assert stats.cost.min == 0.001
        assert stats.cost.max == 0.100
        assert abs(stats.cost.total - 0.111) < 0.0001

    def test_model_breakdown(self, make_trace):
        traces = [
            make_trace(model="gpt-4o", provider="openai", cost=0.01),
            make_trace(model="gpt-4o", provider="openai", cost=0.02),
            make_trace(model="claude-3-opus", provider="anthropic", cost=0.05),
        ]
        stats = aggregate(traces)
        assert len(stats.models) == 2
        models_by_name = {m.model: m for m in stats.models}
        assert models_by_name["gpt-4o"].count == 2
        assert models_by_name["claude-3-opus"].count == 1

    def test_span_kind_breakdown(self, make_trace):
        traces = [
            make_trace(extra_spans=[
                {"span_id": "sp-tool-1", "name": "search", "kind": "tool",
                 "started_at": "2026-03-19T12:00:00Z", "ended_at": "2026-03-19T12:00:01Z",
                 "status": "ok"},
            ]),
        ]
        stats = aggregate(traces)
        kinds = {sk.kind: sk for sk in stats.span_kinds}
        assert "llm" in kinds
        assert "tool" in kinds

    def test_error_counting(self, make_trace):
        traces = [
            make_trace(error="timeout"),
            make_trace(),
        ]
        stats = aggregate(traces)
        assert stats.error_count == 1

    def test_with_evaluations(self, make_trace, make_evaluation):
        traces = [make_trace(trace_id="t1"), make_trace(trace_id="t2")]
        evals = {
            "t1": make_evaluation("t1", human_score=1),
            "t2": make_evaluation("t2", human_score=0),
        }
        stats = aggregate(traces, evals)
        assert stats.human_eval_count == 2
        assert stats.human_avg_grade == 0.5

    def test_no_cost_traces(self, make_trace):
        trace = make_trace()
        # Remove cost from spans
        for span in trace["spans"]:
            del span["cost"]
        stats = aggregate([trace])
        assert stats.cost.count == 0  # Not reported, not zero
        assert stats.cost.total == 0.0

    def test_partial_cost_coverage(self, make_trace):
        with_cost = make_trace(cost=0.05)
        without_cost = make_trace()
        for span in without_cost["spans"]:
            del span["cost"]
        stats = aggregate([with_cost, without_cost])
        # Only 1 of 2 traces has cost data
        assert stats.cost.count == 1
        assert stats.trace_count == 2
        assert abs(stats.cost.total - 0.05) < 0.001

    def test_no_token_traces(self, make_trace):
        trace = make_trace()
        for span in trace["spans"]:
            del span["input_tokens"]
            del span["output_tokens"]
        stats = aggregate([trace])
        assert stats.tokens.count == 0
