#!/usr/bin/env python3
"""
pro_v7 预测生成脚本
策略：按联赛选择 pro_v5_v6 和 pro_v5 中表现更好的版本
- pro_v5: v4_models_serving_current_v5/ (37维特征)
- pro_v5_v6: v4_models_serving_current_v6/ (54维特征)
  → 路由表见 USE_V6 dict
输出: predictions/pro_v7_24h.csv
"""
import os, sys, time, json, warnings, joblib
from pathlib import Path
from datetime import datetime, timezone, timedelta
import numpy as np
import pandas as pd
import requests

warnings.filterwarnings('ignore')
np.random.seed(42)

REPO = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
ODDS_DIR = REPO / 'artifacts' / 'odds_api_history'
PRED_DIR = REPO / 'predictions'
PRED_DIR.mkdir(exist_ok=True)

# pro_v5 (37维) 和 pro_v5_v6 (54维) 模型目录
V5_DIR = REPO / 'artifacts' / 'v4_models_serving_current_v5'
V6_DIR = REPO / 'artifacts' / 'v4_models_serving_current_v6'

# ── 路由表：每个联赛用哪个版本 ──
USE_V6 = {
    'e0': False,   # v5 73.7% > v6 70.2%
    'e1': True,    # v6 68.4% > v5 65.8%
    'e2': False,   # v5 68.6% > v6 64.7%
    'e2n': True,   # v6 71.0% > v5 67.7%
    'd1': True,    # v6 69.1% > v5 64.7%
    'd2': True,    # v6 67.5% = v5 67.5%
    'f1': False,   # v5 75.0% > v6 65.6%
    'f2': False,   # v5 77.8% > v6 59.3%
    'g1': False,   # v5 72.4% > v6 65.5%
    'i1': False,   # v5 60.7% > v6 58.9%
    'i2': True,    # v6 76.6% = v5 76.6%
    'j1': False,   # v5 68.5% > v6 67.4%
    'm1': False,   # v5 73.0% > v6 68.3%
    'n1': True,    # v6 73.7% > v5 71.1%
    'p1': True,    # v6 81.1% > v5 64.9%
    'sp1': True,   # v6 78.4% > v5 75.7%
    'sp2': True,   # v6 75.4% > v5 67.2%
    'tr1': True,   # v6 76.9% = v5 76.9%
    'br1': True,   # v6 86.4% = v5 86.4%
}

LEAGUE_NAME = {
    'e0':'英超','e1':'英甲','e2':'英冠','e2n':'英乙',
    'd1':'德甲','d2':'德乙','f1':'法甲','f2':'法乙',
    'i1':'意甲','i2':'意乙','sp1':'西甲','sp2':'西乙',
    'tr1':'土超','p1':'葡超','g1':'希超',
    'n1':'荷兰甲','j1':'日职联','m1':'美职联','br1':'巴甲',
}

LEAGUE_IDS = {
    'e0':39,'e1':40,'e2':41,'e2n':42,
    'd1':78,'d2':79,'f1':61,'f2':62,
    'i1':135,'i2':136,'sp1':140,'sp2':141,
    'tr1':203,'p1':94,'g1':197,
    'n1':88,'j1':98,'m1':253,'br1':71,
}

API_KEY = os.environ.get('API_FOOTBALL_KEY', '57b64e857ebeffdc156f0c41d43c7894')
BASE = 'https://v3.football.api-sports.io'
HEADERS = {'x-apisports-key': API_KEY}
TZ = timezone(timedelta(hours=8))

# ── 导入训练脚本的特征构建函数 ──
import importlib.util
spec = importlib.util.spec_from_file_location("train_pro_v5_v6", str(REPO / 'scripts' / 'train_pro_v5_v6.py'))
train_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(train_module)

