"""audit.py — grade the round's picks against real results.

Parses picks_today.md, fetches completed scores from the-odds-api for all 5
leagues, appends per-fixture rows to audit.csv (deduped), and prints the
cumulative calibration summary (accuracy by prob bucket / flag type).

Usage: source .env then: python3 audit.py
"""
import csv
import html
import json
import os
import re
import sys
import unicodedata
import urllib.error
import urllib.request
from datetime import datetime, timezone

HERE = os.path.dirname(os.path.abspath(__file__))
PICKS_FILE = os.path.join(HERE, "picks_today.md")
AUDIT_CSV = os.path.join(HERE, "audit.csv")
DAYS = 3

LEAGUES = [
    "soccer_epl",
    "soccer_spain_la_liga",
    "soccer_italy_serie_a",
    "soccer_france_ligue_one",
    "soccer_germany_bundesliga",
]

LEAGUE_NAMES = {
    "soccer_epl": "Premier League",
    "soccer_spain_la_liga": "La Liga",
    "soccer_italy_serie_a": "Serie A",
    "soccer_france_ligue_one": "Ligue 1",
    "soccer_germany_bundesliga": "Bundesliga",
}

# the-odds-api name variants that normalisation/containment cannot resolve
ALIASES = {
    "deportivo acoruna": "deportivo lacoruna",
    "sv 07 elversberg": "elversberg",
}

PICK_RE = re.compile(
    r"^(?P<home>.+?) vs (?P<away>.+?) \| "
    r"1 (?P<p1>\d+)% X (?P<px>\d+)% 2 (?P<p2>\d+)% \| "
    r"pick (?P<pick>[12X]) \((?P<pprob>\d+)%\)"
    r"(?: \| odds (?P<odds>[^ |]+))?"
    r"(?: \| EV\((?P<evk>[12X])\) (?P<ev>[+-]\d+)% K [\d.]+% ?(?P<flag>\S*))?"
)

CSV_FIELDS = ["date_utc", "league", "home", "away", "pick", "pick_prob",
              "p1", "px", "p2", "odds1", "oddsX", "odds2", "ev_outcome",
              "ev_pct", "flag", "res_home", "res_away", "res_outcome", "hit"]


def norm(name):
    n = unicodedata.normalize("NFKD", name)
    n = "".join(c for c in n if not unicodedata.combining(c)).lower()
    n = re.sub(r"[^a-z0-9]+", "", n)
    return ALIASES.get(n, n)


def contains(a, b):
    return len(a) >= 5 and len(b) >= 5 and (a in b or b in a)


def load_picks():
    """Parse picks from history (all rounds) + today's file, deduped."""
    rows = []
    seen = set()
    for path in (os.path.join(HERE, "picks_history.md"), PICKS_FILE):
        try:
            text = open(path, encoding="utf-8").read()
        except OSError:
            continue
        section_date = ""
        for ln in text.splitlines():
            if ln.startswith("# Sports picks —"):
                section_date = ln[len("# Sports picks — "):][:10]
                continue
            if "home_win" in ln or "|" not in ln:
                continue
            m = PICK_RE.search(ln)
            if not m:
                continue
            key = (section_date, m.group("home"), m.group("away"))
            if key in seen:
                continue
            seen.add(key)
            odds = m.group("odds") or ""
            o = odds.split("/") if odds and odds != "-" else ["", "", ""]
            rows.append({
                "home": html.unescape(m.group("home")).strip(),
                "away": html.unescape(m.group("away")).strip(),
                "p1": int(m.group("p1")), "px": int(m.group("px")),
                "p2": int(m.group("p2")), "pick": m.group("pick"),
                "pprob": int(m.group("pprob")),
                "odds1": o[0] if len(o) > 0 else "",
                "oddsX": o[1] if len(o) > 1 else "",
                "odds2": o[2] if len(o) > 2 else "",
                "evk": m.group("evk"), "ev": m.group("ev"),
                "flag": (m.group("flag") or "").strip(),
            })
    return rows


