#!/usr/bin/env python3
"""V3模型回测 — 对所有联赛历史数据进行回测"""
import joblib, json, pandas as pd, numpy as np, warnings
from pathlib import Path
from collections import defaultdict
warnings.filterwarnings('ignore')

REPO = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
AH1_MODEL_DIR = REPO / 'models' / 'ah_1year_v3'
HIST_DIR = REPO / 'historical_data'
ODDS_DIR = REPO / 'artifacts' / 'odds_api_history'

ODDS_AH = ['B365AHH', 'B365AHA', 'AvgAHH', 'AvgAHA', 'PAHH', 'PAHA', 'AHh']
ODDS_OU = ['B365>2.5', 'B365<2.5', 'Avg>2.5', 'Avg<2.5']

FEAT_COLS = ['home_gavg','home_gavga','home_tavg','home_winr','home_undr','home_gstd',
    'home_hgf','home_hga','home_hgc','home_agf','home_aga','home_agc','home_mom','home_rf',
    'home_gf3','home_ga3','home_gf5','home_ga5','home_gf10','home_ga10',
    'away_gavg','away_gavga','away_tavg','away_winr','away_undr','away_gstd',
    'away_hgf','away_hga','away_hgc','away_agf','away_aga','away_agc','away_mom','away_rf',
    'away_gf3','away_ga3','away_gf5','away_ga5','away_gf10','away_ga10',
    'hg_diff','ga_diff','tot_diff','win_diff','mom_diff',
    'B365AHH','B365AHA','AvgAHH','AvgAHA','PAHH','PAHA','AHh',
    'B365>2.5','B365<2.5','Avg>2.5','Avg<2.5','ah_ratio','ah_prob_home']

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

def _build_team_hist():
    return {'gf':[],'ga':[],'tot':[],'win':[],'und':[],'hgf':[],'hga':[],'hgc':[],'agf':[],'aga':[],'agc':[],'mom':[],'rf':[]}

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 _stdv(v):
    v = list(v)
    return float(np.std(v[-10:])) if len(v) >= 2 else 0.5

def _arr(v):
    if isinstance(v, np.ndarray): return list(v)
    return list(v) if v else []

def _mean(v):
    v = _arr(v)
    return float(np.mean(v)) if v else 0.0

def _build_feature(handicap, ahh, aha, home_name, away_name, team_cache):
    hh = team_cache.get(home_name, _build_team_hist())
    aa = team_cache.get(away_name, _build_team_hist())
    d = {}
    for p, s in [('home', hh), ('away', aa)]:
        g = _arr(s['gf']); ga_ = _arr(s['ga']); tot = _arr(s['tot'])
        win = _arr(s['win']); und = _arr(s['und'])
        hgf = _arr(s['hgf']); hga_ = _arr(s['hga']); hgc = _arr(s['hgc'])
        agf = _arr(s['agf']); aga_ = _arr(s['aga']); agc = _arr(s['agc'])
        mom = _arr(s['mom']); rf = _arr(s['rf'])
        d[f'{p}_gavg'] = _mean(g); d[f'{p}_gavga'] = _mean(ga_)
        d[f'{p}_tavg'] = _mean(tot); d[f'{p}_winr'] = _mean(win)
        d[f'{p}_undr'] = _mean(und); d[f'{p}_gstd'] = _stdv(g)
        d[f'{p}_hgf'] = _mean(hgf); d[f'{p}_hga'] = _mean(hga_)
        d[f'{p}_hgc'] = _mean(hgc); d[f'{p}_agf'] = _mean(agf)
        d[f'{p}_aga'] = _mean(aga_); d[f'{p}_agc'] = _mean(agc)
        d[f'{p}_mom'] = _mean(mom[-5:]) if len(mom) >= 2 else 0.0
        d[f'{p}_rf'] = _mean(rf[-5:]) if len(rf) >= 2 else 0.0
        d[f'{p}_gf3'] = _mn(g, 3); d[f'{p}_ga3'] = _mn(ga_, 3)
        d[f'{p}_gf5'] = _mn(g, 5); d[f'{p}_ga5'] = _mn(ga_, 5)
        d[f'{p}_gf10'] = _mn(g, 10); d[f'{p}_ga10'] = _mn(ga_, 10)
    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']
    for c in ODDS_AH + ODDS_OU:
        d[c] = 0.0
    d['AHh'] = float(handicap) if handicap else 0.0
    h = float(ahh) if ahh and float(ahh) > 1 else 0.0
    a = float(aha) if aha and float(aha) > 1 else 0.0
    d['B365AHH'] = h; d['B365AHA'] = a
    d['AvgAHH'] = h; d['AvgAHA'] = a; d['PAHH'] = h; d['PAHA'] = a
    if h > 1 and a > 1:
        d['ah_ratio'] = h / a; d['ah_prob_home'] = a / (h + a)
    else:
        d['ah_ratio'] = 1.0; d['ah_prob_home'] = 0.5
    df = pd.DataFrame([d])
    df = df.reindex(columns=FEAT_COLS, fill_value=0)
    return df.fillna(0).replace([np.inf, -np.inf], 0)

