"""Original synthetic teaching experiment; no external data or network access."""
import hashlib
import json
import random
from pathlib import Path

SEED = 247022


def metrics(rows, predict):
    counts = {"tp": 0, "tn": 0, "fp": 0, "fn": 0}
    for row in rows:
        actual, predicted = row["y"], predict(row)
        key = ("tp" if actual else "fp") if predicted else ("fn" if actual else "tn")
        counts[key] += 1
    return {"n": len(rows), "accuracy": (counts["tp"] + counts["tn"]) / len(rows), **counts}


def run():
    rng = random.Random(SEED)
    rows = []
    for i in range(1000):
        a, b, c = (round(rng.random(), 6) for _ in range(3))
        noise = rng.uniform(-0.25, 0.25)
        rows.append({"id": i, "a": a, "b": b, "c": c,
                     "y": int(0.7 * a + 0.3 * b + noise > 0.55)})
    # IID teaching data, generated once; contiguous indices are not a time split.
    train, validation, test = rows[:600], rows[600:800], rows[800:]
    scores = {
        "a_only": lambda row: row["a"],
        "b_only": lambda row: row["b"],
        "c_only": lambda row: row["c"],
        "equal_a_b": lambda row: (row["a"] + row["b"]) / 2,
    }
    majority = int(sum(row["y"] for row in train) * 2 >= len(train))
    candidates = []
    for name, score in scores.items():
        trials = []
        for step in range(5, 16):
            threshold = step / 20
            result = metrics(train, lambda row: int(score(row) >= threshold))
            trials.append((result["accuracy"], threshold))
        # Tie policy: lower threshold; model ties resolved by name below.
        _, threshold = sorted(trials, key=lambda item: (-item[0], item[1]))[0]
        predict = lambda row, score=score, threshold=threshold: int(score(row) >= threshold)
        candidates.append({"name": name, "threshold": threshold,
                           "train": metrics(train, predict),
                           "validation": metrics(validation, predict)})
    winner = sorted(candidates, key=lambda item: (-item["validation"]["accuracy"], item["name"]))[0]
    chosen = scores[winner["name"]]
    # The final test is evaluated only after all selection has finished.
    final = metrics(test, lambda row: int(chosen(row) >= winner["threshold"]))
    dataset_bytes = json.dumps(rows, sort_keys=True, separators=(",", ":")).encode("utf-8")
    result = {
        "purpose": "synthetic teaching example, not a real-world benchmark",
        "seed": SEED, "dataset_sha256": hashlib.sha256(dataset_bytes).hexdigest(),
        "split": {"train": 600, "validation": 200, "test": 200},
        "threshold_grid": [step / 20 for step in range(5, 16)],
        "candidates": candidates, "selected": {"name": winner["name"], "threshold": winner["threshold"]},
        "test": final, "baseline": {"constant_class": majority, "test": metrics(test, lambda row: majority)},
        "limitations": ["IID synthetic rows", "single split and seed", "accuracy assumes equal error costs",
                        "candidate family designed for this exercise", "no deployment or historical MASH results"],
    }
    assert set(row["id"] for row in train).isdisjoint(row["id"] for row in validation + test)
    assert set(row["id"] for row in validation).isdisjoint(row["id"] for row in test)
    for item in [final, result["baseline"]["test"]]:
        assert sum(item[key] for key in ("tp", "tn", "fp", "fn")) == item["n"]
    return result


if __name__ == "__main__":
    print(json.dumps(run(), indent=2))