def fetch_scores(api_key):
    out = {}
    for key in LEAGUES:
        url = (f"https://api.the-odds-api.com/v4/sports/{key}/scores/"
               f"?daysFrom={DAYS}&apiKey={api_key}")
        try:
            with urllib.request.urlopen(url, timeout=30) as r:
                data = json.load(r)
        except (urllib.error.HTTPError, OSError) as e:
            print(f"  ! {key}: {e}")
            continue
        for ev in data:
            if not ev.get("completed"):
                continue
            sc = {s["name"]: int(s["score"]) for s in ev.get("scores", [])}
            if ev["home_team"] not in sc or ev["away_team"] not in sc:
                continue
            out[(key, norm(ev["home_team"]), norm(ev["away_team"]))] = (
                ev["commence_time"], sc[ev["home_team"]], sc[ev["away_team"]])
    return out


def outcome(h, a):
    return "1" if h > a else ("2" if a > h else "X")


def existing_keys():
    keys = set()
    if not os.path.exists(AUDIT_CSV):
        return keys
    with open(AUDIT_CSV, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            keys.add((row["home"], row["away"], row["res_home"],
                      row["res_away"], row["date_utc"][:10]))
    return keys


def export_app_results():
    """Write app_results.json (graded picks + calibration) for the mini-app."""
    if not os.path.exists(AUDIT_CSV):
        return 0
    rows = list(csv.DictReader(open(AUDIT_CSV, encoding="utf-8")))
    days = {}
    for r in rows:
        d = (r["date_utc"] or "")[:10]
        days.setdefault(d, []).append({
            "league": LEAGUE_NAMES.get(r["league"], r["league"]),
            "home": html.unescape(r["home"]), "away": html.unescape(r["away"]),
            "pick": r["pick"], "pick_prob": int(r["pick_prob"] or 0),
            "score_home": int(r["res_home"] or 0),
            "score_away": int(r["res_away"] or 0),
            "res_outcome": r["res_outcome"], "hit": r["hit"] == "1",
        })
    total = len(rows)
    hits = sum(1 for r in rows if r["hit"] == "1")
    buckets = []
    for label, lo, hi in [("50-59%", 50, 60), ("60-69%", 60, 70),
                          ("70%+", 70, 200)]:
        sel = [r for r in rows if lo <= int(r["pick_prob"] or 0) < hi]
        if sel:
            buckets.append({
                "label": label, "n": len(sel),
                "hits": sum(1 for r in sel if r["hit"] == "1"),
                "avg_prob": round(sum(int(r["pick_prob"]) for r in sel)
                                  / len(sel)),
            })
    out = {
        "generated_utc": datetime.now(timezone.utc).isoformat(),
        "total": total, "hits": hits,
        "accuracy": round(hits / total, 3) if total else None,
        "buckets": buckets,
        "days": [{"date": d, "matches": days[d]}
                 for d in sorted(days, reverse=True)],
    }
    with open(os.path.join(HERE, "app_results.json"), "w",
              encoding="utf-8") as f:
        json.dump(out, f, ensure_ascii=False)
    return total


def main():
    if not os.path.exists(PICKS_FILE):
        print("no picks file:", PICKS_FILE)
        return
    api_key = os.environ.get("ODDS_API_KEY", "")
    if not api_key:
        print("ODDS_API_KEY not set — source .env first")
        return

    picks = load_picks()
    print(f"parsed {len(picks)} fixtures from picks file")
    scores = fetch_scores(api_key)
    print(f"fetched {sum(len(v) for v in scores.values())} completed events")

    new_rows, pending = [], []
    for p in picks:
        key = (None, norm(p["home"]), norm(p["away"]))
        match = scores.get(key)
        if not match:
            for (lk, nh, na), val in scores.items():
                if contains(norm(p["home"]), nh) and contains(norm(p["away"]), na):
                    match = val
                    key = (lk, nh, na)
                    break
        if not match:
            pending.append(f"{p['home']} vs {p['away']}")
            continue
        ts, rh, ra = match
        res = outcome(rh, ra)
        row = {
            "date_utc": ts[:10], "league": key[0] or "", "home": p["home"],
            "away": p["away"], "pick": p["pick"], "pick_prob": p["pprob"],
            "p1": p["p1"], "px": p["px"], "p2": p["p2"],
            "odds1": p["odds1"], "oddsX": p["oddsX"], "odds2": p["odds2"],
            "ev_outcome": p["evk"] or "", "ev_pct": p["ev"] or "",
            "flag": p["flag"], "res_home": rh, "res_away": ra,
            "res_outcome": res, "hit": "1" if p["pick"] == res else "0",
        }
        new_rows.append(row)

    keys_done = existing_keys()
    fresh = [r for r in new_rows
             if (r["home"], r["away"], r["res_home"], r["res_away"],
                 r["date_utc"]) not in keys_done]
    if fresh:
        header = not os.path.exists(AUDIT_CSV)
        with open(AUDIT_CSV, "a", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=CSV_FIELDS)
            if header:
                w.writeheader()
            for r in fresh:
                w.writerow(r)
        print(f"appended {len(fresh)} new audit rows")
    else:
        print("no new rows (all already audited)")

    # ---- cumulative summary over audit.csv ----
    if not os.path.exists(AUDIT_CSV):
        print("nothing pending? remaining unplayed:",
              ", ".join(pending) or "none")
        return
    rows = list(csv.DictReader(open(AUDIT_CSV, encoding="utf-8")))
    if not rows:
        return
    total = len(rows)
    hits = sum(1 for r in rows if r["hit"] == "1")
    print(f"\n=== AUDIT (cumulative, n={total}) — accuracy {hits}/{total} "
          f"= {100*hits/total:.1f}% ===")
    for bucket, lo, hi in [("50-59%", 50, 60), ("60-69%", 60, 70),
                           ("70%+", 70, 200)]:
        sel = [r for r in rows if lo <= int(r["pick_prob"]) < hi]
        if sel:
            h = sum(1 for r in sel if r["hit"] == "1")
            print(f"  prob {bucket}: {h}/{len(sel)} = "
                  f"{100*h/len(sel):.0f}%  (expected ~{sum(int(r['pick_prob']) for r in sel)//len(sel)}%)")
    bets = [r for r in rows if "BET" in r["flag"]]
    if bets:
        bhit = [r for r in bets if r["res_outcome"] == r["ev_outcome"]]
        blist = ", ".join(f"{r['home']} {r['res_home']}-{r['res_away']} {r['away']}"
                          for r in bhit) or "none"
        print(f"  ⚡BET flagged outcome won: {len(bhit)}/{len(bets)} = "
              f"{100*len(bhit)/len(bets):.0f}%  ({blist})")
    huge = [r for r in rows if "HUGE" in r["flag"]]
    if huge:
        hhit = [r for r in huge if r["res_outcome"] == r["ev_outcome"]]
        hlist = ", ".join(f"{r['home']} {r['res_home']}-{r['res_away']} "
                          f"{r['away']} [EV {r['ev_pct']}%]"
                          for r in hhit) or "none"
        print(f"  ⚠HUGE flagged outcome ('model error' candidate) won: "
              f"{len(hhit)}/{len(huge)} = {100*len(hhit)/len(huge):.0f}%  ({hlist})")
    draws = [r for r in rows if r["res_outcome"] == "X"]
    if draws:
        print(f"  actual draws: {len(draws)}/{total} "
              f"({100*len(draws)/total:.0f}%) vs model X-avg "
              f"{sum(int(r['px']) for r in rows)//total}%")
    if pending:
        print("\nnot yet played:", ", ".join(pending))
    n = export_app_results()
    print(f"[app_results] exported {n} graded rows")


if __name__ == "__main__":
    main()