def _load_team_cache(league_code):
    """加载联赛历史数据并构建球队统计缓存"""
    cache = defaultdict(_build_team_hist)
    hist_file = HIST_DIR / f'{league_code}_history.csv'
    if hist_file.exists():
        try:
            df = pd.read_csv(hist_file, usecols=['Date','HomeTeam','AwayTeam','FTHG','FTAG'], low_memory=False)
            df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
            df = df[df['Date'].notna()].sort_values('Date')
            for _, r in df.iterrows():
                ht = str(r['HomeTeam']); at = str(r['AwayTeam'])
                fthg = float(r['FTHG']) if pd.notna(r.get('FTHG')) else 0
                ftag = float(r['FTAG']) if pd.notna(r.get('FTAG')) else 0
                for team, gf, ga, win_ in [(ht, fthg, ftag, 1 if fthg > ftag else 0),(at, ftag, fthg, 1 if ftag > fthg else 0)]:
                    if team not in cache:
                        cache[team] = _build_team_hist()
                    tg = cache[team]
                    tg['gf'].append(float(gf)); tg['ga'].append(float(ga))
                    tg['win'].append(float(win_)); tg['und'].append(1.0 - float(win_))
                    tg['tot'].append(float(gf) + float(ga))
                    tg['hgf'].append(float(gf)); tg['hga'].append(float(ga)); tg['hgc'].append(float(win_))
                    tg['agf'].append(float(ga)); tg['aga'].append(float(fthg)); tg['agc'].append(1.0 - float(win_))
                    tg['mom'].append(float(win_)); tg['rf'].append(float(gf) - float(ga))
        except Exception as e:
            print(f"  Warning loading {league_code}: {e}")
    return cache

def _get_actual_direction(fthg, ftag, ah_handicap):
    """计算实际亚盘方向"""
    home_goals = fthg - ah_handicap
    if home_goals > 0.5: return '上盘'
    elif home_goals < -0.5: return '下盘'
    else: return '走'

