#!/usr/bin/env python3
"""
ah_1year_v4 预测生成脚本 (修复版)
- 从 /fixtures 获取比赛列表
- 对每个比赛调用 /odds 端点获取真实亚洲让球盘赔率
- 使用 Pinnacle/bookmaker 的 Asian Handicap 数据
- 按 ah_1year_v4 训练时的特征结构构建 26 维特征向量
"""
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'
OUT_DIR = REPO / 'models' / 'ah_1year_v4'
PRED_DIR = REPO / 'predictions'
PRED_DIR.mkdir(exist_ok=True)

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))

LEAGUE_IDS = {
    'e0':39,'e1':40,'e2':41,'e2n':42,'e3':183,
    'd1':78,'d2':79,'f1':61,'f2':62,
    'i1':135,'i2':136,'sp1':140,'sp2':141,
    'tr1':203,'p1':94,'b1':144,'g1':197,
    'n1':88,'n2':89,'sc1':179,'cz1':345,
    'a1':188,'r1':235,'br1':71,'m1':253,'j1':98,
}

def rate_call():
    time.sleep(0.6)


def parse_asian_handicap(bookmakers, home_1x2=None, away_1x2=None):
    """
    选择上下盘赔率最接近平衡的让球线。
    原理：在均衡的让球线下，两边赔率倒数的概率应该最接近50%
    """
    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):
    """解析独赢赔率，返回 (home_1x2, draw_1x2, away_1x2)"""
    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', None), vals.get('Draw', None), vals.get('Away', None)
    return None, None, None


def build_features_v4(ah_h, home_odd, away_odd):
    """
    构建 ah_1year_v4 模型的 26 维特征向量（与训练脚本 build_features_v4 一致）
    由于预测时无球队历史数据，历史特征用中性值填充。
    
    特征顺序（与 train_ah_1year_v4.py 完全一致）：
      0: ahh_f          (AH 让球数)
      1: norm_home      (B365 归一化主队概率)
      2: norm_away      (B365 归一化客队概率)
      3: odds_gap       (|norm_home - norm_away|)
      4: ratio_h        (主队/客队 B365 赔率比)
      5: ratio_a        (客队/主队 B365 赔率比)
      6: adj_home_prob  (让球调整后主队概率)
      7: h_hw           (主队主场胜率均值, 缺省0.5)
      8: a_aw           (客队客场胜率均值, 缺省0.5)
      9: h_aw           (主队客场胜率均值, 缺省0.5)
     10: a_hw           (客队主场胜率均值, 缺省0.5)
     11: h_dr - a_dr    (主客队平局率差, 缺省0)
     12: gf_diff        (主队进球-客队失球均值差, 缺省0)
     13: tot_avg        (场均总进球均值, 缺省2.5)
     14: (h_hw - a_hw) * ahh_f  (交互项, 缺省0)
     15: (h_dr + a_dr) / 2      (平均平局率, 缺省0.5)
     16: odds_gap * abs(ahh_f)  (赔率差距×让球绝对值)
     17-26: 近10场主队主场/客队客场胜率 (10个值, 缺省0.5)
    """
    try:
        ho = float(home_odd)
        ao = float(away_odd)
        ratio_h = ho / ao if ao > 0 else 1.0
        ratio_a = ao / ho if ho > 0 else 1.0
        total_odds = (1.0/ho) + (1.0/ao)
        norm_home = (1.0/ho) / total_odds if total_odds > 0 else 0.5
        norm_away = (1.0/ao) / total_odds if total_odds > 0 else 0.5
        odds_gap = abs(norm_home - norm_away)
    except:
        ratio_h = ratio_a = norm_home = norm_away = odds_gap = 1.0
        ho = ao = 2.0

    # 用 AH 赔率推导隐含概率来构建 adj_home_prob（与训练逻辑一致）
    try:
        ahh_f = float(ah_h)
        adj_home_prob = max(0.1, min(0.9, norm_home + ahh_f * 0.1))
    except:
        ahh_f = 0.0
        adj_home_prob = norm_home

    # 中性历史特征（预测时不可用）
    h_hw = a_aw = h_aw = a_hw = 0.5
    h_dr = a_dr = 0.3
    gf_diff = 0.0
    tot_avg = 2.5

    recent = [0.5] * 10  # 近10场填充中性值

    return [
        ahh_f,
        norm_home, norm_away,
        odds_gap,
        ratio_h, ratio_a,
        adj_home_prob,
        h_hw, a_aw,
        h_aw, a_hw,
        h_dr - a_dr,
        gf_diff,
        tot_avg,
        (h_hw - a_hw) * ahh_f,
        (h_dr + a_dr) / 2.0,
        odds_gap * abs(ahh_f),
    ] + recent


