#!/usr/bin/env python3
"""Synthetic selection-bias fixture, not an OpenTelemetry collector.

Run with Python 3.9+: python3 agent-trace-sampling.py
No external dependencies, network access, or files written.
"""
import json
import unittest
from collections import Counter


def summarize(rows):
    """Count unique run outcomes; unknown outcomes stay in the inventory."""
    ids = set()
    counts = Counter()
    for row in rows:
        if set(row) != {"run_id", "outcome"}:
            raise ValueError("expected exactly run_id and outcome")
        run_id, outcome = row["run_id"], row["outcome"]
        if not isinstance(run_id, str) or not run_id.strip() or run_id in ids:
            raise ValueError("run_id must be a nonempty unique string")
        if outcome not in ("success", "failure", "unknown"):
            raise ValueError("unsupported outcome")
        ids.add(run_id)
        counts[outcome] += 1
    known = counts["success"] + counts["failure"]
    return {
        "runs": len(ids),
        "success": counts["success"],
        "failure": counts["failure"],
        "unknown": counts["unknown"],
        "failure_fraction_of_known": counts["failure"] / known if known else None,
        "complete_failure_fraction": (
            counts["failure"] / len(ids)
            if ids and not counts["unknown"] else None
        ),
    }


def fixture():
    # IDs and outcomes deliberately construct a counterexample, not a random sample.
    return [{"run_id": f"run-{i:03}",
             "outcome": "failure" if i >= 95 else "success"}
            for i in range(100)]


def scenarios():
    runs = fixture()
    head_ids = {row["run_id"] for row in runs[:10]}
    head = [row for row in runs if row["run_id"] in head_ids]
    # Idealised outcome-aware retention with all runs available upstream.
    tail = [row for row in runs
            if row["outcome"] == "failure" or row["run_id"] in head_ids]
    # Later retention cannot recover events removed by the upstream stage.
    chained = [row for row in head
               if row["outcome"] == "failure" or row["run_id"] in head_ids]
    return {name: summarize(rows) for name, rows in (
        ("full_inventory", runs), ("preselected_subset", head),
        ("failure_enriched_subset", tail), ("tail_after_preselection", chained))}


class SamplingTests(unittest.TestCase):
    def test_full_inventory(self):
        s = scenarios()["full_inventory"]
        self.assertEqual((s["runs"], s["failure"]), (100, 5))
        self.assertEqual(s["complete_failure_fraction"], 0.05)

    def test_preselection_loses_failures(self):
        s = scenarios()["preselected_subset"]
        self.assertEqual((s["runs"], s["failure"]), (10, 0))

    def test_enrichment_changes_denominator(self):
        s = scenarios()["failure_enriched_subset"]
        self.assertEqual((s["runs"], s["failure"]), (15, 5))
        self.assertAlmostEqual(s["complete_failure_fraction"], 1 / 3)

    def test_tail_cannot_recover(self):
        s = scenarios()
        self.assertEqual(s["tail_after_preselection"], s["preselected_subset"])

    def test_unknown_blocks_complete_rate(self):
        s = summarize([{"run_id": "a", "outcome": "failure"},
                       {"run_id": "b", "outcome": "unknown"}])
        self.assertIsNone(s["complete_failure_fraction"])
        self.assertEqual(s["unknown"], 1)
        self.assertEqual(s["failure_fraction_of_known"], 1)

    def test_empty_has_no_rate(self):
        s = summarize([])
        self.assertIsNone(s["complete_failure_fraction"])
        self.assertIsNone(s["failure_fraction_of_known"])

    def test_duplicate_rejected(self):
        row = {"run_id": "a", "outcome": "success"}
        with self.assertRaises(ValueError):
            summarize([row, row])

    def test_invalid_records_rejected(self):
        for row in ({"run_id": "a"}, {"run_id": "", "outcome": "success"},
                    {"run_id": True, "outcome": "success"},
                    {"run_id": "a", "outcome": "pending"},
                    {"run_id": "a", "outcome": "success", "extra": 1}):
            with self.subTest(row=row), self.assertRaises(ValueError):
                summarize([row])

    def test_order_does_not_change_summary(self):
        self.assertEqual(summarize(fixture()), summarize(list(reversed(fixture()))))

    def test_all_unknown_has_no_known_rate(self):
        s = summarize([{"run_id": "a", "outcome": "unknown"}])
        self.assertIsNone(s["failure_fraction_of_known"])
        self.assertIsNone(s["complete_failure_fraction"])


if __name__ == "__main__":
    print(json.dumps(scenarios(), indent=2))
    result = unittest.TextTestRunner(verbosity=2).run(
        unittest.defaultTestLoader.loadTestsFromTestCase(SamplingTests))
    raise SystemExit(0 if result.wasSuccessful() else 1)
