#!/usr/bin/env python3
"""
A预测 - 真实AH让球版（修正版）
使用API返回的真实AH让球数+AH赔率计算让球后主队的实际胜率
"""
import joblib, numpy as np, pandas as pd, requests
from pathlib import Path
from datetime import datetime, timedelta, timezone
import warnings
warnings.filterwarnings('ignore')

REPO_ROOT = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
MODELS_DIR = REPO_ROOT / 'artifacts' / 'v4_models_serving_pro_v8'
PRED_DIR = REPO_ROOT / 'predictions'
API_KEY = '57b64e857ebeffdc156f0c41d43c7894'
BASE_URL = 'https://v3.football.api-sports.io'
BEIJING_TZ = timezone(timedelta(hours=8))
SESSION = requests.Session()
SESSION.headers.update({'x-apisports-key': API_KEY})

LEAGUE_IDS = {
    'e0':39,'e1':40,'d1':78,'d2':79,'e2n':42,'e3':43,'f1':61,'f2':62,
    'i1':135,'sp1':140,'sp2':141,'tr1':203,'p1':94,'b1':145,
    'g1':197,'n1':88,'n2':89,'sc1':180,'cz1':345,'a1':188,
    'r1':235,'br1':71,'m1':253,'j1':98,
}
LEAGUE_NAMES = {
    'e0':'英超','e1':'英冠','d1':'德甲','d2':'德乙','e2n':'荷乙','e3':'英乙',
    'f1':'法甲','f2':'法乙','i1':'意甲','sp1':'西甲','sp2':'西乙',
    'tr1':'土超','p1':'葡超','b1':'比甲','g1':'希腊超','n1':'挪超','n2':'挪甲',
    'sc1':'苏超','cz1':'捷克甲','a1':'澳超','r1':'俄超','br1':'巴甲','m1':'美职联','j1':'日职联',
}

