#!/usr/bin/env python3
"""Evaluate 1year-V1 optimized models on recent data (2025-12-01+)."""
import json, warnings
from pathlib import Path
from collections import defaultdict
import pandas as pd
import numpy as np
import joblib

warnings.filterwarnings('ignore')

REPO_ROOT = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
HISTORICAL_DIR = REPO_ROOT / 'historical_data'
MODEL_DIR = REPO_ROOT / 'models' / '1year_V1_optimized'
RECENT_SINCE = '2025-12-01'

LEAGUES = ['a1', 'br1', 'cz1', 'd1', 'e0', 'e1', 'e2', 'e2n', 'e3', 'f1', 'g1', 'i1', 'p1', 'sc1', 'tr1']

LEAGUE_NAMES = {
    'a1': '澳超', 'br1': '巴甲', 'cz1': '捷克甲', 'd1': '德甲',
    'e0': '英超', 'e1': '英甲', 'e2': '英冠', 'e2n': '英乙',
    'e3': '英格兰第五级', 'f1': '法甲', 'g1': '希腊超', 'i1': '意甲',
    'm1': '美职联', 'p1': '葡超', 'r1': '俄超', 'sc1': '苏超',
    'sp1': '西甲', 'tr1': '土超', 'uz1': '乌兹别克超'
}

def seed_histories(df):
    """Seed team histories from a dataframe of matches."""
    team_hist = defaultdict(lambda: {
        "gf": [], "ga": [], "tot": [], "over": [],
        "home_gf": [], "home_ga": [], "home_over": [],
        "away_gf": [], "away_ga": [], "away_over": [],
        "gf_last3": [], "ga_last3": [], "over_last3": [],
        "gf_last5": [], "ga_last5": [], "over_last5": [],
        "gf_last10": [], "ga_last10": [], "over_last10": [],
        "gf_last15": [], "ga_last15": [], "over_last15": [],
        "gf_last20": [], "ga_last20": [], "over_last20": [],
        "momentum": [], "recent_form": [], "goal_trend": [],
        "home_clean": [], "away_clean": [],
        "btts_yes": [], "btts_no": [],
    })
    
    for _, match in df.iterrows():
        home, away = str(match["HomeTeam"]), str(match["AwayTeam"])
        hh, ah = team_hist[home], team_hist[away]
        fthg = float(match.get('FTHG', 0) or 0)
        ftag = float(match.get('FTAG', 0) or 0)
        tg = fthg + ftag
        ov = 1 if tg > 2.5 else 0
        
        for w in [3,5,10,15,20]:
            hh[f"gf_last{w}"].append(tg); hh[f"ga_last{w}"].append(tg); hh[f"over_last{w}"].append(ov)
            ah[f"gf_last{w}"].append(tg); ah[f"ga_last{w}"].append(tg); ah[f"over_last{w}"].append(ov)
            if w in [5,10,15,20]:
                hh["home_gf"].append(fthg); hh["home_ga"].append(ftag); hh["home_over"].append(ov)
                ah["away_gf"].append(fthg); ah["away_ga"].append(ftag); ah["away_over"].append(ov)
        
        hh["gf"].append(tg); hh["ga"].append(tg); hh["tot"].append(tg); hh["over"].append(ov)
        ah["gf"].append(tg); ah["ga"].append(tg); ah["tot"].append(tg); ah["over"].append(ov)
        hh["home_clean"].append(1 if ftag==0 else 0); ah["away_clean"].append(1 if fthg==0 else 0)
        hh["btts_yes"].append(1 if fthg>0 and ftag>0 else 0); ah["btts_yes"].append(1 if fthg>0 and ftag>0 else 0)
        hh["btts_no"].append(1 if fthg==0 or ftag==0 else 0); ah["btts_no"].append(1 if fthg==0 or ftag==0 else 0)
        
        hh["momentum"].append(1 if tg>ftag else 0.5); ah["momentum"].append(1 if tg>fthg else 0.5)
        hh["recent_form"].append(1 if tg>ftag else 0.5); ah["recent_form"].append(1 if tg>fthg else 0.5)
        
        if len(hh["gf_last10"]) >= 2:
            r, o = hh["gf_last10"][-5:], hh["gf_last10"][:-5]
            hh["goal_trend"].append(np.mean(r)-np.mean(o) if o else 0)
        if len(ah["gf_last10"]) >= 2:
            r, o = ah["gf_last10"][-5:], ah["gf_last10"][:-5]
            ah["goal_trend"].append(np.mean(r)-np.mean(o) if o else 0)
    
    return team_hist


