"""Unit tests for the RunSummary value type and the single cross-run
aggregation seam (ADR-0004: faults count 0.0, user-cancelled excluded)."""

from agenteval.evaluation.metrics import aggregate_runs
from agenteval.models import EvalRun, RunStatus, RunSummary

# ── RunSummary parsing (legacy dict rows must keep parsing) ────────────────


def test_parses_full_engine_summary():
    s = RunSummary.model_validate({
        "total_cases": 3,
        "passed_cases": 2,
        "failed_cases": 1,
        "total_rules": 5,
        "passed_rules": 4,
        "pass_rate": 0.6667,
        "avg_latency_ms": 123.4,
        "case_outcomes": {"c1": {"passed": True, "connectivity": False}},
        "case_errors": [{"case_id": "c2", "stage": "generate_messages", "error": "boom"}],
        "model_configs": {"judge": {"model": "m"}},
    })
    assert s.pass_rate == 0.6667
    assert s.avg_latency_ms == 123.4
    assert s.case_outcomes["c1"].passed is True
    assert s.case_outcomes["c1"].connectivity is False
    assert s.error is None


def test_parses_legacy_string_error():
    s = RunSummary.model_validate({"error": "channel exploded"})
    assert s.error is not None
    assert s.error.code == "error"
    assert s.error.message == "channel exploded"
    assert s.is_cancelled is False


def test_parses_object_error_and_detects_cancel():
    s = RunSummary.model_validate({"error": {"code": "cancelled_by_user", "message": "评测已手动停止"}})
    assert s.error is not None
    assert s.error.code == "cancelled_by_user"
    assert s.is_cancelled is True


def test_missing_keys_default_and_unknown_keys_tolerated():
    s = RunSummary.model_validate({"pass_rate": 0.5, "some_future_key": 1})
    assert s.pass_rate == 0.5
    assert s.total_cases == 0
    assert s.case_outcomes == {}
    assert s.error is None


def test_eval_run_summary_field_is_typed():
    run = EvalRun(target_id="t", scenario_id="s", summary={"pass_rate": 1.0})
    assert isinstance(run.summary, RunSummary)
    assert run.summary.pass_rate == 1.0


# ── aggregate_runs (ADR-0004) ──────────────────────────────────────────────


def _run(status, *, pass_rate=None, latency=None, error=None) -> EvalRun:
    summary = None
    if pass_rate is not None or latency is not None or error is not None:
        summary = {}
        if pass_rate is not None:
            summary["pass_rate"] = pass_rate
        if latency is not None:
            summary["avg_latency_ms"] = latency
        if error is not None:
            summary["error"] = error
    return EvalRun(target_id="t", scenario_id="s", status=status, summary=summary)


def test_empty_runs_aggregate_to_none():
    agg = aggregate_runs([])
    assert agg == {"run_count": 0, "pass_rate": None, "availability": None, "avg_latency_ms": None}


def test_completed_runs_average_pass_rate_and_latency():
    agg = aggregate_runs([
        _run(RunStatus.COMPLETED, pass_rate=1.0, latency=100),
        _run(RunStatus.COMPLETED, pass_rate=0.5, latency=200),
    ])
    assert agg["run_count"] == 2
    assert agg["pass_rate"] == 0.75
    assert agg["availability"] == 1.0
    assert agg["avg_latency_ms"] == 150.0


def test_faulted_run_counts_zero_in_both_denominators():
    # ADR-0004: a genuine execution fault drags pass_rate AND availability down.
    agg = aggregate_runs([
        _run(RunStatus.COMPLETED, pass_rate=1.0),
        _run(RunStatus.FAILED, error="channel exploded"),
    ])
    assert agg["pass_rate"] == 0.5
    assert agg["availability"] == 0.5


def test_cancelled_run_excluded_from_denominators():
    # ADR-0004: user cancellation is neither a quality nor availability signal.
    agg = aggregate_runs([
        _run(RunStatus.COMPLETED, pass_rate=1.0),
        _run(RunStatus.FAILED, error={"code": "cancelled_by_user", "message": "stop"}),
    ])
    assert agg["run_count"] == 2          # what happened stays visible
    assert agg["pass_rate"] == 1.0        # cancelled run out of the denominator
    assert agg["availability"] == 1.0


def test_all_cancelled_aggregates_to_none():
    agg = aggregate_runs([
        _run(RunStatus.FAILED, error={"code": "cancelled_by_user", "message": "stop"}),
    ])
    assert agg["run_count"] == 1
    assert agg["pass_rate"] is None
    assert agg["availability"] is None
    assert agg["avg_latency_ms"] is None


def test_in_flight_runs_count_zero_not_cancelled():
    # pending/running (no summary) are not cancelled — they count 0.0 for now.
    agg = aggregate_runs([
        _run(RunStatus.RUNNING),
        _run(RunStatus.PENDING),
    ])
    assert agg["pass_rate"] == 0.0
    assert agg["availability"] == 0.0