# ── 获取模型文件路径 ──
def get_model_for_league(code):
    use_v6 = USE_V6.get(code, True)
    if use_v6:
        model_file = V6_DIR / f'{code}_pro_v5_v6_model.pkl'
        if model_file.exists():
            return model_file, True
    # fallback: 尝试另一版本
    v5_file = V5_DIR / f'{code}_pro_v5_model.pkl'
    if v5_file.exists():
        return v5_file, False
    v6_file = V6_DIR / f'{code}_pro_v5_v6_model.pkl'
    if v6_file.exists():
        return v6_file, True
    return None, None

def rate_call():
    time.sleep(0.5)

def parse_asian_handicap(bookmakers):
    all_handicaps = {}
    for bm in bookmakers:
        for bet in bm.get('bets', []):
            if bet['name'] != 'Asian Handicap':
                continue
            for v in bet.get('values', []):
                raw = v.get('value', '')
                odd_s = v.get('odd', '')
                if not raw or not odd_s:
                    continue
                try:
                    odd = float(odd_s)
                except:
                    continue
                parts = raw.split()
                if len(parts) < 2:
                    continue
                side, h_str = parts[0], parts[1]
                sign = 1 if h_str.startswith('+') else -1 if h_str.startswith('-') else 1
                h_num = float(h_str.replace('+','').replace('-',''))
                handicap = sign * h_num
                if handicap not in all_handicaps:
                    all_handicaps[handicap] = [None, None]
                if side in ('Home', 'H'):
                    all_handicaps[handicap][0] = odd
                elif side in ('Away', 'A'):
                    all_handicaps[handicap][1] = odd

    valid = [(h, all_handicaps[h][0], all_handicaps[h][1])
             for h in all_handicaps
             if all_handicaps[h][0] is not None and all_handicaps[h][1] is not None]
    if not valid:
        return None, None, None

    def balance_score(item):
        h, ho, ao = item
        if ho <= 1 or ao <= 1:
            return 999
        prob_home = 1.0 / ho
        prob_away = 1.0 / ao
        return abs(prob_home - prob_away)

    return min(valid, key=balance_score)

def parse_match_winner(bookmakers):
    for bm in bookmakers:
        for bet in bm.get('bets', []):
            if bet['name'] == 'Match Winner':
                vals = {v['value']: float(v['odd']) for v in bet['values']}
                return vals.get('Home', 2.5), vals.get('Draw', 3.2), vals.get('Away', 2.8)
    return None, None, None

