#!/usr/bin/env python3
"""亚盘让球盘模型预测 — 结合API实际赔率"""
import json, warnings
from pathlib import Path
from collections import defaultdict
import pandas as pd
import numpy as np
import joblib
import requests
from datetime import datetime, timedelta

warnings.filterwarnings('ignore')

REPO = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
HIST = REPO / 'historical_data'
OUT = REPO / 'models' / 'ah_1year_v1'
API_KEY = '57b64e857ebeffdc156f0c41d43c7894'
API_BASE = 'https://v3.football.api-sports.io'

LEAGUE_NAMES = {
    'd1':'德甲','d2':'德乙','f1':'法甲','f2':'法乙',
    'sc1':'苏超','tr1':'土超','i1':'意甲','sp1':'西甲','p1':'葡超'
}

LEAGUE_API_IDS = {
    'd1': 78, 'd2': 79, 'f1': 61, 'f2': 62,
    'sc1': 179, 'tr1': 203, 'i1': 135, 'sp1': 140, 'p1': 94,
}

def make_hist():
    return defaultdict(lambda: {
        'gf':[],'ga':[],'tot':[],'win':[],'und':[],
        'gf3':[],'ga3':[],'gf5':[],'ga5':[],'gf10':[],'ga10':[],
        'hgf':[],'hga':[],'hgc':[],'agf':[],'aga':[],'agc':[],'mom':[],'rf':[]
    })

def build_features_from_odds(home_name, away_name, ah_home_odds, ah_away_odds, ah_handicap, team_hist):
    """用赔率和历史数据构建特征"""
    # 基础历史特征
    hh = team_hist.get(home_name, make_hist()[home_name])
    aa = team_hist.get(away_name, make_hist()[away_name])
    
    def mn(v, n):
        v = list(v)
        return float(np.mean(v[-n:])) if len(v) >= n else (float(np.mean(v)) if v else 0.0)
    def std(v):
        v = list(v)
        return float(np.std(v[-10:])) if len(v) >= 2 else 0.5
    
    d = {}
    for p, s in [('home', hh), ('away', aa)]:
        d[f'{p}_gavg'] = np.mean(s['gf']) if s['gf'] else 1.5
        d[f'{p}_gavga'] = np.mean(s['ga']) if s['ga'] else 1.5
        d[f'{p}_tavg'] = np.mean(s['tot']) if s['tot'] else 2.5
        d[f'{p}_winr'] = np.mean(s['win']) if s['win'] else 0.5
        d[f'{p}_undr'] = np.mean(s['und']) if s['und'] else 0.5
        d[f'{p}_gstd'] = std(s.get('gf',[]))
        d[f'{p}_hgf'] = np.mean(s['hgf']) if s['hgf'] else d[f'{p}_gavg']
        d[f'{p}_hga'] = np.mean(s['hga']) if s['hga'] else d[f'{p}_gavga']
        d[f'{p}_hgc'] = np.mean(s['hgc']) if s['hgc'] else 0.3
        d[f'{p}_agf'] = np.mean(s['agf']) if s['agf'] else d[f'{p}_gavg']
        d[f'{p}_aga'] = np.mean(s['aga']) if s['aga'] else d[f'{p}_gavga']
        d[f'{p}_agc'] = np.mean(s['agc']) if s['agc'] else 0.3
        d[f'{p}_mom'] = np.mean(s['mom'][-5:]) if len(s.get('mom',[])) >= 2 else 0.0
        d[f'{p}_rf'] = np.mean(s['rf'][-5:]) if len(s.get('rf',[])) >= 2 else 0.0
        for n in [3, 5, 10]:
            d[f'{p}_gf{n}'] = mn(s.get('gf',[]), n)
            d[f'{p}_ga{n}'] = mn(s.get('ga',[]), n)
    
    d['hg_diff'] = d['home_gavg'] - d['away_agf']
    d['ga_diff'] = d['home_gavga'] - d['away_aga']
    d['tot_diff'] = d['home_tavg'] - d['away_tavg']
    d['win_diff'] = d['home_winr'] - d['away_winr']
    d['mom_diff'] = d['home_mom'] - d['away_mom']
    
    # 赔率特征
    d['AHh'] = float(ah_handicap) if ah_handicap else 0.0
    d['B365AHH'] = float(ah_home_odds) if ah_home_odds and ah_home_odds > 1 else 0.0
    d['B365AHA'] = float(ah_away_odds) if ah_away_odds and ah_away_odds > 1 else 0.0
    d['AvgAHH'] = d['B365AHH']
    d['AvgAHA'] = d['B365AHA']
    d['PAHH'] = d['B365AHH']
    d['PAHA'] = d['B365AHA']
    
    # 赔率衍生特征
    b365h = d['B365AHH']
    b365a = d['B365AHA']
    if b365h > 1 and b365a > 1:
        d['ah_ratio'] = b365h / b365a
        d['ah_prob_home'] = b365a / (b365h + b365a)  # 从赔率反推概率
        d['ah_prob_away'] = b365h / (b365h + b365a)
        d['ah_margin_home'] = 1 - (1/b365h + 1/b365a)  # 博彩公司margin
    else:
        d['ah_ratio'] = 1.0
        d['ah_prob_home'] = 0.5
        d['ah_prob_away'] = 0.5
        d['ah_margin_home'] = 0.05
    
    # 虚拟大小球
    d['B365>2.5'] = 0.0
    d['B365<2.5'] = 0.0
    d['Avg>2.5'] = 0.0
    d['Avg<2.5'] = 0.0
    
    return d

