#!/usr/bin/env python3
"""
ah_1year_v4 训练脚本
- 基于22个联赛的完整Pinnacle让球盘数据
- 特征：赔率比较特征 + 球队历史胜负率 + 让球数交互
- 模型：Logistic Regression + Ridge 集成（与pro_v5的RF/GBT不同）
- 输出: models/ah_1year_v4/
"""
import os, sys, json, warnings, joblib
from pathlib import Path
from collections import defaultdict
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression, RidgeClassifier
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
from sklearn.calibration import CalibratedClassifierCV
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 / 'models' / 'ah_1year_v4'
OUT_DIR.mkdir(parents=True, exist_ok=True)

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':'日职联',
}

# ═══════════════════════════════════════════════════════════════════
# 特征工程（与 pro_v5 不同：侧重赔率比值和历史均值）
# ═══════════════════════════════════════════════════════════════════

def build_team_hist(hist_df):
    """球队历史统计（历史均值，不是近期趋势）"""
    cache = defaultdict(lambda: {'hw':[],'aw':[],'dr':[],'gf':[],'ga':[],'tot':[]})
    for _, r in hist_df.iterrows():
        ht, at = str(r.get('HomeTeam','')).strip(), str(r.get('AwayTeam','')).strip()
        fthg, ftag = r.get('FTHG'), r.get('FTAG')
        if fthg is None or ftag is None: continue
        try:
            fthg, ftag = float(fthg), float(ftag)
        except: continue
        for team, gf, ga, hw, aw in [(ht,fthg,ftag,1,0),(at,ftag,fthg,0,1)]:
            c = cache[team]
            c['gf'].append(float(gf)); c['ga'].append(float(ga))
            c['tot'].append(float(gf)+float(ga))
            c['hw'].append(float(hw)); c['aw'].append(float(aw))
            c['dr'].append(1.0 if fthg==ftag else 0.0)
    return cache

def _m(v, n=20):
    vals = v[-n:]; return sum(vals)/len(vals) if vals else 0.5

def _arr10(v):
    return ([0.5]*10 + list(v))[-10:]

def build_features_v4(ahh, b365hh, b365ha, home, away, cache):
    """ah_1year_v4 专属特征（与 pro_v5 不同）"""
    c = cache.get(home, {'hw':[],'aw':[],'dr':[],'gf':[],'ga':[],'tot':[]})
    ca = cache.get(away, {'hw':[],'aw':[],'dr':[],'gf':[],'ga':[],'tot':[]})

    # 赔率比值特征
    try:
        ratio_h = float(b365hh) / float(b365ha) if float(b365ha) > 0 else 1.0
        ratio_a = float(b365ha) / float(b365hh) if float(b365hh) > 0 else 1.0
        # 归一化赔率到概率
        total_odds = 1/float(b365hh) + 1/float(b365ha)
        norm_home = (1/float(b365hh)) / total_odds if total_odds > 0 else 0.5
        norm_away = (1/float(b365ha)) / total_odds if total_odds > 0 else 0.5
        odds_gap = abs(norm_home - norm_away)
    except:
        ratio_h = ratio_a = norm_home = norm_away = odds_gap = 1.0

    # 历史主队/客队胜率（全局，不是近期）
    h_hw = _m(c['hw'], 20); a_hw = _m(ca['hw'], 20)
    h_aw = _m(c['aw'], 20); a_aw = _m(ca['aw'], 20)
    h_dr = _m(c['dr'], 20); a_dr = _m(ca['dr'], 20)

    # 让球数 × 历史胜率差
    try:
        ahh_f = float(ahh)
    except:
        ahh_f = 0.0

    # 主队让球后理论胜率调整
    # AHh > 0: 主队受让（客队让球）→ 主队胜率降低
    # AHh < 0: 主队让球 → 主队胜率提高
    # 这里用赔率概率 + 让球调整
    adj_home_prob = norm_home + (ahh_f * 0.1)  # 让球对胜率的线性调整
    adj_home_prob = max(0.1, min(0.9, adj_home_prob))

    # 均值回归特征
    recent = _arr10(c['hw']) + _arr10(ca['aw'])
    gf_diff = _m(c['gf'],10) - _m(ca['ga'],10)
    tot_avg = (_m(c['tot'],10) + _m(ca['tot'],10)) / 2

    return [
        ahh_f,
        norm_home, norm_away,
        odds_gap,
        ratio_h, ratio_a,
        adj_home_prob,
        h_hw, a_aw,
        h_aw, a_hw,
        h_dr - a_dr,
        gf_diff,
        tot_avg,
        (h_hw - a_hw) * ahh_f,
        (h_dr + a_dr) / 2,
        odds_gap * abs(ahh_f),
    ] + recent


