#!/usr/bin/env python3
"""Odds API 历史赔率采集 — 赛季感知周采样版

策略：每个赛季（5月-次年4月）期间每周采样一次，
      用已收集的俄超数据验证，只取有比分的记录。

配额：4 leagues × 12月/年 × 4周 × 10 = ~1920 units/年
"""
import time, os
from pathlib import Path
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
import requests

REPO_ROOT = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
OUT_DIR = REPO_ROOT / 'artifacts' / 'odds_api_history'
OUT_DIR.mkdir(parents=True, exist_ok=True)

API_KEY = os.environ.get('ODDS_API_KEY', '08e1ca367452b446f7bee50bbc9d0fc1')
BASE = 'https://api.the-odds-api.com'
MARKETS = 'spreads,h2h,totals'
REGIONS = 'eu'

# 各联赛赛季月份（粗略）
SEASON_MONTHS = {
    'r1': list(range(7, 13)) + list(range(1, 6)),  # 俄超: 7-12月, 1-5月
    'a1': list(range(10, 13)) + list(range(1, 5)),  # 澳超: 10-12月, 1-4月
    'br1': list(range(4, 9)),                          # 巴甲: 4-8月
    'm1': list(range(2, 11)),                         # 美职联: 2-9月
    'n1':  ('soccer_netherlands_eredivisie', '荷兰甲'),
}

LEAGUES = {
    'r1':  ('soccer_russia_premier_league',    '俄超'),
    'a1':  ('soccer_australia_aleague',         '澳超'),
    'br1': ('soccer_brazil_campeonato',         '巴甲'),
    'm1':  ('soccer_usa_mls',                  '美职联'),
    'n1':  ('soccer_netherlands_eredivisie',    '荷兰甲'),
}

# 各联赛最早可用快照（从 Odds API 官方文档/探测确认）
EARLIEST = {
    'r1':  '2020-06-06',
    'a1':  '2020-06-24',
    'br1': '2020-07-28',
    'm1':  '2020-06-27',
    'n1':  '2020-09-04',
}


def call_hist(sport_key, date_str):
    ts = f'{date_str}T18:00:00Z'
    r = requests.get(
        f'{BASE}/v4/sports/{sport_key}/odds-history',
        params={'apiKey': API_KEY, 'sport': sport_key,
                'regions': REGIONS, 'markets': MARKETS, 'date': ts},
        timeout=30
    )
    r.raise_for_status()
    return r.json()


def parse_match(m, seen, all_rows):
    home = m.get('home_team', '')
    away = m.get('away_team', '')
    commence = m.get('commence_time') or ''
    key = (home, away, commence)
    if key in seen:
        return
    seen.add(key)

    scores = m.get('scores') or []
    fthg = ftag = None
    if scores:
        try:
            fthg = int(scores[0]['score']['home'])
            ftag = int(scores[0]['score']['away'])
        except Exception:
            pass

    # 亚盘
    spreads, h2h, totals = [], [], []
    for bm in m.get('bookmakers', []):
        for mkt in bm.get('markets', []):
            k = mkt.get('key')
            if k == 'spreads':
                for o in mkt.get('outcomes', []):
                    spreads.append({'team': o.get('name', ''), 'pt': o.get('point'), 'pr': o.get('price')})
            elif k == 'h2h':
                for o in mkt.get('outcomes', []):
                    h2h.append({'name': o.get('name', ''), 'pr': o.get('price')})
            elif k == 'totals':
                for o in mkt.get('outcomes', []):
                    totals.append({'name': o.get('name', ''), 'pt': o.get('point'), 'pr': o.get('price')})

    all_rows.append({
        'home': home, 'away': away, 'commence': commence,
        'Date': commence[:10] if commence else None,
        'Time': commence[11:16] if commence else None,
        'FTHG': fthg, 'FTAG': ftag,
        'spreads': spreads, 'h2h': h2h, 'totals': totals,
    })