def get_ah_odds_from_response(fix_id, home_team, away_team):
    """从赔率响应中提取亚盘数据"""
    headers = {'x-apisports-key': API_KEY}
    try:
        r = requests.get(f'{API_BASE}/odds', params={'fixture': fix_id}, headers=headers, timeout=10)
        data = r.json()
        if data.get('results', 0) == 0:
            return None, None, None
        
        for bm_data in data['response']:
            bm_name = bm_data['bookmaker']['name']
            if 'Bet365' in bm_name or 'beton' in bm_name.lower() or bm_name in ['10Bet', 'William Hill', 'Betclic', 'Unibet', 'Marathonbet', 'Pinnacle']:
                for bet in bm_data['bookmaker']['bets']:
                    if 'Asian Handicap' in bet['name'] or 'Handicap' in bet['name']:
                        # 解析AH赔率
                        ah_home = None; ah_away = None; ah_hcap = None
                        for val in bet['values']:
                            v = val['value']
                            odd = float(val['odd'])
                            # 识别主队盘口值
                            if 'Home' in v or '+' in v or '-' in v:
                                # 解析盘口值
                                import re
                                m = re.search(r'([+-]?[\d.]+)', v)
                                if m:
                                    hcap = float(m.group(1))
                                    if 'Home' in v or ('+' in v and '-' not in v):
                                        ah_hcap = hcap
                                        ah_home = odd
                                    else:
                                        ah_away = odd
                            elif 'Away' in v:
                                ah_away = odd
                        
                        if ah_home and ah_away:
                            return ah_home, ah_away, ah_hcap
    except:
        pass
    return None, None, None

def get_upcoming_with_odds(api_id, code):
    """获取即将比赛及赔率"""
    headers = {'x-apisports-key': API_KEY}
    today = datetime.utcnow()
    
    # 获取未来4天比赛
    params = {
        'league': api_id,
        'season': 2025,
        'from': today.strftime('%Y-%m-%d'),
        'to': (today + timedelta(days=4)).strftime('%Y-%m-%d'),
        'status': 'NS',
        'timezone': 'Asia/Shanghai',
    }
    
    try:
        r = requests.get(f'{API_BASE}/fixtures', params=params, headers=headers, timeout=10)
        data = r.json()
        if data.get('results', 0) == 0:
            return []
        return data['response']
    except:
        return []