def build_feature_row(hh, ah):
    """Build a single feature dict from team histories."""
    row = {}
    for w in [3,5,10,15,20]:
        row[f"home_gf_last{w}"] = np.mean(hh[f"gf_last{w}"]) if hh[f"gf_last{w}"] else 1.4
        row[f"home_ga_last{w}"] = np.mean(hh[f"ga_last{w}"]) if hh[f"ga_last{w}"] else 1.4
        row[f"away_gf_last{w}"] = np.mean(ah[f"gf_last{w}"]) if ah[f"gf_last{w}"] else 1.2
        row[f"away_ga_last{w}"] = np.mean(ah[f"ga_last{w}"]) if ah[f"ga_last{w}"] else 1.2
        row[f"home_over_last{w}"] = np.mean(hh[f"over_last{w}"]) if hh[f"over_last{w}"] else 0.5
        row[f"away_over_last{w}"] = np.mean(ah[f"over_last{w}"]) if ah[f"over_last{w}"] else 0.5
        if w in [5,10,15,20]:
            row[f"home_home_gf_last{w}"] = np.mean(hh["home_gf"][-w:]) if len(hh["home_gf"])>=w else 1.4
            row[f"home_home_ga_last{w}"] = np.mean(hh["home_ga"][-w:]) if len(hh["home_ga"])>=w else 1.4
            row[f"away_away_gf_last{w}"] = np.mean(ah["away_gf"][-w:]) if len(ah["away_gf"])>=w else 1.2
            row[f"away_away_ga_last{w}"] = np.mean(ah["away_ga"][-w:]) if len(ah["away_ga"])>=w else 1.2
            row[f"home_home_over_last{w}"] = np.mean(hh["home_over"][-w:]) if len(hh["home_over"])>=w else 0.5
            row[f"away_away_over_last{w}"] = np.mean(ah["away_over"][-w:]) if len(ah["away_over"])>=w else 0.5

    row["home_gf_avg"] = np.mean(hh["gf"]) if hh["gf"] else 1.4
    row["home_ga_avg"] = np.mean(hh["ga"]) if hh["ga"] else 1.4
    row["away_gf_avg"] = np.mean(ah["gf"]) if ah["gf"] else 1.2
    row["away_ga_avg"] = np.mean(ah["ga"]) if ah["ga"] else 1.2
    row["home_tot_avg"] = np.mean(hh["tot"]) if hh["tot"] else 2.6
    row["away_tot_avg"] = np.mean(ah["tot"]) if ah["tot"] else 2.6
    row["home_over_rate"] = np.mean(hh["over"]) if hh["over"] else 0.5
    row["away_over_rate"] = np.mean(ah["over"]) if ah["over"] else 0.5
    row["home_home_gf_avg"] = np.mean(hh["home_gf"]) if hh["home_gf"] else row["home_gf_avg"]
    row["home_home_ga_avg"] = np.mean(hh["home_ga"]) if hh["home_ga"] else row["home_ga_avg"]
    row["away_away_gf_avg"] = np.mean(ah["away_gf"]) if ah["away_gf"] else row["away_gf_avg"]
    row["away_away_ga_avg"] = np.mean(ah["away_ga"]) if ah["away_ga"] else row["away_ga_avg"]
    row["home_home_over_rate"] = np.mean(hh["home_over"]) if hh["home_over"] else row["home_over_rate"]
    row["away_away_over_rate"] = np.mean(ah["away_over"]) if ah["away_over"] else row["away_over_rate"]
    
    row["home_momentum"] = np.mean(hh["momentum"][-10:]) if len(hh["momentum"])>=10 else 0.5
    row["away_momentum"] = np.mean(ah["momentum"][-10:]) if len(ah["momentum"])>=10 else 0.5
    row["home_recent_form"] = np.mean(hh["recent_form"][-5:]) if len(hh["recent_form"])>=5 else 0.5
    row["away_recent_form"] = np.mean(ah["recent_form"][-5:]) if len(ah["recent_form"])>=5 else 0.5
    row["home_goal_trend"] = hh["goal_trend"][-1] if hh["goal_trend"] else 0
    row["away_goal_trend"] = ah["goal_trend"][-1] if ah["goal_trend"] else 0
    
    for key, default in [("gf_var",0.5),("ga_var",0.5),("gf_std",0.5),("ga_std",0.5)]:
        row[f"home_{key}"] = np.var(hh["gf_last20"]) if len(hh["gf_last20"])>=2 else default
        row[f"away_{key}"] = np.var(ah["gf_last20"]) if len(ah["gf_last20"])>=2 else default
    
    row["expected_goals_home"] = row["home_gf_avg"] + row["away_ga_avg"]
    row["expected_goals_away"] = row["away_gf_avg"] + row["home_ga_avg"]
    row["expected_goals_proxy"] = (row["expected_goals_home"]+row["expected_goals_away"])/2
    row["combined_over_rate"] = (row["home_over_rate"]+row["away_over_rate"])/2
    row["home_away_gf_diff"] = row["home_gf_avg"] - row["away_gf_avg"]
    row["home_away_ga_diff"] = row["home_ga_avg"] - row["away_ga_avg"]
    row["home_away_tot_diff"] = row["home_tot_avg"] - row["away_tot_avg"]
    row["home_away_over_diff"] = row["home_over_rate"] - row["away_over_rate"]
    row["form_ratio"] = row["home_momentum"]/(row["away_momentum"]+0.01)
    row["momentum_advantage"] = row["home_momentum"] - row["away_momentum"]
    row["home_clean_sheet_rate"] = np.mean(hh["home_clean"]) if hh["home_clean"] else 0.3
    row["away_clean_sheet_rate"] = np.mean(ah["away_clean"]) if ah["away_clean"] else 0.3
    row["home_btts_rate"] = np.mean(hh["btts_yes"]) if hh["btts_yes"] else 0.45
    row["away_btts_rate"] = np.mean(ah["btts_yes"]) if ah["btts_yes"] else 0.45
    row["combined_btts_rate"] = (row["home_btts_rate"]+row["away_btts_rate"])/2
    
    return row


