"""
Weekly strategy-metrics report — emails the running forward-test state every week.

Covers: frozen xlow forward test (primary + f10/t60/unique/overlap diagnostics),
the strict-xlow live ledger (units + dollars, week and cumulative), league
breakdown. (The n>=100 checkpoint was passed 2026-08 and no longer tracked.)

Scheduled weekly via Task Scheduler (SoccerWeeklyStrategyMetrics).
Usage: python main\\weekly_strategy_metrics.py
"""
import io, json, sys, smtplib
from datetime import date, timedelta
from pathlib import Path
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from collections import defaultdict

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

sys.path.insert(0, r'E:\soccer\research')
sys.stdout.reconfigure(encoding='utf-8', errors='replace')

import forward_test_unique_book as F
import kalshi_only_execution as K

BET_HISTORY = Path(r'E:\soccer\data\bet_history.json')
CHECKPOINT_N = 100


def _load_env(path):
    env = {}
    try:
        for line in open(path):
            line = line.strip()
            if line and not line.startswith('#') and '=' in line:
                k, v = line.split('=', 1)
                env[k.strip()] = v.strip()
    except Exception:
        pass
    return env


_env = _load_env(r'E:\soccer\.env')
EMAIL_ADDRESS = _env.get('EMAIL_SENDER', '')
EMAIL_RECIPIENT = _env.get('EMAIL_RECIPIENT', '')
EMAIL_APP_PASSWORD = _env.get('EMAIL_APP_PASSWORD', '')


def send_email(subject, html_body, inline_png=None):
    msg = MIMEMultipart('related')
    msg['Subject'] = subject
    msg['From'] = EMAIL_ADDRESS
    msg['To'] = EMAIL_RECIPIENT
    msg.attach(MIMEText(html_body, 'html'))
    if inline_png:
        img = MIMEImage(inline_png)
        img.add_header('Content-ID', '<equity>')
        msg.attach(img)
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as srv:
        srv.login(EMAIL_ADDRESS, EMAIL_APP_PASSWORD)
        srv.send_message(msg)


def equity_png():
    """Train/test/forward cumulative-unit curves (flat 1u, Kalshi-execution pricing)."""
    fx = K.load()
    bets = [b for b in (K.xlow(f) for f in fx) if b]
    bets.sort(key=lambda b: b['gd'])
    poly = [b for b in bets if b['book'] == 'poly']
    stream = sorted([b for b in bets if b['book'] == 'kalshi'] +
                    [dict(b, px=b['kalshi_ask']) for b in poly], key=lambda b: b['gd'])
    rets = [((1 - b['px']) / b['px']) if b['won'] else -1 for b in stream]
    cut = int(len(rets) * 0.6)
    prepped = sorted(F.load_fixtures(), key=lambda f: f['gd'])
    # Backtest-league scope, matching the headline (F.BACKTEST_LEAGUES).
    fwd = [b for b in (F.fire_xlow_primary(f) for f in prepped
                       if f['gd'] > F.FREEZE_DATE and F.in_primary_leagues(f)) if b]
    fwd_rets = [((1 - b['px']) / b['px']) if b['won'] else -1 for b in fwd]

    def eq(rs):
        out, c = [0.0], 0.0
        for r in rs:
            c += r
            out.append(c)
        return out

    fig, ax = plt.subplots(figsize=(9, 5.2), dpi=150)
    fig.patch.set_facecolor('white')
    ax.set_facecolor('white')
    for name, series, color in [('Train (first 60% of backtest)', eq(rets[:cut]), '#2a78d6'),
                                ('Test (last 40%)', eq(rets[cut:]), '#008300'),
                                ('Forward (since 7/05 freeze)', eq(fwd_rets), '#eda100')]:
        ax.plot(range(len(series)), series, color=color, linewidth=2, label=name)
        ax.annotate(f'{series[-1]:+.1f}u', xy=(len(series) - 1, series[-1]), xytext=(4, 0),
                    textcoords='offset points', color='#3d3d3a', fontsize=9, va='center')
    ax.axhline(0, color='#c9c8c0', linewidth=1)
    ax.set_xlabel('Bet number', color='#3d3d3a')
    ax.set_ylabel('Cumulative units (flat 1u/bet)', color='#3d3d3a')
    ax.set_title('xlow equity — train vs test vs forward', color='#1a1a19', fontsize=12)
    ax.grid(True, color='#eeede6', linewidth=0.7)
    for s in ('top', 'right'):
        ax.spines[s].set_visible(False)
    for s in ('left', 'bottom'):
        ax.spines[s].set_color('#c9c8c0')
    ax.tick_params(colors='#3d3d3a')
    ax.legend(frameon=False, loc='upper left', fontsize=9)
    ax.margins(x=0.06)
    buf = io.BytesIO()
    fig.tight_layout()
    fig.savefig(buf, format='png', bbox_inches='tight')
    plt.close(fig)
    return buf.getvalue()