def predict():
    all_results = {}
    
    for code, api_id in LEAGUE_API_IDS.items():
        name = LEAGUE_NAMES.get(code, code)
        
        # 加载模型
        cfg_file = OUT / code / 'optimized_config.json'
        pipe_file = OUT / code / 'optimized_pipeline.pkl'
        if not cfg_file.exists() or not pipe_file.exists():
            print(f"【{name}】模型不存在")
            continue
        
        cfg = json.load(open(cfg_file))
        pipeline = joblib.load(pipe_file)
        
        # 加载历史数据
        hist_file = HIST / f'{code}_history.csv'
        if not hist_file.exists():
            continue
        
        df_hist = pd.read_csv(hist_file, low_memory=False, encoding='utf-8-sig')
        df_hist['Date'] = pd.to_datetime(df_hist['Date'], dayfirst=True, errors='coerce')
        df_hist['FTHG'] = pd.to_numeric(df_hist['FTHG'], errors='coerce')
        df_hist['FTAG'] = pd.to_numeric(df_hist['FTAG'], errors='coerce')
        df_hist = df_hist[df_hist['FTHG'].notna() & df_hist['FTAG'].notna()]
        df_hist = df_hist.sort_values('Date').reset_index(drop=True)
        
        # 构建球队历史
        team_hist = make_hist()
        for _, row in df_hist.iterrows():
            h, a = str(row['HomeTeam']), str(row['AwayTeam'])
            hh, aa = team_hist[h], team_hist[a]
            fthg = float(row['FTHG']); ftag = float(row['FTAG'])
            for arr in [hh, aa]:
                arr['gf'].append(fthg + ftag); arr['ga'].append(ftag + fthg); arr['tot'].append(fthg + ftag)
                arr['win'].append(1 if fthg > ftag else 0); arr['und'].append(1 if fthg + ftag < 2.5 else 0)
            hh['hgf'].append(fthg); hh['hga'].append(ftag); hh['hgc'].append(1 if ftag == 0 else 0)
            aa['agf'].append(ftag); aa['aga'].append(fthg); aa['agc'].append(1 if fthg == 0 else 0)
            for arr in [hh, aa]:
                g = arr['gf']
                if len(g) >= 2:
                    arr['mom'].append(np.mean(g[-3:]) - np.mean(g[-6:-3]) if len(g) >= 6 else 0)
                    arr['rf'].append(np.mean(arr['win'][-3:]) - np.mean(arr['win'][:-3]) if len(arr['win']) >= 6 else 0)
        
        # 获取比赛
        fixtures = get_upcoming_with_odds(api_id, code)
        if not fixtures:
            print(f"【{name}】无即将开始的比赛")
            continue
        
        results = []
        for fix in fixtures:
            f = fix['fixture']
            fid = f['id']
            home = fix['teams']['home']['name']
            away = fix['teams']['away']['name']
            date_str = f['date'][:16].replace('T', ' ')
            
            # 获取AH赔率
            ah_home, ah_away, ah_hcap = get_ah_odds_from_response(fid, home, away)
            
            if ah_home is None:
                # 没有赔率，跳过或用默认值
                continue
            
            # 构建特征
            feat_dict = build_features_from_odds(home, away, ah_home, ah_away, ah_hcap, team_hist)
            feat_df = pd.DataFrame([feat_dict])
            feat_df = feat_df.fillna(0).replace([np.inf, -np.inf], 0)
            
            try:
                proba = pipeline.predict_proba(feat_df.values)[:, 1]
                p_home = float(proba[0])
                p_away = 1 - p_home
                
                # 赔率隐含概率
                implied_ph = ah_away / (ah_home + ah_away)
                implied_pa = ah_home / (ah_home + ah_away)
                
                # 综合评分: 50%模型 + 50%赔率
                combined_ph = 0.5 * p_home + 0.5 * implied_ph
                combined_pa = 0.5 * p_away + 0.5 * implied_pa
                
                th = cfg['threshold']
                side = cfg['side']
                
                if side == 'over':
                    bet_home = combined_ph >= th
                    confidence = abs(combined_ph - th)
                else:
                    bet_home = combined_ph <= th
                    confidence = abs(combined_pa - th)
                
                bet_side = '主队赢让球' if bet_home else '客队赢让球'
                conf_label = '高' if confidence > 0.12 else '中' if confidence > 0.07 else '低'
                
                results.append({
                    'date': date_str,
                    'home': home,
                    'away': away,
                    'ah_handicap': ah_hcap,
                    'ah_home': ah_home,
                    'ah_away': ah_away,
                    'p_model': p_home,
                    'p_implied': implied_ph,
                    'p_combined': combined_ph,
                    'bet': bet_side,
                    'conf': conf_label,
                    'confidence': confidence,
                    'wf_acc': cfg.get('wf_bet_acc', cfg.get('bet_acc', 0))
                })
            except Exception as e:
                pass
        
        if results:
            all_results[code] = {'name': name, 'results': results}
            print(f"\n【{name}】（{len(results)}场有赔率）")
            for p in results:
                print(f"  {p['date']} {p['home']} vs {p['away']}")
                print(f"    盘口: 主队{AH_str(p['ah_handicap'])} | 赔率: {p['ah_home']}/{p['ah_away']}")
                print(f"    模型概率: {p['p_model']:.1%} | 赔率隐含: {p['p_implied']:.1%} | 综合: {p['p_combined']:.1%}")
                print(f"    → {p['bet']} (信心:{p['conf']}, WF准确率:{p['wf_acc']:.0%})")
    
    return all_results

def AH_str(h):
    if h is None: return ""
    if h >= 0: return f"+{h}"
    return str(h)

if __name__ == '__main__':
    print("=== 亚盘让球盘预测（结合实际赔率）===\n")
    results = predict()
    
    # 高信心汇总
    print("\n\n=== 高信心投注 ===")
    for code, info in results.items():
        high = [p for p in info['results'] if p['conf'] == '高']
        if high:
            print(f"\n【{info['name']}】")
            for p in high:
                print(f"  {p['date']} {p['home']} vs {p['away']}")
                print(f"    综合概率: {p['p_combined']:.0%} | 投注: {p['bet']} | WF准确率: {p['wf_acc']:.0%}")
