#!/usr/bin/env python3
"""
pro_v5 预测生成脚本
使用 artifacts/v4_models_serving_current_v5/ 中的模型生成预测
"""
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 / 'artifacts' / 'v4_models_serving_current_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,
}

def rate_call():
    time.sleep(0.5)

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

    # 选择两边赔率最均衡的让球线
    # 均衡程度 = |prob_home - prob_away|，越小越均衡
    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, league_id in LEAGUE_IDS.items():
        model_file = OUT_DIR / f'{code}_pro_v5_model.pkl'
        if not model_file.exists():
            continue

        model_data = joblib.load(model_file)
        scaler = model_data['scaler']
        model = model_data['model']
        acc = model_data.get('acc', 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:
            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']

            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, mw[0], mw[2])

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

            # 构建特征
            p_home = 1.0 / ah_home_odd if ah_home_odd > 1 else 0.5
            p_away = 1.0 / ah_away_odd if ah_away_odd > 1 else 0.5
            prob_diff = p_home - p_away
            prob_sum = p_home + p_away
            water_home = ah_home_odd - (prob_sum / p_home) if p_home > 0 else 0
            water_away = ah_away_odd - (prob_sum / p_away) if p_away > 0 else 0

            feat = np.array([[
                float(ah_h), p_home, p_away, prob_diff,
                water_home, water_away,
                float(ah_h) * prob_diff,
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0,
                0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
            ]])

            try:
                feat_s = scaler.transform(feat)
                proba = model.predict_proba(feat_s)[0]
                prob_up = float(proba[1] * 100)
                prob_down = float(proba[0] * 100)
            except:
                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 = '★★★ 强烈推荐'
            elif conf >= 62:
                rec = '★★ 推荐'
            elif conf >= 52:
                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({
                '联赛代码': code,
                '联赛': code.upper(),
                '开赛时间': kickoff.strftime('%m-%d %H:%M'),
                '主队': home, '客队': away,
                '让球数': ah_h,
                '预测方向': pred,
                '上盘概率': round(prob_up, 1),
                '下盘概率': round(prob_down, 1),
                '置信度': round(conf, 1),
                '推荐等级': rec,
                '模型准确率': round(acc * 100, 1),
                '让球说明': hand,
                '预测生成时间': datetime.now(TZ).strftime('%Y-%m-%d %H:%M'),
            })

    df = pd.DataFrame(results)
    return df

if __name__ == '__main__':
    print("生成 pro_v5 24h 预测...")
    df = generate_predictions(hours=24)
    csv_path = PRED_DIR / f'pro_v5_24h.csv'
    xlsx_path = PRED_DIR / f'pro_v5_24h.xlsx'
    df.to_csv(csv_path, index=False, encoding='utf-8')
    try:
        df.to_excel(xlsx_path, index=False)
    except:
        pass
    print(f"生成 {len(df)} 条预测")
    if len(df) > 0:
        print(df[['联赛','主队','客队','让球数','预测方向','置信度']].head(10).to_string())
