#!/usr/bin/env python3
"""亚盘让球盘模型训练 — 1year-V1"""
import json, warnings, sys
from pathlib import Path
from collections import defaultdict
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier, AdaBoostClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, RobustScaler
from sklearn.impute import SimpleImputer
from sklearn.metrics import accuracy_score
import joblib

warnings.filterwarnings('ignore')
np.random.seed(42)

REPO = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
HIST = REPO / 'historical_data'
OUT = REPO / 'models' / 'ah_1year_v1'
OUT.mkdir(parents=True, exist_ok=True)

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

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

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(df, team_hist):
    rows = []
    for _, m in df.iterrows():
        h, a = str(m['HomeTeam']), str(m['AwayTeam'])
        hh, aa = team_hist[h], team_hist[a]
        fthg = float(m.get('FTHG', 0) or 0)
        ftag = float(m.get('FTAG', 0) or 0)

        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']

        for c in ODDS_AH + ODDS_OU:
            v = m.get(c)
            if c == 'AHh':
                d[c] = float(v) if pd.notna(v) else 0.0
            else:
                d[c] = float(v) if pd.notna(v) and float(v) > 1 else 0.0

        b365h = d.get('B365AHH', 0)
        b365a = d.get('B365AHA', 0)
        if b365h > 1 and b365a > 1:
            d['ah_ratio'] = b365h / b365a
            d['ah_prob_home'] = b365a / (b365h + b365a)
        else:
            d['ah_ratio'] = 1.0
            d['ah_prob_home'] = 0.5

        rows.append(d)

        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)

    return pd.DataFrame(rows)

def get_ah_outcome(row):
    fthg = float(row.get('FTHG', 0) or 0)
    ftag = float(row.get('FTAG', 0) or 0)
    ahh = float(row.get('AHh', 0) or 0)
    return 1 if (fthg - ftag - ahh) > 0 else 0

print("=== 亚盘让球盘模型训练 ===")

results = []
TARGET_LEAGUES = ['d1','d2','f1','f2','cz1','sc1','e1','e2','e2n','e3','i1','sp1','tr1','p1','e0','sp2','i2','b1','g1','n1']