def generate_predictions(hours=24):
    """生成 ah_1year_v4 预测"""
    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, league_id in LEAGUE_IDS.items():
        model_dir = OUT_DIR / code
        model_file = model_dir / 'optimized_pipeline.pkl'
        cfg_file = model_dir / 'optimized_config.json'
        if not model_file.exists() or not cfg_file.exists():
            continue

        model_data = joblib.load(model_file)
        scaler = model_data['scaler']
        model = model_data['model']
        cfg = json.load(open(cfg_file))
        acc = cfg.get('accuracy', 0)

        # 获取比赛列表
        rate_call()
        r = session.get(f'{BASE}/fixtures', params={
            'league': league_id, 'season': 2025,
            'from': start_dt.strftime('%Y-%m-%d'),
            'to': end_dt.strftime('%Y-%m-%d'),
            'timezone': 'Asia/Shanghai',
        }, timeout=30)
        if r.status_code != 200:
            continue
        fixtures = r.json().get('response', [])
        if not fixtures:
            # 重试一次
            rate_call()
            r = session.get(f'{BASE}/fixtures', params={
                'league': league_id, 'season': 2025,
                'from': start_dt.strftime('%Y-%m-%d'),
                'to': end_dt.strftime('%Y-%m-%d'),
                'timezone': 'Asia/Shanghai',
            }, timeout=30)
            if r.status_code == 200:
                fixtures = r.json().get('response', [])
        if not fixtures:
            continue

        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']

            # 调用 /odds 端点获取真实 AH 赔率
            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 Exception:
                continue

            if not odds_data:
                continue

            bookmakers = odds_data[0].get('bookmakers', [])

            # 解析 1X2 赔率（用于判断主客队热门）
            mw = parse_match_winner(bookmakers)
            home_1x2, draw_1x2, away_1x2 = mw[0], mw[1], mw[2]

            # 解析亚洲让球盘
            ah_h, ah_home_odd, ah_away_odd = parse_asian_handicap(bookmakers, home_1x2, away_1x2)
            if ah_h is None or ah_home_odd is None or ah_away_odd is None:
                continue

            # 构建 26 维特征向量
            feat = build_features_v4(ah_h, ah_home_odd, ah_away_odd)

            try:
                feat_s = scaler.transform([feat])
                prob = model.predict_proba(feat_s)[0][1]  # [prob_0, prob_1] -> 取 index=1
                pred = '主队' if prob >= 0.5 else '客队'
                confidence = abs(prob - 0.5) * 2 * 100
            except Exception:
                # 降级：用 AH 赔率直接计算概率
                try:
                    home_prob = (1.0/ah_away_odd) / ((1.0/ah_home_odd) + (1.0/ah_away_odd))
                    prob = home_prob if ah_h < 0 else (1.0 - home_prob)
                    pred = '主队' if prob >= 0.5 else '客队'
                    confidence = abs(prob - 0.5) * 2 * 100
                except:
                    continue

            if confidence < 20:
                pred = '不确定'

            # 推荐等级（按置信度）
            if confidence >= 35:
                rec = '★★★ 强烈推荐'
            elif confidence >= 25:
                rec = '★★ 推荐'
            elif confidence >= 15:
                rec = '★ 谨慎推荐'
            else:
                rec = '✗ 不建议'

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

            results.append({
                'fixture_id': fid,
                '联赛代码': code,
                '联赛': code.upper(),
                '开赛时间': kickoff.strftime('%m-%d %H:%M'),
                '主队': home,
                '客队': away,
                '让球数': ah_h,
                '让球说明': hand,
                '预测方向': pred,
                '预测概率': f'{prob:.1%}',
                '置信度': f'{confidence:.0f}%',
                '推荐等级': rec,
                '模型准确率': f'{acc:.1%}',
                '预测生成时间': datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S'),
            })

    df = pd.DataFrame(results)
    return df


def save_outputs(df, hours=24):
    csv_path = PRED_DIR / f'ah1_v4_{hours}h.csv'
    xlsx_path = PRED_DIR / f'ah1_v4_{hours}h.xlsx'
    df.to_csv(csv_path, index=False, encoding='utf-8')
    try:
        df.to_excel(xlsx_path, index=False)
    except:
        pass
    return csv_path, xlsx_path


if __name__ == '__main__':
    hours = int(sys.argv[1]) if len(sys.argv) > 1 else 24
    print(f"生成 ah_1year_v4 {hours}h 预测...")
    df = generate_predictions(hours)
    print(f"生成 {len(df)} 条预测")
    if len(df):
        csv_path, xlsx_path = save_outputs(df, hours)
        print(f"已保存: {csv_path}")
        print("\n前10条预测：")
        print(df[['联赛','主队','客队','让球说明','预测方向','置信度','推荐等级']].head(10).to_string())
    else:
        print("⚠️ 未生成任何预测，检查 API 连通性和赔率数据可用性")