def train_league(code):
    """训练单个联赛的 ah_1year_v4 模型"""
    full_file = ODDS_DIR / f'{code}_full_odds.csv'
    if not full_file.exists():
        return None, f'无文件'

    df = pd.read_csv(full_file, low_memory=False)
    df = df[df['PinnAHH'].notna() & (df['PinnAHH'] > 1.0)]
    df = df[df['PinnAHA'].notna() & (df['PinnAHA'] > 1.0)]
    df = df[df['FTHG'].notna() & df['FTAG'].notna()]
    df = df[df['PinnAHh'].notna()]

    if len(df) < 60:
        return None, f'数据太少({len(df)})'

    # 标签：让球后主队赢=1
    df['label'] = (df['FTHG'] + df['PinnAHh'] > df['FTAG']).astype(int)

    # 构建球队历史缓存
    hist_files = list(ODDS_DIR.glob(f'{code}_S*_fixtures.csv'))
    all_hist = pd.concat([pd.read_csv(f, usecols=['HomeTeam','AwayTeam','FTHG','FTAG'], low_memory=False) for f in hist_files], ignore_index=True)
    team_cache = build_team_hist(all_hist)

    X_rows, y_rows = [], []
    for _, r in df.iterrows():
        try:
            feat = build_features_v4(float(r['PinnAHh']), float(r['PinnAHH']), float(r['PinnAHA']),
                                     str(r['HomeTeam']), str(r['AwayTeam']), team_cache)
            X_rows.append(feat)
            y_rows.append(int(r['label']))
        except:
            continue

    X = np.array(X_rows, dtype=float)
    y = np.array(y_rows)
    n = len(X)
    if n < 60:
        return None, f'特征构建后不足({n})'

    split = max(30, int(n * 0.8))
    X_tr, X_te = X[:split], X[split:]
    y_tr, y_te = y[:split], y[split:]

    # 标准化（必须，因为LR对尺度敏感）
    scaler = StandardScaler()
    X_tr_s = scaler.fit_transform(X_tr)
    X_te_s = scaler.transform(X_te)

    # 使用 HistGradientBoostingClassifier（原 LogisicRegression 改）
    m = HistGradientBoostingClassifier(max_iter=200, random_state=42)
    m.fit(X_tr_s, y_tr)
    best_acc = accuracy_score(y_te, m.predict(X_te_s))
    best_model = m

    # 校准
    try:
        cal = CalibratedClassifierCV(best_model, cv=3, method='isotonic')
        cal.fit(X_tr_s, y_tr)
        cal_acc = accuracy_score(y_te, cal.predict(X_te_s))
    except:
        cal = best_model
        cal_acc = best_acc

    # 保存
    out_dir = OUT_DIR / code
    out_dir.mkdir(parents=True, exist_ok=True)
    cfg = {
        'league': code, 'league_name': LEAGUE_NAMES.get(code, code),
        'accuracy': float(cal_acc), 'n_samples': int(n),
        'model_type': 'calibrated_HGB',
        'threshold': 0.5,
    }
    with open(out_dir / 'optimized_config.json', 'w') as f:
        json.dump(cfg, f, indent=2)
    joblib.dump({'model': cal, 'scaler': scaler, 'acc': cal_acc, 'n_samples': n}, out_dir / 'optimized_pipeline.pkl')

    return cal_acc, n


# ═══════════════════════════════════════════════════════════════════
# 主流程
# ═══════════════════════════════════════════════════════════════════

print("=" * 60)
print("ah_1year_v4 训练开始")
print("=" * 60)

results = {}
for code in sorted(LEAGUE_NAMES.keys()):
    name = LEAGUE_NAMES[code]
    print(f"\n{'='*50}")
    print(f"训练 {code} ({name})")
    print(f"{'='*50}")
    try:
        acc, n = train_league(code)
        if acc is not None:
            results[code] = {'acc': acc, 'n': n, 'status': '✅成功'}
            print(f"  → acc={acc:.3f}, n={n}")
        else:
            results[code] = {'status': f'⚠️{n}'}
            print(f"  → {n}")
    except Exception as e:
        import traceback; traceback.print_exc()
        results[code] = {'status': f'❌{e}'}
        print(f"  → ❌ {e}")

print("\n" + "=" * 60)
print("ah_1year_v4 训练结果汇总")
print("=" * 60)
print(f"{'联赛':<8} {'准确率':<10} {'样本数':<8} {'状态'}")
for code, r in sorted(results.items()):
    acc_str = f"{r['acc']:.1%}" if 'acc' in r else '-'
    n_str = str(r.get('n', '-'))
    print(f"{code:<8} {acc_str:<10} {n_str:<8} {r['status']}")

summary = {code: {'league_name': LEAGUE_NAMES.get(code, code), **r}
            for code, r in results.items()}
with open(OUT_DIR / 'ah_1year_v4_summary.json', 'w') as f:
    json.dump(summary, f, indent=2)
print(f"\n模型保存目录: {OUT_DIR}")
