"""
Backfill SETTLED Kalshi soccer markets with their pre-kickoff prices.

WHY: our live_data only holds ~976 fixtures with Kalshi quotes -- not enough to
resolve a ~5% relative calibration effect (it would need the effect to be ~22%
before it could see it). Kalshi's own API has every settled market we ever cared
about, so the sample is sitting there waiting to be pulled.

HOW:
  1. /markets?series_ticker=...&status=settled  (paginated) -> every finalised
     market for each of our leagues.
  2. Group the 3 markets of an event (home / away / tie) by event_ticker.
  3. For each market, /series/{s}/markets/{t}/candlesticks around kickoff and
     take the LAST candle that closes at or before kickoff and still has a live
     two-sided book (bid > 0, ask < 1). Settled markets report bid 0 / ask 1, so
     that filter is what keeps post-resolution prices out of the sample.
  4. Devig the three mids proportionally, record the result, write one row per
     fixture.

Output: research/kalshi_backfill.csv  (resumable -- reruns skip events already
written, so an interrupted pull can just be restarted).

Usage:  python research\\kalshi_backfill.py [max_events_per_series]
"""
import sys
import os
import csv
import json
import time
import datetime as dt
from collections import defaultdict

sys.path.insert(0, r'E:\soccer\main')
import kalshi_trader as kt

OUT = r'E:\soccer\research\kalshi_backfill.csv'
MAP = r'E:\soccer\data\league_map.json'
MAX_PER_SERIES = int(sys.argv[1]) if len(sys.argv) > 1 else 400
FIELDS = ['div', 'series', 'event', 'kickoff', 'home', 'away', 'ftr',
          'k_home_bid', 'k_home_ask', 'k_draw_bid', 'k_draw_ask',
          'k_away_bid', 'k_away_ask', 'fair_home', 'fair_draw', 'fair_away']


def series_map():
    m = json.load(open(MAP, encoding='utf-8'))
    return [(k, v['kalshi_series']) for k, v in m.items()
            if isinstance(v, dict) and v.get('kalshi_series')]


def settled_markets(series, cap):
    """All finalised markets for a series, paginated."""
    out, cursor = [], None
    while len(out) < cap:
        p = {'series_ticker': series, 'status': 'settled', 'limit': 200}
        if cursor:
            p['cursor'] = cursor
        try:
            r = kt._get('/markets', p)
        except Exception as e:
            print(f'    list failed: {e}')
            break
        ms = r.get('markets', [])
        out += ms
        cursor = r.get('cursor')
        if not cursor or not ms:
            break
        time.sleep(0.12)
    return out[:cap]


def preko_quote(series, ticker, ko_ts):
    """Last candle closing at/before kickoff with a live two-sided book."""
    for lookback, interval in ((6 * 3600, 60), (24 * 3600, 60)):
        try:
            r = kt._get(f'/series/{series}/markets/{ticker}/candlesticks',
                        {'start_ts': ko_ts - lookback, 'end_ts': ko_ts,
                         'period_interval': interval})
        except Exception:
            return None
        best = None
        for c in r.get('candlesticks', []):
            if c.get('end_period_ts', 0) > ko_ts:
                continue
            try:
                b = float(c['yes_bid']['close_dollars'])
                a = float(c['yes_ask']['close_dollars'])
            except (KeyError, TypeError, ValueError):
                continue
            # a settled/closed book reads bid 0 / ask 1 -- reject it
            if b <= 0 or a >= 1 or a <= b:
                continue
            best = (b, a)
        if best:
            return best
    return None


def main():
    done = set()
    if os.path.exists(OUT):
        with open(OUT, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                done.add(r['event'])
        print(f'resuming: {len(done)} events already written')

    fh = open(OUT, 'a', newline='', encoding='utf-8')
    w = csv.DictWriter(fh, fieldnames=FIELDS)
    if not done:
        w.writeheader()

    for div, series in series_map():
        ms = settled_markets(series, MAX_PER_SERIES)
        if not ms:
            print(f'{div:5s} {series:26s} 0 settled')
            continue
        by = defaultdict(list)
        for m in ms:
            by[m.get('event_ticker')].append(m)
        wrote = 0
        for ev, mk in by.items():
            if ev in done or len(mk) != 3:
                continue
            ko = mk[0].get('occurrence_datetime') or mk[0].get('expected_expiration_time')
            if not ko:
                continue
            try:
                ko_ts = int(dt.datetime.fromisoformat(
                    ko.replace('Z', '+00:00')).timestamp())
            except Exception:
                continue
            # identify the three legs; the tie leg names itself "Tie"
            legs, tie = {}, None
            for m in mk:
                sub = (m.get('yes_sub_title') or '').strip()
                if sub.lower() in ('tie', 'draw'):
                    tie = m
                else:
                    legs[sub] = m
            if not tie or len(legs) != 2:
                continue
            title = mk[0].get('title', '')
            if ' vs ' not in title:
                continue
            home_nm, away_nm = [t.strip() for t in
                                title.split(' Winner')[0].split(' vs ')[:2]]
            hm = legs.get(home_nm) or next(
                (v for k, v in legs.items() if k and k in home_nm), None)
            am = legs.get(away_nm) or next(
                (v for k, v in legs.items() if k and k in away_nm), None)
            if not hm or not am or hm is am:
                continue
            ftr = ('H' if hm.get('result') == 'yes' else
                   'A' if am.get('result') == 'yes' else
                   'D' if tie.get('result') == 'yes' else None)
            if not ftr:
                continue
            q = {}
            ok = True
            for tag, m in (('home', hm), ('draw', tie), ('away', am)):
                pq = preko_quote(series, m['ticker'], ko_ts)
                time.sleep(0.06)
                if not pq:
                    ok = False
                    break
                q[tag] = pq
            if not ok:
                continue
            mids = [(q[t][0] + q[t][1]) / 2 for t in ('home', 'draw', 'away')]
            s = sum(mids)
            if not (0.80 < s < 1.60):
                continue
            fair = [m / s for m in mids]
            w.writerow({
                'div': div, 'series': series, 'event': ev,
                'kickoff': ko, 'home': home_nm, 'away': away_nm, 'ftr': ftr,
                'k_home_bid': q['home'][0], 'k_home_ask': q['home'][1],
                'k_draw_bid': q['draw'][0], 'k_draw_ask': q['draw'][1],
                'k_away_bid': q['away'][0], 'k_away_ask': q['away'][1],
                'fair_home': round(fair[0], 6), 'fair_draw': round(fair[1], 6),
                'fair_away': round(fair[2], 6)})
            done.add(ev)
            wrote += 1
            if wrote % 25 == 0:
                fh.flush()
                print(f'    {div} ... {wrote}')
        fh.flush()
        print(f'{div:5s} {series:26s} {len(by):4d} events -> {wrote:4d} usable')
    fh.close()
    print('done ->', OUT)


if __name__ == '__main__':
    main()
