#!/usr/bin/env python3
"""
ah_1year_v5 预测生成脚本
- 从 /fixtures 获取比赛列表
- 对每个比赛调用 /odds 端点获取真实亚洲让球盘赔率
- 使用 ah_1year_v5 模型预测
- 输出到 predictions/ah1_v5_{hours}h.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')
OUT_DIR = REPO / 'models' / 'ah_1year_v5'
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,
}

CHINESE_LEAGUE_NAMES = {
    'e0':'英超','e1':'英甲','e2':'英冠','e2n':'英乙','e3':'英格兰第五级',
    'd1':'德甲','d2':'德乙','f1':'法甲','f2':'法乙',
    'i1':'意甲','i2':'意乙','sp1':'西甲','sp2':'西乙',
    'tr1':'土超','p1':'葡超','b1':'比甲','g1':'希腊超',
    'n1':'荷兰甲','n2':'荷兰乙','sc1':'苏超','cz1':'捷甲',
    'a1':'澳超','r1':'俄超','br1':'巴甲','m1':'美职联','j1':'日职联',
}


def rate_call():
    time.sleep(0.6)


def parse_asian_handicap(bookmakers):
    """解析亚洲让球盘，返回 (ah_h, home_odd, away_odd)"""
    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 _m(v, n=20):
    vals = v[-n:] if len(v) >= n else v
    return sum(vals)/len(vals) if vals else 0.5

def _arr5(v):
    return ([0.5]*5 + list(v))[-5:]


def build_features_v5_pred(ah_h, home_odd, away_odd):
    """
    构建 ah_1year_v5 模型的 32 维特征向量
    
    特征顺序（32维 = 22静态 + 10近期）：
    0: ahh_f              (让球数)
    1: norm_home          (归一化主队概率)
    2: norm_away          (归一化客队概率)
    3: odds_gap           (|norm_home - norm_away|)
    4: odds_dev_h          (B365/Pinn偏离，缺省1.0)
    5: odds_dev_a          (B365/Pinn偏离，缺省1.0)
    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: h2h_hw_5           (H2H近5场主队胜率, 缺省0.5)
    15: h2h_hw_10          (H2H近10场主队胜率, 缺省0.5)
    16: h2h_gf_diff        (H2H近5场得失球差, 缺省0)
    17: goal_trend          (近期得失球趋势, 缺省0)
    18: interaction_5       (让球×近5场胜率差, 缺省0)
    19: (h_dr + a_dr) / 2  (平均平局率, 缺省0.3)
    20: odds_gap * abs(ahh_f)
    21: league_home_adv     (联赛主队优势, 缺省0.5)
    22-26: recent_5_home    (主队近5场主场胜率, 缺省0.5)
    27-31: recent_5_away    (客队近5场客场胜率, 缺省0.5)
    """
    try:
        ho = float(home_odd)
        ao = float(away_odd)
        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:
        norm_home = norm_away = odds_gap = 0.5
        ho = ao = 2.0

    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
    h2h_hw_5 = h2h_hw_10 = 0.5
    h2h_gf_diff = 0.0
    goal_trend = 0.0
    interaction_5 = 0.0
    league_home_adv = 0.5
    recent = [0.5] * 10

    return [
        ahh_f, norm_home, norm_away, odds_gap,
        1.0, 1.0, adj_home_prob,
        h_hw, a_aw, h_aw, a_hw,
        h_dr - a_dr, gf_diff, tot_avg,
        h2h_hw_5, h2h_hw_10, h2h_gf_diff,
        goal_trend, interaction_5,
        (h_dr + a_dr) / 2,
        odds_gap * abs(ahh_f),
        league_home_adv,
    ] + recent


def generate_predictions(hours=24):
    """生成 ah_1year_v5 预测"""
    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']
        threshold = model_data.get('threshold', 0.5)
        cfg = json.load(open(cfg_file))
        acc = cfg.get('accuracy', 0)
        league_name = CHINESE_LEAGUE_NAMES.get(code, code)

        # 获取比赛列表
        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', [])
            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

            # 构建特征
            feat = build_features_v5_pred(ah_h, ah_home_odd, ah_away_odd)

            try:
                feat_s = scaler.transform([feat])
                prob = model.predict_proba(feat_s)[0][1]
                pred = '主队' if prob >= threshold else '客队'
                confidence = abs(prob - 0.5) * 2 * 100
            except Exception:
                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,
                '联赛': CHINESE_LEAGUE_NAMES.get(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_v5_{hours}h.csv'
    xlsx_path = PRED_DIR / f'ah1_v5_{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_v5 {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 连通性和赔率数据可用性")