def run_backtest(target_month=None, target_year=2025):
    """运行回测，target_month为None时测所有时间，否则为1-12"""
    all_rows = []
    
    # 需要同时有B365盘口和比分数据的联赛
    available_codes = []
    for code in LEAGUE_NAMES:
        hf = HIST_DIR / f'{code}_history.csv'
        if hf.exists():
            df_t = pd.read_csv(hf, usecols=['B365AHH','FTHG'], nrows=5)
            if 'B365AHH' in df_t.columns:
                available_codes.append(code)
    
    print(f"可用联赛: {len(available_codes)}个")
    
    for league_code in available_codes:
        model_dir = AH1_MODEL_DIR / league_code
        cfg_path = model_dir / 'optimized_config.json'
        pip_path = model_dir / 'optimized_pipeline.pkl'
        if not cfg_path.exists() or not pip_path.exists():
            continue
        
        cfg = json.load(open(cfg_path))
        pipeline = joblib.load(pip_path)
        threshold = cfg.get('threshold', 0.55)
        direction = cfg.get('direction', 'over')
        league_name = LEAGUE_NAMES.get(league_code, league_code)
        
        hist_file = HIST_DIR / f'{league_code}_history.csv'
        try:
            df = pd.read_csv(hist_file, low_memory=False)
        except:
            continue
        
        # 过滤有效数据
        df = df[df['B365AHH'].notna() & (df['B365AHH'] > 1.0) & 
                 df['B365AHA'].notna() & (df['B365AHA'] > 1.0) &
                 df['FTHG'].notna() & df['FTAG'].notna()].copy()
        
        if df.empty:
            continue
        
        try:
            df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
            df = df[df['Date'].notna()]
        except:
            continue
        
        # 按时间排序
        df = df.sort_values('Date')
        
        # 如果指定了月份，只看该月
        if target_month:
            df = df[(df['Date'].dt.month == target_month) & (df['Date'].dt.year == target_year)]
        
        if df.empty:
            continue
        
        # 加载球队统计
        team_cache = _load_team_cache(league_code)
        
        # 逐条预测
        bet_count = 0; hit_count = 0
        for idx, match in df.iterrows():
            try:
                ahh = float(match['B365AHH'])
                aha = float(match['B365AHA'])
                ahh = max(ahh, 1.01); aha = max(aha, 1.01)
                handicap = float(match['AHh']) if pd.notna(match.get('AHh')) else 0.0
                fthg = int(match['FTHG']); ftag = int(match['FTAG'])
                home_name = str(match['HomeTeam']); away_name = str(match['AwayTeam'])
                date_str = match['Date'].strftime('%Y-%m-%d') if pd.notna(match['Date']) else ''
                
                feat = _build_feature(handicap, ahh, aha, home_name, away_name, team_cache)
                prob = float(pipeline.predict_proba(feat.values)[0, 1])
                
                # 预测方向
                if direction == 'over':
                    if prob >= threshold:
                        pred = '上盘'
                    elif prob <= (1 - threshold):
                        pred = '下盘'
                    else:
                        pred = '不确定'
                else:
                    if prob <= (1 - threshold):
                        pred = '上盘'
                    elif prob >= threshold:
                        pred = '下盘'
                    else:
                        pred = '不确定'
                
                actual = _get_actual_direction(fthg, ftag, handicap)
                conf = abs(prob - 0.5) * 2 * 100
                
                if pred in ('上盘', '下盘'):
                    bet_count += 1
                    if pred == actual:
                        hit_count += 1
                        hit = '是'
                        result = '命中'
                    elif actual == '走':
                        hit = '走'
                        result = '走盘'
                    else:
                        hit = '否'
                        result = '失误'
                else:
                    hit = '-'
                    result = '跳过'
                
                all_rows.append({
                    '日期': date_str,
                    '联赛': league_name,
                    '主队': home_name,
                    '客队': away_name,
                    '比分': f'{fthg}-{ftag}',
                    '让球数': f'{handicap:+.2f}',
                    '预测方向': pred,
                    '预测概率': f'{prob:.1%}',
                    '置信度': f'{conf:.1f}%',
                    '实际方向': actual,
                    '命中': hit,
                    '结果': result,
                })
                
                # 更新球队缓存（加入这场比赛的结果，供后续比赛使用）
                for team, gf, ga, win_ in [(home_name, fthg, ftag, 1 if fthg > ftag else 0),
                                            (away_name, ftag, fthg, 1 if ftag > fthg else 0)]:
                    if team in team_cache:
                        tg = team_cache[team]
                        tg['gf'].append(float(gf)); tg['ga'].append(float(ga))
                        tg['win'].append(float(win_)); tg['und'].append(1.0 - float(win_))
                        tg['tot'].append(float(gf) + float(ga))
                        tg['hgf'].append(float(gf)); tg['hga'].append(float(ga)); tg['hgc'].append(float(win_))
                        tg['agf'].append(float(ga)); tg['aga'].append(float(fthg)); tg['agc'].append(1.0 - float(win_))
                        tg['mom'].append(float(win_)); tg['rf'].append(float(gf) - float(ga))
            except Exception as e:
                continue
        
        if bet_count > 0:
            acc = hit_count / bet_count
            print(f"  {league_name}({league_code}): {len(df)}场, 有效{bet_count}场, 准确率={acc:.1%} ({hit_count}/{bet_count})")
    
    return pd.DataFrame(all_rows)

if __name__ == '__main__':
    import sys
    month = int(sys.argv[1]) if len(sys.argv) > 1 else None
    year = int(sys.argv[2]) if len(sys.argv) > 2 else 2025
    
    print(f"=== V3模型回测 {'%d年%d月' % (year, month) if month else '全部数据'} ===")
    result = run_backtest(target_month=month, target_year=year)
    
    out_dir = REPO / 'predictions'
    out_dir.mkdir(exist_ok=True)
    fname = f'ah1_backtest_{year}{month:02d}' if month else 'ah1_backtest_all'
    csv_path = out_dir / f'{fname}.csv'
    result.to_csv(csv_path, index=False, encoding='utf-8')
    
    if not result.empty:
        bets = result[result['结果'].isin(['命中', '失误'])]
        hits = (result['结果'] == '命中').sum()
        print(f"\n总计: {len(bets)}场有效, 准确率={hits/len(bets):.1%} ({hits}/{len(bets)})")
        print(f"结果已保存: {csv_path}")
