"""Score the sealed replication rule. Nothing here chooses a threshold.

    python analyse.py > results.txt

The filters and the release definition are the first study's, restated here
and checked against it: the drop counters print in the same order, and the
first study's own rule module is loaded by path at the end to apply ITS bars
to this population's Friday share.
"""

import collections
import csv
import datetime as dt
import os
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))

from packaging.version import InvalidVersion, Version

import rule  # this directory's rule.py comes first on sys.path

FIRST = pathlib.Path(__file__).resolve().parent.parent / "friday-releases"
CSV = pathlib.Path(os.path.expanduser("~/data/friday-releases-replication/releases.csv"))
START = dt.datetime(2016, 1, 1, tzinfo=dt.timezone.utc)
END = dt.datetime(2026, 9, 1, tzinfo=dt.timezone.utc)
DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]


def load():
    drops = collections.Counter()
    rows = []
    with CSV.open() as fh:
        for r in csv.DictReader(fh):
            drops["rows read"] += 1
            if not r["first_upload_iso"]:
                drops["no file (deleted release)"] += 1
                continue
            try:
                v = Version(r["version"])
            except InvalidVersion:
                drops["version unparseable"] += 1
                continue
            if v.is_prerelease:
                drops["pre-release"] += 1
                continue
            t = dt.datetime.fromisoformat(r["first_upload_iso"].replace("Z", "+00:00"))
            if not (START <= t < END):
                drops["outside 2016..2026-09"] += 1
                continue
            rows.append((r["project"], t, r["version"], r["yanked"] == "1"))
    drops["kept"] = len(rows)
    return rows, drops


def triple(v):
    """(major, minor, micro) if the version is a plain three-part release."""
    try:
        p = Version(v)
    except InvalidVersion:
        return None
    return (p.major, p.minor, p.micro) if len(p.release) == 3 else None


def follow_ups(rows, window_h):
    """(weekday, any follow-up, patch-level follow-up, eligible-for-patch)."""
    by_project = collections.defaultdict(list)
    for project, t, v, _ in rows:
        by_project[project].append((t, v))
    horizon = END - dt.timedelta(hours=rule.HORIZON_H)
    out = []
    for times in by_project.values():
        times.sort()
        for i, (t, v) in enumerate(times):
            if t > horizon:
                continue
            nxt = times[i + 1] if i + 1 < len(times) else None
            inside = nxt is not None and (nxt[0] - t) <= dt.timedelta(hours=window_h)
            a, b = triple(v), triple(nxt[1]) if nxt else None
            patch_eligible = a is not None and (nxt is None or b is not None)
            patch = bool(inside and a and b and a[:2] == b[:2] and b[2] > a[2])
            out.append((t.weekday(), inside, patch, patch_eligible))
    return out


def counts(pairs, days, field):
    n = sum(1 for r in pairs if r[0] in days)
    k = sum(1 for r in pairs if r[0] in days and r[field])
    return k, n


def patch_counts(pairs, days):
    n = sum(1 for r in pairs if r[0] in days and r[3])
    k = sum(1 for r in pairs if r[0] in days and r[3] and r[2])
    return k, n


def score(name, k1, n1, k2, n2):
    rr, lo, hi = rule.rr_ci(k1, n1, k2, n2)
    v = rule.verdict(rr, lo, hi)
    print(f"  {name}")
    print(f"    Friday {k1:,}/{n1:,} = {k1/n1:.4f}   Mon..Thu {k2:,}/{n2:,} = {k2/n2:.4f}")
    print(f"    RR = {rr:.4f}   95% CI [{lo:.4f}, {hi:.4f}]   "
          f"half-width {rule.half_width(rr, lo, hi):.3%} "
          f"(sealed ceiling {rule.MAX_HALF_WIDTH:.1%})")
    print(f"    VERDICT: {v.upper()}")
    return v