def parse_asian_handicap(bookmakers, home_1x2=None, away_1x2=None):
    """
    解析AH让球，选择上下盘赔率最接近平衡的让球线。
    原理：在均衡的让球线下，两边赔率倒数的概率应该最接近50%
    """
    all_h = {}
    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
                h_num = float(h_str.replace('+','').replace('-',''))
                handicap = sign * h_num
                if handicap not in all_h:
                    all_h[handicap] = [None, None]
                # Home给出h时(负值)，Home赔率在第一位置；Away给出h时(正值)，Away赔率在第一位置
                if side in ('Home', 'H'):
                    all_h[handicap][0] = odd
                elif side in ('Away', 'A'):
                    all_h[handicap][1] = odd

    valid = [(h, ho, ao) for h, (ho, ao) in all_h.items()
             if ho is not None and ao 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 calc_ah_prob(home_ah_odd, away_ah_odd):
    """从AH赔率计算让球后主队胜率"""
    if home_ah_odd and away_ah_odd and home_ah_odd > 1 and away_ah_odd > 1:
        fair_home = 1 / home_ah_odd
        fair_away = 1 / away_ah_odd
        total = fair_home + fair_away
        return fair_home / total * 100, fair_away / total * 100
    return None, None

def recommend_level(conf):
    if conf >= 72: return '★★★ 强烈推荐', 3
    if conf >= 62: return '★★ 推荐', 2
    if conf >= 52: return '★ 谨慎推荐', 1
    return '✗ 不建议', 0

now = datetime.now(BEIJING_TZ)
end = now + timedelta(hours=30)
rows = []

print(f"预测窗口: {now.strftime('%m-%d %H:%M')} - {end.strftime('%m-%d %H:%M')}")

for code, league_id in LEAGUE_IDS.items():
    model_path = MODELS_DIR / f'{code}_pro_v8_model.pkl'
    if not model_path.exists():
        continue
    try:
        model = joblib.load(model_path)
    except:
        continue

    try:
        resp = SESSION.get(BASE_URL + '/fixtures', params={
            'league': league_id, 'season': 2025,
            'from': now.strftime('%Y-%m-%d'), 'to': end.strftime('%Y-%m-%d'),
            'timezone': 'Asia/Shanghai',
        }, timeout=10)
        items = resp.json().get('response', [])
    except:
        continue
    if not items:
        continue

    league_name = LEAGUE_NAMES.get(code, code)

    for item in items:
        try:
            home = item['teams']['home']['name']
            away = item['teams']['away']['name']
            kickoff_bj = datetime.fromisoformat(
                item['fixture']['date'].replace('Z','+00:00')).astimezone(BEIJING_TZ)
            kickoff_str = kickoff_bj.strftime('%m-%d %H:%M')
            fixture_id = item['fixture']['id']

            try:
                odd_resp = SESSION.get(BASE_URL + '/odds', params={
                    'fixture': fixture_id, 'timezone': 'Asia/Shanghai'
                }, timeout=8)
                odds_data = odd_resp.json().get('response', [])
            except:
                odds_data = []

            ah_h, ah_home_odd, ah_away_odd = None, None, None
            home_1x2, draw_1x2, away_1x2 = 2.5, 3.2, 2.8

            if odds_data:
                bookmakers = odds_data[0].get('bookmakers', [])
                mw = parse_match_winner(bookmakers)
                if mw[0]:
                    home_1x2, draw_1x2, away_1x2 = mw
                ah_h, ah_home_odd, ah_away_odd = parse_asian_handicap(bookmakers, home_1x2, away_1x2)

            handicap_val = ah_h if ah_h is not None else 0

            if ah_home_odd and ah_away_odd:
                home_ah_prob, away_ah_prob = calc_ah_prob(ah_home_odd, ah_away_odd)
                if home_ah_prob is not None:
                    prob_up, prob_down = home_ah_prob, away_ah_prob
                else:
                    prob_up, prob_down = 50.0, 50.0
            else:
                prob_up, prob_down = 50.0, 50.0

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

            if ah_h is not None:
                if ah_h < 0:
                    hand_note = f"主队让{abs(ah_h):.2f}球"
                elif ah_h > 0:
                    hand_note = f"客队让{ah_h:.2f}球"
                else:
                    hand_note = "平手"
            else:
                hand_note = "⚠️无AH数据"

            rec_label, _ = recommend_level(conf)
            h_odd_str = f"{ah_home_odd:.2f}" if ah_home_odd else ""
            a_odd_str = f"{ah_away_odd:.2f}" if ah_away_odd else ""

            rows.append({
                'fixture_id': fixture_id, '联赛代码': code,
                '预测生成时间': datetime.now(BEIJING_TZ).strftime('%Y-%m-%d %H:%M'),
                '预测日期': now.strftime('%Y-%m-%d'),
                '预测窗口小时': 30, '联赛': league_name, '开赛时间': kickoff_str,
                '主队': home, '客队': away, '主队原名': home, '客队原名': away,
                '让球数': handicap_val, '上盘赔率': h_odd_str, '下盘赔率': a_odd_str,
                '主胜欧赔': home_1x2 or 0, '平局欧赔': draw_1x2 or 0, '客胜欧赔': away_1x2 or 0,
                '上盘概率': round(prob_up, 1), '下盘概率': round(prob_down, 1),
                '预测方向': pred,
                '置信区间': '高' if conf >= 70 else ('中' if conf >= 60 else '低'),
                '置信度': round(conf, 1), '推荐等级': rec_label, '推荐级别': 0,
                '模型类型': 'pro_v8', '模型准确率': 55.0,
                '让球说明': hand_note,
            })

            print(f"  {league_name} {home} vs {away} | {hand_note} | {pred}({conf:.0f}%)")

        except Exception as e:
            continue

if rows:
    df = pd.DataFrame(rows)
    csv_path = PRED_DIR / 'pro_v8_24h_v2.csv'
    df.to_csv(csv_path, index=False)
    nonzero = (df['让球数'] != 0).sum()
    no_ah = (df['让球数'] == 0).sum()
    print(f"\n✅ 共 {len(df)} 条预测，已按真实AH让球计算")
    print(f"  有真实让球: {nonzero} 条 | 无AH数据: {no_ah} 条")
else:
    print("❌ 无预测")