def update_histories(hh, ah, fthg, ftag):
    """Update team histories after a match."""
    tg = fthg + ftag
    ov = 1 if tg > 2.5 else 0
    
    for w in [3,5,10,15,20]:
        hh[f"gf_last{w}"].append(tg); hh[f"ga_last{w}"].append(tg); hh[f"over_last{w}"].append(ov)
        ah[f"gf_last{w}"].append(tg); ah[f"ga_last{w}"].append(tg); ah[f"over_last{w}"].append(ov)
        if w in [5,10,15,20]:
            hh["home_gf"].append(fthg); hh["home_ga"].append(ftag); hh["home_over"].append(ov)
            ah["away_gf"].append(fthg); ah["away_ga"].append(ftag); ah["away_over"].append(ov)
    
    hh["gf"].append(tg); hh["ga"].append(tg); hh["tot"].append(tg); hh["over"].append(ov)
    ah["gf"].append(tg); ah["ga"].append(tg); ah["tot"].append(tg); ah["over"].append(ov)
    hh["home_clean"].append(1 if ftag==0 else 0); ah["away_clean"].append(1 if fthg==0 else 0)
    hh["btts_yes"].append(1 if fthg>0 and ftag>0 else 0); ah["btts_yes"].append(1 if fthg>0 and ftag>0 else 0)
    hh["btts_no"].append(1 if fthg==0 or ftag==0 else 0); ah["btts_no"].append(1 if fthg==0 or ftag==0 else 0)
    hh["momentum"].append(1 if tg>ftag else 0.5); ah["momentum"].append(1 if tg>fthg else 0.5)
    hh["recent_form"].append(1 if tg>ftag else 0.5); ah["recent_form"].append(1 if tg>fthg else 0.5)
    if len(hh["gf_last10"])>=2:
        r,o = hh["gf_last10"][-5:], hh["gf_last10"][:-5]
        hh["goal_trend"].append(np.mean(r)-np.mean(o) if o else 0)
    if len(ah["gf_last10"])>=2:
        r,o = ah["gf_last10"][-5:], ah["gf_last10"][:-5]
        ah["goal_trend"].append(np.mean(r)-np.mean(o) if o else 0)


