"""Backtest the strength-Poisson model on Bundesliga history.

Honest protocol:
- For every finished match, the standings used are built ONLY from matches that
  kicked off strictly BEFORE it (no lookahead, postponements handled by time).
- Metrics: pick accuracy, hit rate per pick type, Brier score, and a
  theoretical flat-stake P&L at model-fair odds 1/p (NOT real money - no vig).
- Baselines: always-home (league edge) and random coin-flip-ish.

Usage: python3 backtest.py [season ...]   (default: 2024 2025 2026)
"""
import sys
import json

from sources import openligadb_matches
from model import predict, match_probs

SEASONS = [int(s) for s in sys.argv[1:]] or [2024, 2025, 2026]


def standings_before(matches, kickoff):
    pts, played = {}, {}
    for m in matches:
        if m["finished"] and m["hg"] is not None and m["kickoff_utc"] \
                and m["kickoff_utc"] < kickoff:
            pts[m["home"]] = pts.get(m["home"], 0) + m["hg"]
            pts[m["away"]] = pts.get(m["away"], 0) + m["ag"]
            played[m["home"]] = played.get(m["home"], 0) + 1
            played[m["away"]] = played.get(m["away"], 0) + 1
    return {t: (pts[t] / played[t], played[t]) for t in pts if played.get(t)}


def evaluate_season(season):
    matches = [m for m in openligadb_matches("bl1", season)
               if m["finished"] and m["hg"] is not None]
    preds, brier_sum, n = [], 0.0, 0
    for m in matches:
        st = standings_before(matches, m["kickoff_utc"])
        if m["home"] not in st or m["away"] not in st:
            continue  # both teams need a prior game
        avg = sum(v[0] for v in st.values()) / len(st)
        p = predict(st[m["home"]][0], st[m["away"]][0], avg,
                    st[m["home"]][1], st[m["away"]][1])
        actual = "1" if m["hg"] > m["ag"] else ("X" if m["hg"] == m["ag"] else "2")
        ap = {"1": p["ph"], "X": p["pd"], "2": p["pa"]}[actual]
        brier_sum += (1 - ap) ** 2
        preds.append((p, actual, m))
        n += 1

    def stats(picks):
        if not picks:
            return {"n": 0}
        ok = sum(1 for p, a, _ in picks if p["pick"] == a)
        by_pick = {}
        for p, a, _ in picks:
            d = by_pick.setdefault(p["pick"], {"n": 0, "hit": 0})
            d["n"] += 1
            d["hit"] += (p["pick"] == a)
        # flat stake at FAIR odds 1/p (no bookmaker margin -> hypothetical)
        pl = sum((1.0 / p["pick_prob"] - 1.0) if p["pick"] == a else -1.0
                 for p, a, _ in picks)
        return {"n": len(picks), "acc": ok / len(picks),
                "by_pick": {k: {"n": v["n"], "hit": v["hit"] / v["n"]}
                            for k, v in by_pick.items()},
                "fair_odds_pl_per_bet": pl / len(picks)}

    conf = [x for x in preds if x[0]["pick_prob"] >= 0.55]
    return {"season": season, "n": n, "brier": brier_sum / n,
            "all": stats(preds), "conf_ge_55": stats(conf)}


def main():
    total_n = 0
    print("== Strength-Poisson backtest, Bundesliga ==")
    print("(standings built only from matches kicked off earlier; "
          "fair-odds P&L is hypothetical, no vig)\n")
    print(f"{'season':<8}{'n':>5}{'acc':>8}{'brier':>8}{'acc>=55%':>10}"
          f"{'n>=55%':>8}{'fairPL(all)':>13}{'fairPL(>=55%)':>14}")
    for s in SEASONS:
        r = evaluate_season(s)
        a = r["all"]; c = r["conf_ge_55"]
        total_n += r["n"]
        print(f"{s:<8}{r['n']:>5}{a['acc']:>8.1%}{r['brier']:>8.3f}"
              f"{c['acc'] if c['n'] else float('nan'):>10.1%}"
              f"{c['n']:>8}{a['fair_odds_pl_per_bet']:>+13.2f}"
              f"{c['fair_odds_pl_per_bet'] if c['n'] else float('nan'):>+14.2f}")
        hp = a["by_pick"].get("1", {}); hx = a["by_pick"].get("X", {})
        ha = a["by_pick"].get("2", {})
        print(f"   by pick -> 1:{hp.get('hit',0):.1%}(n={hp.get('n',0)}) "
              f"X:{hx.get('hit',0):.1%}(n={hx.get('n',0)}) "
              f"2:{ha.get('hit',0):.1%}(n={ha.get('n',0)})")
    print(f"\ntotal matches evaluated: {total_n}")


if __name__ == "__main__":
    main()