"""
Midnight job - resolves previous day's pending bets via API-Football
and updates bankroll. Runs daily at midnight via Task Scheduler.

Also reconciles auto_trade_log.json against Kalshi fills (Phase 3): flags
auto-placed orders that never filled, and Kalshi fills that have no log entry.
"""
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
sys.stdout.reconfigure(encoding='utf-8', errors='replace')

from datetime import datetime, date, timedelta, timezone
from daily_signals import (resolve_pending_bets, load_bankroll, load_kalshi_bankroll,
                           load_poly_bankroll, build_html, send_email, load_history,
                           save_history, AUTO_TRADE_LOG)

AUTO_LOG = Path(AUTO_TRADE_LOG)


def _load_auto_log():
    try:
        return json.loads(AUTO_LOG.read_text())
    except (FileNotFoundError, ValueError):
        return []


def _save_auto_log(log):
    AUTO_LOG.write_text(json.dumps(log, indent=2))


def reconcile_auto_orders(lookback_hours=48):
    """
    Walk auto_trade_log.json, ask Kalshi for fills, and produce a report.

    Returns dict {orphan_logs, orphan_fills, filled, partial, html} or None
    if no auto-trade activity. orphan_logs = placed but never filled;
    orphan_fills = Kalshi fills whose order_id is not in our log.
    Also persists fill counts back onto each log entry (filled_count, filled_dollars).
    """
    log = _load_auto_log()
    if not log:
        return None

    try:
        import kalshi_trader as kt
    except Exception as e:
        print(f"  [RECON] kalshi_trader import failed: {e}")
        return None

    # Pull every fill since lookback window; aggregate by order_id
    cutoff_ts = int((datetime.now(timezone.utc) - timedelta(hours=lookback_hours)).timestamp())
    fills_by_order = {}
    try:
        cursor = None
        while True:
            params = {'limit': 200, 'min_ts': cutoff_ts}
            if cursor:
                params['cursor'] = cursor
            resp = kt._get('/portfolio/fills', params=params)
            _rows = list(resp.get('fills', []) or [])
            # XLOW FILLS ARE IN SUBACCOUNT 1 SINCE 2026-09-01, and this endpoint
            # defaults to 0. Without this pass every xlow bet placed from now on
            # would resolve as "no fill found" and be written off as never
            # having traded -- silently, and in the direction that hides losses.
            if not cursor:
                try:
                    _rows += (kt._get('/portfolio/fills',
                                      params={'limit': 200, 'min_ts': cutoff_ts,
                                              'subaccount': 1}).get('fills') or [])
                except Exception:
                    pass
            resp = dict(resp)
            resp['fills'] = _rows
            for f in resp.get('fills', []):
                oid = f.get('order_id')
                if not oid:
                    continue
                d = fills_by_order.setdefault(oid, {'count': 0.0, 'dollars': 0.0, 'ticker': f.get('ticker')})
                qty = float(f.get('count_fp', f.get('count', 0)) or 0)
                # yes_price_dollars for yes-side fills, no_price_dollars for no-side
                if f.get('side') == 'yes':
                    px = float(f.get('yes_price_dollars', 0) or 0)
                else:
                    px = float(f.get('no_price_dollars', 0) or 0)
                d['count']   += qty
                d['dollars'] += qty * px
            cursor = resp.get('cursor')
            if not cursor:
                break
    except Exception as e:
        print(f"  [RECON] fetching fills failed: {e}")
        return None

    logged_ids = {e.get('order_id') for e in log if e.get('order_id')}
    orphan_logs = []   # in log but never filled
    filled      = []   # matched (full or partial)
    for e in log:
        oid = e.get('order_id')
        if not oid:
            continue
        f = fills_by_order.get(oid)
        if not f:
            # Only flag as orphan if it's old enough to have plausibly filled or expired
            try:
                placed = datetime.fromisoformat(e['placed_at'].replace('Z', '+00:00'))
                age_h = (datetime.now(timezone.utc) - placed).total_seconds() / 3600
            except Exception:
                age_h = 999
            if age_h >= 1:
                orphan_logs.append(e)
        else:
            e['filled_count']   = f['count']
            e['filled_dollars'] = round(f['dollars'], 4)
            filled.append(e)

    # Save back the enriched log entries
    _save_auto_log(log)

    # Find Kalshi fills with no matching log entry on tickers we tried to auto-trade.
    # (Manual fills on other tickers are expected -- user still trades by hand.)
    logged_tickers = {e.get('ticker') for e in log if e.get('ticker')}
    orphan_fills = []
    for oid, f in fills_by_order.items():
        if oid in logged_ids:
            continue
        if f.get('ticker') in logged_tickers:
            orphan_fills.append({'order_id': oid, **f})

    # Build HTML report snippet for the nightly email
    rows = []
    for e in filled:
        rows.append(f"<tr><td>{e.get('home','?')} vs {e.get('away','?')}</td>"
                    f"<td>{e['ticker']}</td>"
                    f"<td>{e.get('count','?')} @ {e.get('price_cents','?')}c</td>"
                    f"<td>{e.get('filled_count',0):.1f}</td>"
                    f"<td>${e.get('filled_dollars',0):.2f}</td>"
                    f"<td style='color:#2d7d2d'>FILLED</td></tr>")
    for e in orphan_logs:
        rows.append(f"<tr><td>{e.get('home','?')} vs {e.get('away','?')}</td>"
                    f"<td>{e['ticker']}</td>"
                    f"<td>{e.get('count','?')} @ {e.get('price_cents','?')}c</td>"
                    f"<td>0</td><td>$0.00</td>"
                    f"<td style='color:#cc3333'>UNFILLED</td></tr>")
    for f in orphan_fills:
        rows.append(f"<tr><td colspan='2'><i>DRIFT: fill not in log</i></td>"
                    f"<td>{f['ticker']}</td><td>{f['count']:.1f}</td>"
                    f"<td>${f['dollars']:.2f}</td>"
                    f"<td style='color:#cc6600'>ORPHAN</td></tr>")

    html = ''
    if rows:
        html = ("<h3>Auto-trade reconciliation</h3>"
                "<table border='1' cellpadding='4' cellspacing='0' "
                "style='border-collapse:collapse;font-size:12px'>"
                "<tr style='background:#222;color:white'><th>Match</th><th>Ticker</th>"
                "<th>Placed</th><th>Filled qty</th><th>Filled $</th><th>Status</th></tr>"
                + ''.join(rows) + "</table>")

    return {
        'filled': filled,
        'orphan_logs': orphan_logs,
        'orphan_fills': orphan_fills,
        'html': html,
    }