def get_best_model(league):
    """Load the best available model (R1, R2, or R4) for a league."""
    ld = MODEL_DIR / league
    configs = {}
    
    for key, cfg_name, pkl_name in [('R1','optimized_config.json','optimized_pipeline.pkl'),
                                     ('R2','optimized_r2_config.json','optimized_r2_pipeline.pkl'),
                                     ('R4','optimized_r4_config.json','optimized_r4_pipeline.pkl')]:
        cp = ld / cfg_name
        pp = ld / pkl_name
        if cp.exists() and pp.exists():
            with open(cp) as f:
                configs[key] = (json.load(f), pp)
    
    if not configs:
        return None, None, None
    
    # Choose by bet_acc (high-confidence accuracy from training)
    best_key = max(configs.keys(), key=lambda k: configs[k][0].get('bet_acc', 0))
    cfg, pkl_path = configs[best_key]
    pipeline = joblib.load(pkl_path)
    
    return cfg, pipeline, best_key


def evaluate_league(league):
    """Evaluate a league model on recent data."""
    hp = HISTORICAL_DIR / f'{league}_history.csv'
    if not hp.exists():
        return None
    
    df = pd.read_csv(hp, low_memory=False)
    df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
    df = df.dropna(subset=['Date','FTHG','FTAG']).sort_values('Date').reset_index(drop=True)
    
    recent_df = df[df['Date'] >= RECENT_SINCE].copy()
    if len(recent_df) < 3:
        return None
    
    cfg, pipeline, model_key = get_best_model(league)
    if cfg is None:
        return None
    
    feature_list = cfg.get('features', [])
    threshold = cfg.get('threshold', 0.6)
    train_acc = cfg.get('overall_acc', 0)
    bet_acc = cfg.get('bet_acc', 0)
    train_bet_count = cfg.get('bet_count', 0)
    
    # Seed histories with all data before first recent match
    first_recent = recent_df['Date'].min()
    seed_df = df[df['Date'] < first_recent]
    team_hist = seed_histories(seed_df)
    
    # Build features and predict for each recent match
    y_true, y_pred, y_prob, dates = [], [], [], []
    
    for _, match in recent_df.iterrows():
        home, away = str(match["HomeTeam"]), str(match["AwayTeam"])
        hh, ah = team_hist[home], team_hist[away]
        
        row = build_feature_row(hh, ah)
        row["is_over"] = 1 if (float(match['FTHG'])+float(match['FTAG'])) > 2.5 else 0
        
        feat_vec = {}
        for f in feature_list:
            feat_vec[f] = row.get(f, 0.0)
        
        feat_df = pd.DataFrame([feat_vec])
        proba = pipeline.predict_proba(feat_df)[0]
        y_prob.append(proba[1])  # prob of class 1 (over 2.5)
        pred = 1 if proba[1] >= threshold else 0
        y_pred.append(pred)
        y_true.append(row["is_over"])
        dates.append(match["Date"])
        
        # Update histories
        update_histories(hh, ah, float(match['FTHG']), float(match['FTAG']))
    
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)
    y_prob = np.array(y_prob)
    
    # Overall accuracy
    n_total = len(y_true)
    n_correct = (y_pred == y_true).sum()
    overall_acc = n_correct / n_total if n_total > 0 else 0
    
    # High-confidence accuracy (where prob >= threshold)
    high_conf_mask = (y_prob >= threshold) | (y_prob <= (1 - threshold))
    n_hc = high_conf_mask.sum()
    if n_hc > 0:
        hc_acc = (y_pred[high_conf_mask] == y_true[high_conf_mask]).mean()
    else:
        hc_acc = 0.0
    
    # Also check at a stricter threshold (0.7)
    strict_mask = (y_prob >= 0.7) | (y_prob <= 0.3)
    n_strict = strict_mask.sum()
    strict_acc = (y_pred[strict_mask] == y_true[strict_mask]).mean() if n_strict > 0 else 0.0
    
    # Overfitting indicator
    overfit_gap = train_acc - overall_acc
    
    return {
        'league': league,
        'league_name': LEAGUE_NAMES.get(league, league),
        'model_key': model_key,
        'train_overall_acc': train_acc,
        'train_bet_acc': bet_acc,
        'train_bet_count': train_bet_count,
        'recent_overall_acc': overall_acc,
        'recent_hc_acc': hc_acc,
        'recent_strict_acc': strict_acc,
        'n_recent': n_total,
        'n_high_conf': n_hc,
        'n_strict': n_strict,
        'threshold': threshold,
        'overfit_gap': overfit_gap,
    }


