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

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.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 build_feature_vector(ah_h, ah_home_odd, ah_away_odd):
    """构建与训练时相同的特征向量（20个基础特征 + 8个近期rec特征 + 18个新特征 = 46个）"""
    try:
        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
    except:
        p_home = p_away = 0.5

    prob_sum = p_home + p_away
    prob_diff = 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

    rec = [0.5] * 20  # 10个主队 + 10个客队近期胜率

    feat = [
        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,  # 6个差值特征
        0.0, 0.0, 0.0, 0.0,            # 4个得失球特征
    ] + rec

    # 新增特征（部分默认值，因为预测时无历史数据）
    feat += [
        0.5, 0.5,        # h2h_wr5, h2h_wr10
        0.5,             # league_home_adj
        0.0,             # goal_exp_diff
        0.0, 0.0, 0.0, 0.0,  # streak features
        0.0, 0.0, 0.0,   # interaction features
        0.0, 0.0,        # home_advantage, wr_strength_diff
        0.5, 0.5,        # h_home_wr5, a_away_wr5
        0.0, 0.0,        # 进攻/防守综合指标
    ]
    return np.array([feat], dtype=float)


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_v6_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)
        n_features = model_data.get('n_features', 46)

        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)

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

            feat = build_feature_vector(ah_h, ah_home_odd, ah_away_odd)

            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,
                '联赛': LEAGUE_NAMES.get(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'),
            })

    return pd.DataFrame(results)


if __name__ == '__main__':
    print("生成 pro_v5_v6 24h 预测...")
    df = generate_predictions(hours=24)
    csv_path = PRED_DIR / f'pro_v5_v6_24h.csv'
    xlsx_path = PRED_DIR / f'pro_v5_v6_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())