def main():
    today = datetime.now().strftime('%Y-%m-%d')
    print(f"\n=== Nightly Bet Resolution - {today} ===")
    resolve_pending_bets()
    bankroll        = load_bankroll()
    kalshi_bankroll = load_kalshi_bankroll()
    poly_bankroll   = load_poly_bankroll()

    # Report EVERY bet resolved since the last nightly email, not just this run's
    # batch. resolve_pending_bets() is also called by the 9 AM daily_signals job,
    # so bets can be resolved (and bankroll-applied) in a run that never emails --
    # those would otherwise never appear in any Results table. The `emailed_nightly`
    # flag on each history entry is the source of truth for what's already reported.
    history  = load_history()
    resolved = [h for h in history if not h.get('emailed_nightly')]
    print(f"Resolved (unreported): {len(resolved)} bets")
    wins   = sum(1 for r in resolved if r.get('won'))
    voids  = sum(1 for r in resolved if r.get('voided'))
    losses = len(resolved) - wins - voids
    net    = sum(r.get('pnl', 0) for r in resolved)
    k_net  = sum(r.get('kalshi_pnl', 0) for r in resolved if r.get('kalshi_pnl') is not None)
    p_net  = sum(r.get('poly_pnl', 0) for r in resolved if r.get('poly_pnl') is not None)
    if resolved:
        void_str = f"  ({voids} void)" if voids else ""
        print(f"  {wins}W / {losses}L{void_str}  |  Net P&L: ${net:+.2f}  Kalshi: ${k_net:+.2f}  Poly: ${p_net:+.2f}")
    print(f"  Kalshi Bankroll: ${kalshi_bankroll:,.2f}")
    print(f"  Poly Bankroll:   ${poly_bankroll:,.2f}")

    # Phase 3: reconcile auto-trade log vs Kalshi fills
    recon = None
    try:
        recon = reconcile_auto_orders()
        if recon:
            print(f"  [RECON] filled={len(recon['filled'])} "
                  f"unfilled={len(recon['orphan_logs'])} "
                  f"orphan_fills={len(recon['orphan_fills'])}")
    except Exception as e:
        print(f"  [RECON] failed: {e}")

    if resolved:
        yesterday = (date.today() - timedelta(days=1)).strftime('%Y-%m-%d')
        subject_extra = ''
        if recon and (recon['orphan_logs'] or recon['orphan_fills']):
            subject_extra = ' [RECON-ALERT]'
        # B365 dropped 2026-08-09 with the baseline strategy -- not a tradeable
        # venue, so its bankroll was never real money. See
        # daily_signals.B365_BETTING_ENABLED.
        subject = (f"[{wins}W/{losses}L]{subject_extra} NIGHTLY RESULTS - {yesterday}  "
                   f"|  Kalshi ${kalshi_bankroll:,.2f}  Poly ${poly_bankroll:,.2f}")
        html = build_html([], yesterday, bankroll, kalshi_bankroll=kalshi_bankroll, resolved=resolved)
        if recon and recon.get('html'):
            # Inject the recon table just before </body>
            html = html.replace('</body>', recon['html'] + '</body>')
        if send_email(subject, html):
            print(f"  Email sent.")
            for h in resolved:
                h['emailed_nightly'] = True
            save_history(history)

    print("Done.")


if __name__ == '__main__':
    main()
