"""
Live Scoreboard — the single source of truth for real betting performance.
=========================================================================
Reads the *forward-tracked* results in `prediction_runs` (one row per analyzed
match, with the staked main pick + actual outcome + realized unit_profit) and
reports what ACTUALLY happened with real money logic — NOT a backtest.

Why this exists: backtests on this codebase are overfit (a paper "+32.7% ROI"
strategy that the live engine never even ran). The only trustworthy number is
the realized P/L recorded after matches settle. This tool surfaces it.

Read-only. SELECT only. Safe to run anytime.

Usage:
  python scripts/live_scoreboard.py
  python scripts/live_scoreboard.py --days 30
  python scripts/live_scoreboard.py --version v28-pro-max
"""
from __future__ import annotations

import argparse
import json
import os
import sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional

# utf-8 stdout so Turkish market/league names never crash on Windows cp1252
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
    try:
        sys.stdout.reconfigure(encoding="utf-8")
    except Exception:
        pass

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
AI_ENGINE_DIR = os.path.dirname(SCRIPT_DIR)
sys.path.insert(0, AI_ENGINE_DIR)

from data.db import get_clean_dsn  # noqa: E402
import psycopg2  # noqa: E402
from psycopg2.extras import RealDictCursor  # noqa: E402

ODDS_BANDS = [(0, 1.5, "<1.5"), (1.5, 2.0, "1.5-2"), (2.0, 3.0, "2-3"),
              (3.0, 5.0, "3-5"), (5.0, 6.0, "5-6"), (6.0, 7.5, "6-7.5"),
              (7.5, 999, "7.5+")]


def _f(x: Any, d: Optional[float] = None) -> Optional[float]:
    try:
        return float(x) if x is not None else d
    except (TypeError, ValueError):
        return d


def _parse(j: Any) -> Dict[str, Any]:
    if isinstance(j, str):
        try:
            return json.loads(j)
        except Exception:
            return {}
    return j or {}


def _band(odds: Optional[float]) -> str:
    if odds is None:
        return "?"
    for lo, hi, name in ODDS_BANDS:
        if lo <= odds < hi:
            return name
    return "?"


def fetch_rows(args) -> List[Dict[str, Any]]:
    dsn = get_clean_dsn()
    where = ["eventual_outcome IS NOT NULL"]
    params: List[Any] = []
    if args.version:
        where.append("engine_version = %s")
        params.append(args.version)
    if args.days:
        cutoff = datetime.now(timezone.utc) - timedelta(days=args.days)
        where.append("generated_at >= %s")
        params.append(cutoff)
    sql = f"""
        SELECT match_id, engine_version, generated_at, eventual_outcome,
               unit_profit, payload_summary
        FROM prediction_runs
        WHERE {' AND '.join(where)}
        ORDER BY generated_at ASC
    """
    with psycopg2.connect(dsn) as conn:
        with conn.cursor(cursor_factory=RealDictCursor) as cur:
            cur.execute(sql, params)
            return cur.fetchall()