def main():
    rows, drops = load()
    names = set((pathlib.Path(__file__).parent / "sample.txt").read_text().split())
    first = set((FIRST / "sample.txt").read_text().split())
    print("=" * 72)
    print("FRIDAY RELEASES, REPLICATED on a held-out population")
    print("rule.py sha256 63906c4b11fec868b492e3378a67af5b9a7efb6e6f2a8fcbe80d49266be632e1")
    print("seal rule.friday-releases-replication id 6014, 1789586963188 ms,")
    print("2026-09-16 21:29 Rome; the first row was on disk at 21:30")
    print("=" * 72)
    print(f"\n  overlap with the first study's sample: {len(names & first)} "
          f"(the sealed rule asserts zero)")

    print("\nFILTERS")
    for k in ("rows read", "no file (deleted release)", "version unparseable",
              "pre-release", "outside 2016..2026-09", "kept"):
        print(f"  {k:<32} {drops[k]:>8,}")
    print(f"  {'distinct projects kept':<32} {len({p for p, _, _, _ in rows}):>8,}")

    pairs = follow_ups(rows, rule.WINDOW_H)
    print(f"\n  eligible releases ({rule.HORIZON_H} h horizon): {len(pairs):,}")
    print(f"\n  follow-up within {rule.WINDOW_H} h, by day (reported):")
    for d in range(7):
        k, n = counts(pairs, {d}, 1)
        kp, np_ = patch_counts(pairs, {d})
        print(f"    {DAYS[d]}  any {k:>6,}/{n:>6,} = {k/n:6.2%}   "
              f"patch {kp:>6,}/{np_:>6,} = {kp/np_:6.2%}")

    print("\n" + "-" * 72)
    print(f"SCORED CLAIMS (survived lo > {rule.SURVIVE_LO} and RR >= "
          f"{rule.SURVIVE_MIN_RR}; killed CI inside {rule.KILL_BAND})")
    print("-" * 72)
    k1, n1 = counts(pairs, {4}, 1)
    k2, n2 = counts(pairs, {0, 1, 2, 3}, 1)
    v_any = score("any follow-up within 168 h", k1, n1, k2, n2)
    kp1, np1 = patch_counts(pairs, {4})
    kp2, np2 = patch_counts(pairs, {0, 1, 2, 3})
    v_patch = score("patch-level follow-up within 168 h", kp1, np1, kp2, np2)

    print("\n  NEGATIVE CONTROL — Tuesday against Mon/Wed/Thu (sealed, scored):")
    for label, (a, b), (c, d2) in (
        ("any", counts(pairs, {1}, 1), counts(pairs, {0, 2, 3}, 1)),
        ("patch", patch_counts(pairs, {1}), patch_counts(pairs, {0, 2, 3})),
    ):
        rr, lo, hi = rule.rr_ci(a, b, c, d2)
        ok = rule.control_clean(rr, lo, hi)
        print(f"    {label:<6} RR = {rr:.4f}  [{lo:.4f}, {hi:.4f}]  "
              f"{'clean' if ok else 'DIRTY — that claim is VOID by the sealed rule'}")

    print("\n  SECOND CONTROL — the weekend against Mon..Thu (direction only):")
    for d, nm in ((5, "Sat"), (6, "Sun")):
        a, b = counts(pairs, {d}, 1)
        rr, lo, hi = rule.rr_ci(a, b, k2, n2)
        print(f"    {nm}  RR = {rr:.4f}  [{lo:.4f}, {hi:.4f}]")

    print("\n" + "-" * 72)
    print("REPORTED, registered, not scored")
    print("-" * 72)
    wd = collections.Counter(t.weekday() for _, t, _, _ in rows)
    tot = sum(wd.values())
    weekday_n = sum(wd[d] for d in range(5))
    print("  weekday histogram:")
    for d in range(7):
        print(f"    {DAYS[d]}  {wd[d]:>7,}  {wd[d]/tot:6.2%}")
    fri = wd[4] / weekday_n
    import importlib.util
    spec = importlib.util.spec_from_file_location("rule1", FIRST / "rule.py")
    rule1 = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(rule1)
    lo, hi = rule1.wilson(wd[4], weekday_n)
    print(f"\n  claim A out of sample: Friday's share of Mon..Fri {fri:.4f} "
          f"[{lo:.4f}, {hi:.4f}], n = {weekday_n:,}")
    print(f"    the FIRST study's bars say: {rule1.verdict_a(lo, hi).upper()} "
          f"(there it was 0.1899, inconclusive)")

    print(f"\n  the same claim at {rule.SECOND_WINDOW_H} h (also day-balanced):")
    p2 = follow_ups(rows, rule.SECOND_WINDOW_H)
    a, b = counts(p2, {4}, 1)
    c, d2 = counts(p2, {0, 1, 2, 3}, 1)
    rr, lo2, hi2 = rule.rr_ci(a, b, c, d2)
    trr, tlo, thi = rule.rr_ci(*counts(p2, {1}, 1), *counts(p2, {0, 2, 3}, 1))
    print(f"    Friday RR {rr:.4f} [{lo2:.4f}, {hi2:.4f}]   "
          f"control {trr:.4f} [{tlo:.4f}, {thi:.4f}]")

    print("\n  the working-days table that voided the first study's 72 h proxy,")
    print("  recomputed here (it should still hold — it is a fact about calendars):")
    p72 = follow_ups(rows, 72)
    by_w = collections.defaultdict(lambda: [0, 0])
    for d, hit, _, _ in p72:
        w = sum(1 for j in range(1, 4) if (d + j) % 7 < 5)
        by_w[w][1] += 1
        by_w[w][0] += hit
    for w in sorted(by_w):
        k, n = by_w[w]
        print(f"    {w} working days in a 72 h window   {k/n:.4f}   n = {n:,}")

    yank = collections.defaultdict(lambda: [0, 0])
    for _, t, _, y in rows:
        yank[t.weekday()][1] += 1
        yank[t.weekday()][0] += y
    print("\n  yank rate by weekday:")
    for d in range(7):
        k, n = yank[d]
        print(f"    {DAYS[d]}  {k:>5,}/{n:>7,}  {k/n:.3%}")

    print("\n" + "=" * 72)
    print(f"ANY FOLLOW-UP: {v_any.upper()}    PATCH FOLLOW-UP: {v_patch.upper()}")
    print("=" * 72)


if __name__ == "__main__":
    main()