def forward_stats():
    """OOS stats per rule from the frozen tracker, plus per-league xlow rows."""
    prepped = F.load_fixtures()
    oos = [fx for fx in prepped if fx['gd'] > F.FREEZE_DATE]
    xlow = F.classify_xlow(oos)
    # PRIMARY scoped to the backtest's leagues, matching the dashboard exactly
    # (see F.BACKTEST_LEAGUES). This email and the page quote the same headline,
    # so they must read the same bet set.
    xlow_bt = F.classify_xlow_bt(oos)
    rows = {
        'xlow (PRIMARY)': F.stat(xlow_bt),
        'xlow (all leagues, pooled)': F.stat(xlow),
        'xlow (outside scope, tracking)': F.stat(F.classify_xlow_newlg(oos)),
        'xlow_f10': F.stat([b for b in xlow_bt if b['px'] >= F.XLOW_PRICE_FLOOR]),
        't60': F.stat(F.classify_t60(oos)),
    }
    uniq, overlap = F.classify(oos)
    rows['unique'] = F.stat(uniq)
    rows['overlap'] = F.stat(overlap)
    by_lg = defaultdict(list)
    for fx in oos:
        b = F.fire_xlow(fx)
        if b:
            by_lg[fx['league']].append(b)
    leagues = {lg: F.stat(bs) for lg, bs in sorted(by_lg.items(), key=lambda kv: -len(kv[1]))}
    return rows, leagues, len(xlow_bt)


def ledger_stats():
    """Strict-xlow live ledger (strategy=='xlow'): week + cumulative, units + dollars."""
    hist = json.loads(BET_HISTORY.read_text())
    xbets = [b for b in hist if b.get('strategy') == 'xlow' and b.get('ftr') not in ('VOID', 'EXP')]
    week_start = (date.today() - timedelta(days=7)).isoformat()

    def roll(bets):
        n = w = 0
        units = dollars = 0.0
        for b in bets:
            ask = b.get('kalshi_ask') or b.get('poly_ask')
            pnl = b.get('kalshi_pnl') if b.get('kalshi_pnl') is not None else b.get('poly_pnl')
            if not ask or pnl is None:
                continue
            n += 1
            won = pnl > 0
            w += won
            units += (1 - ask) / ask if won else -1
            dollars += pnl
        return n, w, units, dollars

    return roll([b for b in xbets if b.get('date', '') >= week_start]), roll(xbets)


def fmt_stat(s):
    if not s:
        return '<td colspan=4>—</td>'
    return (f"<td>{s['n']}</td><td>{s['wr']:.1f}% (impl {s['iwr']:.1f}%)</td>"
            f"<td>{s['roi']:+.1f}%</td><td>{s['z']:+.2f}</td>")


def main():
    rows, leagues, n_xlow = forward_stats()
    (wn, ww, wu, wd), (tn, tw, tu, td) = ledger_stats()
    # The "checkpoint progress" line was removed 2026-08-16. It read
    # "328/100 bets (100%)": the n>=100 gate was passed nearly 3x over, so the
    # ratio had stopped meaning anything and the bar was pinned full. Worse, a
    # progress indicator sitting at 100% reads as "nothing to do here", which is
    # the opposite of the truth -- passing the gate is what makes the rule
    # judgeable, so the line now says the judging is due.
    html = [f"<h2>xlow forward test — week of {date.today().isoformat()}</h2>",
            f"<p><b>n={n_xlow}</b> (frozen {F.FREEZE_DATE}; the n&ge;{CHECKPOINT_N} "
            f"checkpoint was passed — this is judgeable now)</p>",
            "<h3>Forward (out-of-sample) by rule</h3>",
            "<table border=1 cellpadding=4 cellspacing=0>"
            "<tr><th>rule</th><th>n</th><th>WR</th><th>ROI/bet</th><th>z</th></tr>"]
    for name, s in rows.items():
        html.append(f"<tr><td><b>{name}</b></td>{fmt_stat(s)}</tr>")
    html.append("</table>")

    html.append("<h3>Forward xlow by league</h3>"
                "<table border=1 cellpadding=4 cellspacing=0>"
                "<tr><th>league</th><th>n</th><th>WR</th><th>ROI/bet</th><th>z</th></tr>")
    for lg, s in leagues.items():
        html.append(f"<tr><td>{lg}</td>{fmt_stat(s)}</tr>")
    html.append("</table>")

    html.append("<h3>Equity curves</h3><img src='cid:equity' style='max-width:100%'>"
                "<p style='color:#888'>Forward axis is shorter — compare slopes, not endpoints.</p>")
    html.append(f"<h3>Live ledger (strict xlow only)</h3>"
                f"<p>This week: {wn} bets, {ww}W-{wn-ww}L, {wu:+.2f}u, ${wd:+.2f}<br>"
                f"Cumulative: {tn} bets, {tw}W-{tn-tw}L, {tu:+.2f}u, ${td:+.2f}</p>"
                f"<p style='color:#888'>Reference: backtest target +17.8% fee-adj "
                f"(n=286, p=.008). ROI swings of ±25pp are expected at current n — "
                f"no verdicts before the checkpoint.</p>")

    subject = (f"[XLOW WEEKLY] fwd n={n_xlow} "
               f"ROI {rows['xlow (PRIMARY)']['roi']:+.1f}% | week {wu:+.1f}u (${wd:+.0f})"
               if rows['xlow (PRIMARY)'] else f"[XLOW WEEKLY] fwd n={n_xlow}")
    send_email(subject, ''.join(html), inline_png=equity_png())
    print(f"sent: {subject}")


if __name__ == '__main__':
    main()