def distill(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """One analytic record per run with the staked pick + realized P/L."""
    out = []
    for r in rows:
        ps = _parse(r["payload_summary"])
        mp = ps.get("main_pick") or {}
        playable = bool(mp.get("playable"))
        stake = _f(mp.get("stake_units"), 0.0) or 0.0
        profit = _f(r["unit_profit"], 0.0) or 0.0
        outcome = str(r["eventual_outcome"] or "")
        staked = playable and stake > 0
        # settled stake = a real bet with a win/loss (exclude NO_BET / push)
        settled_stake = staked and not outcome.startswith(("NO_BET", "PUSH", "VOID", "CANCEL"))
        out.append({
            "match_id": r["match_id"],
            "version": r["engine_version"],
            "ts": r["generated_at"],
            "market": mp.get("market") or "?",
            "pick": mp.get("pick"),
            "odds": _f(mp.get("odds")),
            "stake": stake,
            "profit": profit,
            "outcome": outcome,
            "staked": staked,
            "settled_stake": settled_stake,
            "win": settled_stake and profit > 0,
        })
    return out


def _agg(recs: List[Dict[str, Any]]) -> Dict[str, Any]:
    # NOTE: recorded unit_profit is on a FLAT 1u basis (win=odds-1, loss=-1),
    # independent of the brain's suggested stake_units. So ROI is profit per
    # bet at 1u flat = profit / n. (Using stake_units as denominator is wrong:
    # it double-counts and produces impossible >100% losses.)
    s = [r for r in recs if r["settled_stake"]]
    n = len(s)
    wins = sum(1 for r in s if r["win"])
    sug_stake = sum(r["stake"] for r in s)
    profit = sum(r["profit"] for r in s)
    return {
        "n": n,
        "wins": wins,
        "hit_pct": round(100.0 * wins / n, 1) if n else None,
        "sug_stake": round(sug_stake, 2),
        "profit": round(profit, 2),
        "roi_pct": round(100.0 * profit / n, 1) if n else None,  # flat 1u
    }


def _line(label: str, a: Dict[str, Any]) -> str:
    return (f"  {label:<14} n={a['n']:>4}  hit={str(a['hit_pct'] if a['hit_pct'] is not None else '-'):>5}%  "
            f"profit={a['profit']:>8.2f}u  ROI(flat1u)={str(a['roi_pct'] if a['roi_pct'] is not None else '-'):>7}%")


def risk_metrics(recs: List[Dict[str, Any]]) -> Dict[str, Any]:
    s = [r for r in sorted(recs, key=lambda x: x["ts"]) if r["settled_stake"]]
    cum = 0.0
    peak = 0.0
    max_dd = 0.0
    streak = 0
    worst_streak = 0
    for r in s:
        cum += r["profit"]
        peak = max(peak, cum)
        max_dd = min(max_dd, cum - peak)
        if r["profit"] <= 0:
            streak += 1
            worst_streak = max(worst_streak, streak)
        else:
            streak = 0
    return {"max_drawdown_u": round(max_dd, 2),
            "longest_losing_streak": worst_streak,
            "final_cum_u": round(cum, 2)}


def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--days", type=int, default=None, help="Only last N days")
    ap.add_argument("--version", help="Filter by engine_version")
    args = ap.parse_args()

    rows = fetch_rows(args)
    recs = distill(rows)

    print("=" * 74)
    print("LIVE SCOREBOARD  —  realized results from prediction_runs (NOT backtest)")
    print("=" * 74)
    if recs:
        lo = min(r["ts"] for r in recs).date()
        hi = max(r["ts"] for r in recs).date()
        print(f"window: {lo} .. {hi}   settled runs: {len(recs)}"
              + (f"   filter: {args.version}" if args.version else ""))
    print()

    overall = _agg(recs)
    print("OVERALL (staked = playable bets only)")
    print(_line("ALL", overall))
    no_bet = sum(1 for r in recs if not r["staked"])
    print(f"  (analyzed {len(recs)} matches; {overall['n']} actually staked, "
          f"{no_bet} NO_BET)")
    if overall["n"]:
        rm = risk_metrics(recs)
        print(f"  max drawdown: {rm['max_drawdown_u']}u   "
              f"longest losing streak: {rm['longest_losing_streak']}   "
              f"net: {rm['final_cum_u']}u")
    print()

    print("BY ENGINE VERSION")
    by_v = defaultdict(list)
    for r in recs:
        by_v[r["version"]].append(r)
    for v, rs in sorted(by_v.items(), key=lambda kv: -len(kv[1])):
        print(_line(v, _agg(rs)))
    print()

    print("BY MARKET (staked)")
    by_m = defaultdict(list)
    for r in recs:
        if r["settled_stake"]:
            by_m[r["market"]].append(r)
    for m, rs in sorted(by_m.items(), key=lambda kv: -len(kv[1])):
        print(_line(m, _agg(rs)))
    if not by_m:
        print("  (no staked settled bets in window)")
    print()

    print("BY ODDS BAND (staked)")
    by_b = defaultdict(list)
    for r in recs:
        if r["settled_stake"]:
            by_b[_band(r["odds"])].append(r)
    for _, _, name in ODDS_BANDS:
        if name in by_b:
            print(_line(name, _agg(by_b[name])))
    print()

    print("WEEKLY TREND (staked)")
    by_w = defaultdict(list)
    for r in recs:
        if r["settled_stake"]:
            iso = r["ts"].isocalendar()
            by_w[f"{iso[0]}-W{iso[1]:02d}"].append(r)
    for w in sorted(by_w):
        a = _agg(by_w[w])
        print(_line(w, a))
    print()
    print("=" * 74)
    print("READ: ROI < 0 over a meaningful sample = the staked signals are not")
    print("profitable. 'NO_BET' rows are free (no stake). CLV is unmeasurable")
    print("until odds movement is captured (see scripts + odds_history fix).")
    print("=" * 74)


if __name__ == "__main__":
    main()
