#!/usr/bin/env python3
"""Synthetic closed-cohort accounting, not a production collector.
Python 3.9+. No network calls. Run --test for regression tests.
"""
import json
import math
import sys
import unittest
from fractions import Fraction

OUTCOMES = {"success", "timeout", "error", "cancelled"}


def nearest_rank(values, p=Fraction(95, 100)):
    if not values or not 0 < p <= 1:
        raise ValueError("nonempty values and 0 < p <= 1 required")
    return sorted(values)[math.ceil(p * len(values)) - 1]


def report(expected_ids, rows, threshold_ms=500):
    """Rows are (request_id, terminal_outcome, observed_elapsed_ms).

    Caller supplies a closed, fully reconciled arrival cohort. Elapsed time
    ends at the caller-visible terminal outcome, NOT eventual backend success.
    All expected requests remain in the good-event denominator.
    """
    expected = list(expected_ids)
    if not expected or any(type(x) is not str or not x for x in expected):
        raise ValueError("nonempty string inventory required")
    if len(set(expected)) != len(expected):
        raise ValueError("duplicate expected ID")
    if type(threshold_ms) not in (int, float) or not math.isfinite(threshold_ms) or threshold_ms <= 0:
        raise ValueError("positive finite threshold required")
    observed = {}
    for request_id, outcome, elapsed in rows:
        if request_id not in expected or request_id in observed:
            raise ValueError("unexpected or duplicate request ID")
        if outcome not in OUTCOMES:
            raise ValueError("unknown terminal outcome")
        if type(elapsed) not in (int, float) or not math.isfinite(elapsed) or elapsed < 0:
            raise ValueError("finite nonnegative elapsed time required")
        observed[request_id] = (outcome, elapsed)
    if set(observed) != set(expected):
        raise ValueError("cohort incomplete; do not silently drop missing requests")
    successes = [t for o, t in observed.values() if o == "success"]
    durations = [t for o, t in observed.values()]
    good = sum(o == "success" and t <= threshold_ms for o, t in observed.values())
    counts = {o: sum(value[0] == o for value in observed.values()) for o in sorted(OUTCOMES)}
    return {
        "arrivals": len(expected), "outcomes": counts,
        "successful_request_p95_ms": nearest_rank(successes) if successes else None,
        "terminal_outcome_p95_ms": nearest_rank(durations),
        "good_events": good, "good_event_denominator": len(expected),
        "good_fraction": float(Fraction(good, len(expected))),
        "threshold_ms": threshold_ms,
    }


def fixture():
    ids = [f"request-{i}" for i in range(100)]
    rows = [(key, "success", 100) if i < 90 else (key, "timeout", 2000)
            for i, key in enumerate(ids)]
    return ids, rows


class Tests(unittest.TestCase):
    def test_fixture(self):
        result = report(*fixture())
        self.assertEqual(result["successful_request_p95_ms"], 100)
        self.assertEqual(result["terminal_outcome_p95_ms"], 2000)
        self.assertEqual(result["good_events"], 90)
        self.assertEqual(result["good_event_denominator"], 100)
        self.assertEqual(result["good_fraction"], 0.9)
        self.assertEqual(result["outcomes"]["timeout"], 10)

    def test_no_success(self):
        r = report(["a"], [("a", "timeout", 2000)])
        self.assertIsNone(r["successful_request_p95_ms"])
        self.assertEqual(r["good_fraction"], 0)

    def test_threshold_inclusive_and_slow_success(self):
        r = report(["a", "b"], [("a", "success", 500), ("b", "success", 501)])
        self.assertEqual(r["good_fraction"], 0.5)

    def test_fast_errors_and_cancellations_are_not_good(self):
        r = report(["a", "b"], [("a", "error", 1), ("b", "cancelled", 1)])
        self.assertEqual(r["good_events"], 0)

    def test_missing_request(self):
        with self.assertRaises(ValueError):
            report(["a", "b"], [("a", "success", 100)])

    def test_duplicate_row(self):
        with self.assertRaises(ValueError):
            report(["a"], [("a", "success", 100)] * 2)

    def test_duplicate_inventory(self):
        with self.assertRaises(ValueError):
            report(["a", "a"], [("a", "success", 100)])

    def test_unexpected_request(self):
        with self.assertRaises(ValueError):
            report(["a"], [("b", "success", 100)])

    def test_invalid_outcome(self):
        with self.assertRaises(ValueError):
            report(["a"], [("a", "pending", 100)])

    def test_invalid_durations(self):
        for value in (-1, float("nan"), float("inf"), True, "100"):
            with self.subTest(value=value), self.assertRaises(ValueError):
                report(["a"], [("a", "success", value)])

    def test_invalid_thresholds(self):
        for value in (0, -1, float("nan"), float("inf"), True):
            with self.subTest(value=value), self.assertRaises(ValueError):
                report(["a"], [("a", "success", 100)], value)

    def test_empty_inventory(self):
        with self.assertRaises(ValueError):
            report([], [])


if __name__ == "__main__":
    if sys.argv[1:] == ["--test"]:
        unittest.main(argv=[sys.argv[0]])
    elif not sys.argv[1:]:
        print(json.dumps(report(*fixture()), indent=2))
    else:
        raise SystemExit("usage: agent-timeout-latency.py [--test]")