def process(r):
    sr, hr, tr = r['spreads'], r['h2h'], r['totals']
    home, away = r['home'], r['away']

    # 亚盘
    pg = {}
    for s in sr:
        if s['pt'] is not None and s['pr'] and s['pr'] > 1:
            pt = round(float(s['pt']), 2)
            pg.setdefault(pt, {'home': [], 'away': []})
            if s['team'] == home:
                pg[pt]['home'].append(float(s['pr']))
            elif s['team'] == away:
                pg[pt]['away'].append(float(s['pr']))
    if pg:
        best = min(pg, key=lambda x: abs(x))
        ahh = round(float(np.mean(pg[best]['home'])), 2) if pg[best]['home'] else np.nan
        aha = round(float(np.mean(pg[best]['away'])), 2) if pg[best]['away'] else np.nan
    else:
        best, ahh, aha = 0.0, np.nan, np.nan

    # h2h
    ho = [float(h['pr']) for h in hr if h['name'] == home and h.get('pr', 0) > 1]
    do = [float(h['pr']) for h in hr if h['name'] == 'Draw' and h.get('pr', 0) > 1]
    ao = [float(h['pr']) for h in hr if h['name'] == away and h.get('pr', 0) > 1]

    # 大小球
    ou = {}
    for t in tr:
        if t['pt'] is not None and t['pr'] and t['pr'] > 1:
            ln = round(float(t['pt']), 2)
            nm = (t['name'] or '').lower()
            ou.setdefault(ln, {'over': [], 'under': []})
            if 'over' in nm:
                ou[ln]['over'].append(float(t['pr']))
            else:
                ou[ln]['under'].append(float(t['pr']))
    if ou:
        bline = min(ou, key=lambda x: abs(x - 2.5))
        ao2 = round(float(np.mean(ou[bline]['over'])), 2) if ou[bline]['over'] else np.nan
        au2 = round(float(np.mean(ou[bline]['under'])), 2) if ou[bline]['under'] else np.nan
    else:
        bline, ao2, au2 = np.nan, np.nan, np.nan

    fthg, ftag = r['FTHG'], r['FTAG']
    ftr = None
    if fthg is not None and ftag is not None:
        ftr = 'H' if fthg > ftag else ('A' if fthg < ftag else 'D')

    return {
        'Date': r['Date'], 'Time': r['Time'],
        'HomeTeam': home, 'AwayTeam': away,
        'FTHG': fthg, 'FTAG': ftag, 'FTR': ftr,
        'AHh': best, 'B365AHH': ahh, 'B365AHA': aha,
        'AvgH': round(float(np.mean(ho)), 2) if ho else np.nan,
        'AvgD': round(float(np.mean(do)), 2) if do else np.nan,
        'AvgA': round(float(np.mean(ao)), 2) if ao else np.nan,
        'OU_Line': bline,
        'AvgOverOdds': ao2, 'AvgUnderOdds': au2,
        'commence': r['commence'],
    }


def collect(code, sport_key, name, start_str):
    print(f'\n[{"="*50}]')
    print(f'[{code}] {name} | {sport_key}')
    print(f'从 {start_str} 到今天 | 赛季内每周采样一次')

    seen = set()
    all_rows = []
    cur = datetime.strptime(start_str, '%Y-%m-%d')
    end = datetime.now()
    reqs = 0
    quota = 0
    last = None

    while cur <= end:
        y, m, d = cur.year, cur.month, cur.day
        in_season = m in SEASON_MONTHS.get(code, [])

        if in_season:
            # 赛季内每周一和周四采样（覆盖大部分比赛）
            if d % 7 in (1, 4):  # 每周一和周四
                ds = cur.strftime('%Y-%m-%d')
                try:
                    resp = call_hist(sport_key, ds)
                    reqs += 1
                    quota += 10
                    n = len(resp.get('data', []))
                    ts = resp.get('timestamp', '?')
                    if n > 0:
                        for m_data in resp['data']:
                            parse_match(m_data, seen, all_rows)
                        if ds != last:
                            scored = sum(1 for r in all_rows if r['FTHG'] is not None)
                            print(f'  {ds} ts={ts[:10]} +{n} total={len(all_rows)} scored={scored} req={reqs}')
                            last = ds
                except Exception as e:
                    print(f'  {ds} ERROR: {e}')

        cur += timedelta(days=1)
        time.sleep(0.25)

        if reqs > 2000:
            print(f'  上限2000req，停止')
            break

    print(f'  请求:{reqs} 配额:{quota} 比赛:{len(all_rows)}')

    if not all_rows:
        return pd.DataFrame(), quota

    df = pd.DataFrame([process(r) for r in all_rows])
    df = df.sort_values('Date').reset_index(drop=True)

    # 只保留有比分的（已完成比赛）
    df_done = df[df['FTHG'].notna()].reset_index(drop=True)

    out_all = OUT_DIR / f'{code}_odds_api_all.csv'
    out_done = OUT_DIR / f'{code}_odds_api_done.csv'
    df.to_csv(out_all, index=False, encoding='utf-8-sig')
    df_done.to_csv(out_done, index=False, encoding='utf-8-sig')

    print(f'  [保存] 全部={len(df)}场 -> {out_all.name}')
    print(f'  [保存] 已完成={len(df_done)}场 -> {out_done.name}')
    if len(df_done) > 0:
        print(f'  日期范围: {df_done["Date"].min()} ~ {df_done["Date"].max()}')
        print(f'  样本:\n{df_done[["Date","HomeTeam","AwayTeam","FTHG","FTAG","AHh","B365AHH"]].head(3).to_string(index=False)}')

    return df_done, quota


def main():
    print('=== Odds API 历史赔率采集 (赛季感知周采样版) ===')
    print(f'Key: {API_KEY[:8]}... | regions=eu | markets=spreads,h2h,totals')
    print(f'配额: 4 leagues × 赛季24周 × 2次/周 × 10 = ~1920 units/年')

    total_q = 0
    for code, (sport, name) in [("n1", LEAGUES["n1"])]:  # n1 only
        start = EARLIEST.get(code, '2020-01-01')
        try:
            _, q = collect(code, sport, name, start)
            total_q += q
        except Exception as e:
            print(f'ERROR {code}: {e}')
            import traceback
            traceback.print_exc()

    print(f'\n=== 完成 | 配额消耗: {total_q} units ===')
    for f in sorted(OUT_DIR.glob('*_done.csv')):
        df = pd.read_csv(f)
        print(f'  {f.name}: {len(df)}场 AH有={int(df["B365AHH"].notna().sum())}')


if __name__ == '__main__':
    main()