def generate_predictions(hours=24):
    now = datetime.now(TZ)
    start_dt = now - timedelta(hours=hours)
    end_dt = now + timedelta(days=7)

    results = []
    session = requests.Session()
    session.headers.update(HEADERS)

    for code in sorted(LEAGUE_IDS.keys()):
        model_file, is_v6 = get_model_for_league(code)
        if model_file is None:
            print(f"  [{code}] 无模型文件，跳过")
            continue

        try:
            model_data = joblib.load(model_file)
        except Exception as e:
            print(f"  [{code}] 加载失败: {e}")
            continue

        scaler = model_data['scaler']
        model = model_data['model']
        acc = model_data.get('acc', 0)
        n_features = model_data.get('n_features', scaler.n_features_in_)
        league_name = LEAGUE_NAME.get(code, code)
        version = 'v6' if is_v6 else 'v5'

        print(f"  [{code}] pro_{version} | acc={acc:.1%} | n_features={n_features}")

        # 为该联赛构建球队缓存（预测时用空缓存，特征用默认值）
        team_cache = {}
        h2h_cache = {}
        league_home_rate_global = 0.415  # 全局主场胜率

        rate_call()
        try:
            r = session.get(f'{BASE}/fixtures', params={
                'league': LEAGUE_IDS[code], 'season': 2025,
                'from': start_dt.strftime('%Y-%m-%d'),
                'to': end_dt.strftime('%Y-%m-%d'),
                'timezone': 'Asia/Shanghai',
            }, timeout=30)
            fixtures = r.json().get('response', [])
        except Exception as e:
            print(f"  [{code}] 获取赛程失败: {e}")
            continue

        fix_count = 0
        for fix in fixtures:
            fi = fix['fixture']
            teams = fix['teams']
            kickoff = datetime.fromisoformat(fi['date'].replace('Z','+00:00')).astimezone(TZ)
            if kickoff < start_dt or kickoff > end_dt:
                continue

            home = teams['home']['name']
            away = teams['away']['name']
            fid = fi['id']

            rate_call()
            try:
                odd_resp = session.get(f'{BASE}/odds', params={
                    'fixture': fid, 'timezone': 'Asia/Shanghai'
                }, timeout=8)
                odds_data = odd_resp.json().get('response', [])
            except:
                continue

            if not odds_data:
                continue

            bookmakers = odds_data[0].get('bookmakers', [])
            mw = parse_match_winner(bookmakers)
            ah_h, ah_home_odd, ah_away_odd = parse_asian_handicap(bookmakers)

            if ah_h is None or ah_home_odd is None or ah_away_odd is None:
                continue

            # 使用训练脚本的 build_features 函数（与训练时完全一致）
            try:
                feat = train_module.build_features(
                    float(ah_h), float(ah_home_odd), float(ah_away_odd),
                    home, away, team_cache, h2h_cache, league_home_rate_global
                )
                feat_arr = np.array([feat], dtype=float)
                feat_s = scaler.transform(feat_arr)
                proba = model.predict_proba(feat_s)[0]
                prob_up = float(proba[1] * 100)
                prob_down = float(proba[0] * 100)
            except Exception as e:
                print(f"  [{code}] fid={fid} 特征/预测失败: {e}")
                prob_up, prob_down = 50.0, 50.0

            conf = max(prob_up, prob_down)
            pred = '主队' if prob_up >= prob_down else '客队'

            if conf >= 72:
                rec_level = '★★★ 强烈推荐'
            elif conf >= 62:
                rec_level = '★★ 推荐'
            elif conf >= 52:
                rec_level = '★ 谨慎推荐'
            else:
                rec_level = '✗ 不建议'

            if ah_h < 0:
                hand = f"主队让{abs(ah_h):.2f}"
            elif ah_h > 0:
                hand = f"客队让{ah_h:.2f}"
            else:
                hand = "平手"

            results.append({
                '联赛': f"{code.upper()} {league_name}",
                '开赛时间': kickoff.strftime('%m-%d %H:%M'),
                '主队': home, '客队': away,
                '让球说明': hand,
                '预测方向': pred,
                '上盘概率': round(prob_up, 1),
                '下盘概率': round(prob_down, 1),
                '置信度': round(conf, 1),
                '推荐等级': rec_level,
                '模型版本': f'pro_{version}',
                '历史准确率': round(acc * 100, 1),
                '生成时间': datetime.now(TZ).strftime('%Y-%m-%d %H:%M'),
            })
            fix_count += 1

        print(f"    → {fix_count} 场预测")

    df = pd.DataFrame(results)
    return df

if __name__ == '__main__':
    print("=" * 60)
    print("pro_v7 预测生成（v5/v6 联赛级最优路由）")
    print("=" * 60)
    df = generate_predictions(hours=24)
    csv_path = PRED_DIR / 'pro_v7_24h.csv'
    xlsx_path = PRED_DIR / 'pro_v7_24h.xlsx'
    df.to_csv(csv_path, index=False, encoding='utf-8-sig')
    try:
        df.to_excel(xlsx_path, index=False)
    except:
        pass
    print(f"\n共生成 {len(df)} 条预测 → {csv_path}")
    if len(df) > 0:
        high_conf = df[df['置信度'] >= 62].sort_values('置信度', ascending=False)
        print(f"\n★★ 推荐（共 {len(high_conf)} 条）：")
        for _, row in high_conf.iterrows():
            print(f"  {row['联赛']} {row['开赛时间']} {row['主队']} vs {row['客队']} | {row['让球说明']} | 押{row['预测方向']} {row['置信度']}% | {row['推荐等级']} | 历史{row['历史准确率']}%")