if __name__ == '__main__':
    print("="*100)
    print("1year-V1 Optimized Models: Recent Data Evaluation (2025-12-01 onwards)")
    print("="*100)
    
    results = []
    for league in LEAGUES:
        print(f"\nEvaluating {league} ({LEAGUE_NAMES.get(league, league)})...", end=' ', flush=True)
        r = evaluate_league(league)
        if r:
            results.append(r)
            print(f"Done. recent_acc={r['recent_overall_acc']:.1%}, hc_acc={r['recent_hc_acc']:.1%}, n={r['n_recent']}")
        else:
            print(f"No data or no model")
    
    print("\n" + "="*100)
    print("SUMMARY TABLE")
    print("="*100)
    
    header = f"{'League':<12} {'Name':<8} {'Train%':>7} {'TrainHC%':>8} {'Recent%':>8} {'HC Acc%':>7} {'Strict%':>7} {'N':>4} {'N_HC':>5} {'Overfit?':>8}"
    print(header)
    print("-"*100)
    
    for r in sorted(results, key=lambda x: x['recent_overall_acc'], reverse=True):
        overfit_flag = "⚠️ YES" if r['overfit_gap'] > 0.10 else ("△ mild" if r['overfit_gap'] > 0.05 else "✓ no")
        print(f"{r['league']:<12} {r['league_name']:<8} "
              f"{r['train_overall_acc']:>7.1%} "
              f"{r['train_bet_acc']:>8.1%} "
              f"{r['recent_overall_acc']:>8.1%} "
              f"{r['recent_hc_acc']:>7.1%} "
              f"{r['recent_strict_acc']:>7.1%} "
              f"{r['n_recent']:>4} "
              f"{r['n_high_conf']:>5} "
              f"{overfit_flag:>8}")
    
    print("-"*100)
    # Summary stats
    avg_recent = np.mean([r['recent_overall_acc'] for r in results])
    avg_hc = np.mean([r['recent_hc_acc'] for r in results if r['n_high_conf'] > 0])
    n_leagues = len(results)
    print(f"Average recent accuracy across {n_leagues} leagues: {avg_recent:.1%}")
    print(f"Average high-confidence accuracy: {avg_hc:.1%}")
    
    # Save to JSON
    out_path = REPO_ROOT / 'eval_recent_results.json'
    with open(out_path, 'w') as f:
        json.dump(results, f, indent=2, default=str)
    print(f"\nResults saved to {out_path}")