for code in TARGET_LEAGUES:
    print(f"\n处理 {code} ({LEAGUE_NAMES.get(code,code)})...", end=" ")
    sys.stdout.flush()

    hf = HIST / f"{code}_history.csv"
    if not hf.exists():
        print("文件不存在")
        continue

    df = pd.read_csv(hf, low_memory=False, encoding='utf-8-sig')
    df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
    df = df[df['Date'].notna()].sort_values('Date').reset_index(drop=True)
    df['FTHG'] = pd.to_numeric(df['FTHG'], errors='coerce')
    df['FTAG'] = pd.to_numeric(df['FTAG'], errors='coerce')
    df['AHh'] = pd.to_numeric(df.get('AHh', 0), errors='coerce').fillna(0)
    df = df[df['FTHG'].notna() & df['FTAG'].notna()].reset_index(drop=True)

    has_ah = df['B365AHH'].notna() & (df['B365AHH'] > 1.0)
    if has_ah.sum() < 100:
        print(f"AH数据不足({has_ah.sum()})")
        continue

    cutoff = df['Date'].max() - pd.Timedelta(days=365)
    train_df = df[(df['Date'] < cutoff) & has_ah].reset_index(drop=True)
    test_df = df[(df['Date'] >= cutoff) & has_ah].reset_index(drop=True)

    if len(train_df) < 100 or len(test_df) < 30:
        print(f"数据不足 train={len(train_df)} test={len(test_df)}")
        continue

    y_train = np.array([get_ah_outcome(r) for _, r in train_df.iterrows()])
    y_test = np.array([get_ah_outcome(r) for _, r in test_df.iterrows()])

    th_train = make_hist()
    X_train = build_features(train_df, th_train).fillna(0).replace([np.inf, -np.inf], 0)
    th_test = make_hist()
    X_test = build_features(test_df, th_test).fillna(0).replace([np.inf, -np.inf], 0)

    print(f"train={len(train_df)} test={len(test_df)}", end=" ")
    sys.stdout.flush()

    best_bet_acc = 0
    best_result = None

    models = [
        ('LR_05', LogisticRegression(max_iter=1000, C=0.5)),
        ('LR_1', LogisticRegression(max_iter=1000, C=1.0)),
        ('LR_2', LogisticRegression(max_iter=1000, C=2.0)),
        ('RF_d3', RandomForestClassifier(n_estimators=200, max_depth=3, random_state=42, min_samples_leaf=5)),
        ('RF_d4', RandomForestClassifier(n_estimators=200, max_depth=4, random_state=42, min_samples_leaf=3)),
        ('RF_d5', RandomForestClassifier(n_estimators=200, max_depth=5, random_state=42)),
        ('ET_d3', ExtraTreesClassifier(n_estimators=200, max_depth=3, random_state=42, min_samples_leaf=5)),
        ('ET_d4', ExtraTreesClassifier(n_estimators=200, max_depth=4, random_state=42, min_samples_leaf=3)),
        ('ET_d5', ExtraTreesClassifier(n_estimators=200, max_depth=5, random_state=42)),
        ('GB', GradientBoostingClassifier(n_estimators=100, max_depth=3, random_state=42)),
        ('ADA', AdaBoostClassifier(n_estimators=100, random_state=42)),
        ('SVM', SVC(probability=True, C=1.0, kernel='rbf')),
        ('SVM2', SVC(probability=True, C=0.5, kernel='rbf')),
    ]

    for sname, scaler in [('std', StandardScaler()), ('rob', RobustScaler())]:
        for mname, clf in models:
            try:
                pipe = Pipeline([
                    ('scaler', scaler),
                    ('imputer', SimpleImputer(fill_value=0)),
                    ('clf', clf)
                ])
                pipe.fit(X_train.values, y_train)
                proba = pipe.predict_proba(X_test.values)[:, 1]

                for th in np.arange(0.25, 0.80, 0.02):
                    for side in ['over', 'under']:
                        mask = (proba >= th) if side == 'over' else (proba <= th)
                        if mask.sum() >= 5:
                            preds = np.ones(mask.sum(), dtype=int) if side == 'over' else np.zeros(mask.sum(), dtype=int)
                            ba = accuracy_score(y_test[mask], preds)
                            if ba > best_bet_acc:
                                best_bet_acc = ba
                                best_result = {
                                    'model_name': f'{mname}_{sname}',
                                    'threshold': float(th),
                                    'side': side,
                                    'bet_count': int(mask.sum()),
                                    'bet_acc': float(ba),
                                    'pipe': pipe
                                }
            except Exception as e:
                pass

    if best_result:
        flag = '✅' if best_result['bet_acc'] >= 0.75 else ''
        print(f"-> {best_result['model_name']} th={best_result['threshold']:.2f} {best_result['side']} {best_result['bet_acc']:.1%}({best_result['bet_count']}) {flag}")

        (OUT / code).mkdir(parents=True, exist_ok=True)
        joblib.dump(best_result['pipe'], OUT / code / 'optimized_pipeline.pkl')
        save_r = {k: v for k, v in best_result.items() if k != 'pipe'}
        save_r['league'] = code
        save_r['league_name'] = LEAGUE_NAMES.get(code, code)
        with open(OUT / code / 'optimized_config.json', 'w') as f:
            json.dump(save_r, f, ensure_ascii=False)
        results.append(save_r)
    else:
        print("-> 训练失败")

print("\n\n=== 汇总 ===")
for r in sorted(results, key=lambda x: x['bet_acc'], reverse=True):
    flag = '✅' if r['bet_acc'] >= 0.75 else ''
    print(f"  {r['league']} ({r['league_name']}): {r['bet_acc']:.1%} {flag} | {r['model_name']} th={r['threshold']:.2f} {r['side']} {r['bet_count']}bets")

hit75 = sum(1 for r in results if r['bet_acc'] >= 0.75)
avg = sum(r['bet_acc'] for r in results) / len(results) if results else 0
print(f"\n{len(results)}个联赛 | 达到75%+: {hit75} | 平均准确率: {avg:.1%}")

if results:
    with open(OUT / 'ah_summary.json', 'w') as f:
        json.dump(results, f, ensure_ascii=False)
    print(f"结果已保存到 {OUT}")
