"""
Full audit of EVERY soccer league shared between Kalshi and Poly.

Poly-covered = league_map.json poly_tag != null (25 leagues). For each, measure from
poly_backfill how much Kalshi AND Poly actually price it, then the unique/overlap
[0.05,0.12) bet behavior. Columns:
  cov   : data presence -> 'BOTH' if fixtures have both kalshi & poly PRE prices,
          'K-only'/'P-only' if only one book quoted, '-' if no data in window
  fix   : resolved fixtures seen in backfill
  kPx   : fixtures with >=1 Kalshi PRE price
  pPx   : fixtures with >=1 Poly  PRE price
  both  : fixtures with BOTH (the shared opportunity set)
  uN/uROI : unique bets + ROIturn (one book fires)   [K@ask, P@est-ask]
  oN/oROI : overlap bets + ROIturn (both fire; Kalshi side @ ask)
Per-league n is small => ROI is noisy; this is a COVERAGE map, not a per-league edge
claim. Read-only.
"""
import csv, glob, os, json, math
from datetime import datetime
from collections import defaultdict

DIR = r"E:\soccer\poly_backfill"
LMAP = r"E:\soccer\data\league_map.json"
BAND = (0.05, 0.12)
_FMTS = ['%Y-%m-%d %H:%M:%S', '%m/%d/%Y %H:%M', '%m/%d/%Y %H:%M:%S', '%Y-%m-%d %H:%M']
def _pts(s):
    for f in _FMTS:
        try: return datetime.strptime(s, f)
        except (ValueError, TypeError): pass
    return None
def _f(x):
    try: return float(x)
    except (TypeError, ValueError): return None
def _result(rows):
    for r in reversed(rows):
        v = (r.get('ftr') or '').upper().strip()
        if v in ('H', 'D', 'A'): return v
    return None
def fair_mult(o):
    r = [1.0/x for x in o]; s = sum(r); return [ri/s for ri in r]
def half_spread(mid):
    if   mid < 0.05: return 0.003
    elif mid < 0.25: return 0.005
    elif mid < 0.35: return 0.025
    return 0.020

def load():
    fixtures = defaultdict(list)
    for path in glob.glob(os.path.join(DIR, "*_polybackfill.csv")):
        with open(path, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                fixtures[(r.get('game_date'), r.get('league'),
                          r.get('home'), r.get('away'))].append(r)
    fx = []
    for key, rows in fixtures.items():
        gd, league, home, away = key
        if not gd: continue
        pre = sorted([(_pts(r['timestamp']), r) for r in rows
                      if (r.get('status') or '').upper() == 'PRE' and _pts(r['timestamp'])],
                     key=lambda t: t[0])
        if not pre: continue
        ftr = _result(rows)
        if ftr is None: continue
        fx.append(dict(league=league, ftr=ftr, pre=[r for _, r in pre]))
    return fx

def has_price(fx, book):
    for r in fx['pre']:
        for side in ('home', 'away'):
            v = _f(r.get(f'kalshi_{side}_mid')) if book == 'kalshi' else _f(r.get(f'poly_{side}_price'))
            if v is not None and 0 < v < 1: return True
    return False

def fire(fx, book):
    cands = []
    for side, sidx in (('home', 0), ('away', 2)):
        for r in fx['pre']:
            ph, pd_, pa = _f(r.get('pin_home')), _f(r.get('pin_draw')), _f(r.get('pin_away'))
            if None in (ph, pd_, pa) or not (ph > 1 and pd_ > 1 and pa > 1): continue
            if book == 'kalshi':
                mid = _f(r.get(f'kalshi_{side}_mid')); px = _f(r.get(f'kalshi_{side}_ask'))
            else:
                mid = _f(r.get(f'poly_{side}_price')); px = (mid + half_spread(mid)) if mid is not None else None
            if mid is None or px is None or not (0 < mid < 1) or not (0 < px < 1): continue
            pf = fair_mult((ph, pd_, pa))[sidx]
            clv = (pf - mid) / pf if pf else None
            if clv is None or not (BAND[0] <= clv < BAND[1]): continue
            won = (fx['ftr'] == 'H' and side == 'home') or (fx['ftr'] == 'A' and side == 'away')
            cands.append(dict(px=px, won=won, clv=clv)); break
    return max(cands, key=lambda d: d['clv']) if cands else None

def roi(bets):
    n = len(bets)
    if not n: return None
    wins = sum(b['won'] for b in bets)
    net = sum(((1-b['px'])/b['px']) if b['won'] else -1.0 for b in bets)
    return dict(n=n, wr=wins/n*100, roi=net/n*100)

def main():
    lm = json.load(open(LMAP))
    poly_leagues = {c: v for c, v in lm.items()
                    if isinstance(v, dict) and v.get('poly_tag')}
    fx = load()
    by = defaultdict(list)
    for f in fx: by[f['league']].append(f)

    rows = []
    for code, v in poly_leagues.items():
        lgfx = by.get(code, [])
        kpx = sum(1 for f in lgfx if has_price(f, 'kalshi'))
        ppx = sum(1 for f in lgfx if has_price(f, 'poly'))
        both = sum(1 for f in lgfx if has_price(f, 'kalshi') and has_price(f, 'poly'))
        uniq, over = [], []
        for f in lgfx:
            if not (has_price(f, 'kalshi') and has_price(f, 'poly')): continue
            k = fire(f, 'kalshi'); p = fire(f, 'poly')
            if k and p: over.append(k)
            elif k and not p: uniq.append(k)
            elif p and not k: uniq.append(p)
        cov = ('BOTH' if both else 'K-only' if kpx else 'P-only' if ppx else '-')
        rows.append(dict(code=code, name=v.get('name', ''), cov=cov, fix=len(lgfx),
                         kpx=kpx, ppx=ppx, both=both, u=roi(uniq), o=roi(over)))

    rows.sort(key=lambda r: (-r['both'], r['code']))
    print(f"Poly-covered (shared-eligible) leagues: {len(poly_leagues)}\n")
    print(f"  {'code':5s} {'league':18s} {'cov':6s} {'fix':>4} {'kPx':>4} {'pPx':>4} {'both':>5} "
          f"| {'uN':>3} {'uROI%':>7} | {'oN':>3} {'oROI%':>7}")
    tu = to = 0
    for r in rows:
        u, o = r['u'], r['o']
        un = u['n'] if u else 0; on = o['n'] if o else 0; tu += un; to += on
        ur = f"{u['roi']:+7.1f}" if u else "      -"
        orr = f"{o['roi']:+7.1f}" if o else "      -"
        print(f"  {r['code']:5s} {r['name'][:18]:18s} {r['cov']:6s} {r['fix']:4d} {r['kpx']:4d} "
              f"{r['ppx']:4d} {r['both']:5d} | {un:3d} {ur} | {on:3d} {orr}")
    print(f"\n  shared-data leagues (cov=BOTH): {sum(1 for r in rows if r['cov']=='BOTH')}/{len(rows)}")
    print(f"  Poly-covered but NO backfill data this window: "
          f"{sorted(r['code'] for r in rows if r['cov']=='-')}")
    print(f"  totals over shared fixtures: unique={tu}  overlap={to}")

if __name__ == '__main__':
    main()
