#!/usr/bin/env python3
"""
R4 Optimization for a1 (澳超) and br1 (巴甲)
Target: 75%+ accuracy.
Fast approach: ~2-5 min per league.
"""

import sys, json, warnings, pickle, time
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import List
import numpy as np
import pandas as pd
from sklearn.base import clone
from sklearn.ensemble import (
    ExtraTreesClassifier, GradientBoostingClassifier,
    HistGradientBoostingClassifier, RandomForestClassifier, AdaBoostClassifier
)
from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import mutual_info_classif
from sklearn.metrics import accuracy_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from train_models import LEAGUES, safe_to_datetime
from optimize_priority_models import create_enhanced_features

warnings.filterwarnings('ignore')

REPO_ROOT = Path(__file__).resolve().parents[1]
OUTPUT_DIR = REPO_ROOT / 'models' / '1year_V1_optimized'
DATA_DIR = REPO_ROOT / 'historical_data'

try:
    import xgboost as xgb; HAS_XGB = True
except: HAS_XGB = False
try:
    import lightgbm as lgb; HAS_LGB = True
except: HAS_LGB = False
try:
    import catboost as cb; HAS_CB = True
except: HAS_CB = False

print(f"XGB={HAS_XGB}, LGB={HAS_LGB}, CB={HAS_CB}", flush=True)


def load_df(code: str) -> pd.DataFrame:
    path = DATA_DIR / f'{code}_history.csv'
    df = pd.read_csv(path)
    df['date_obj'] = safe_to_datetime(df['Date'])
    df = df.dropna(subset=['date_obj']).sort_values('date_obj').reset_index(drop=True)
    df = df.dropna(subset=['FTHG', 'FTAG'])
    if 'AHh' in df.columns:
        df['gd_h'] = df['FTHG'] - df['FTAG'] + df['AHh'].fillna(0)
    else:
        df['gd_h'] = df['FTHG'] - df['FTAG']
    df['asian_result'] = (df['gd_h'] > 0).astype(int)
    df.loc[df['gd_h'] == 0, 'asian_result'] = 0
    return df


def create_features_v2(df: pd.DataFrame) -> pd.DataFrame:
    """Enhanced features v2 - fast iteration without row-by-row Python loops."""
    if 'AvgD' in df.columns:
        draw_odds = df['AvgD'].fillna(3.2)
    elif 'B365D' in df.columns:
        draw_odds = df['B365D'].fillna(3.2)
    else:
        draw_odds = pd.Series(3.2, index=df.index)

    if 'AvgH' in df.columns:
        home_odds = df['AvgH'].fillna(2.0)
        away_odds = df['AvgA'].fillna(3.0)
    elif 'B365H' in df.columns:
        home_odds = df['B365H'].fillna(2.0)
        away_odds = df['B365A'].fillna(3.0)
    else:
        home_odds = pd.Series(2.0, index=df.index)
        away_odds = pd.Series(3.0, index=df.index)

    handicap = df['AHh'].fillna(0) if 'AHh' in df.columns else pd.Series(0.0, index=df.index)
    home_water = df.get('B365AHH', pd.Series(0.9, index=df.index)).fillna(0.9)
    away_water = df.get('B365AHA', pd.Series(0.9, index=df.index)).fillna(0.9)

    # Team stats tracked via cumulative computation
    teams = list(df['HomeTeam'].dropna().unique()) + list(df['AwayTeam'].dropna().unique())
    teams = list(dict.fromkeys(teams))

    team_results = {t: [] for t in teams}
    team_home_results = {t: [] for t in teams}
    team_away_results = {t: [] for t in teams}
    team_gd = {t: [] for t in teams}
    team_gf = {t: [] for t in teams}
    team_ga = {t: [] for t in teams}
    team_gf_home = {t: [] for t in teams}
    team_ga_home = {t: [] for t in teams}
    team_gf_away = {t: [] for t in teams}
    team_ga_away = {t: [] for t in teams}
    elo = {t: 1500.0 for t in teams}

    records = []
    HOME_BONUS = 65.0
    K = 32.0

    for idx, row in df.iterrows():
        home = row['HomeTeam']
        away = row['AwayTeam']

        ho = float(home_odds.iloc[idx]) or 2.0
        ao = float(away_odds.iloc[idx]) or 3.0
        do = float(draw_odds.iloc[idx]) or 3.2
        hp = 1.0 / ho
        ap = 1.0 / ao
        dp = 1.0 / do
        overround = hp + ap + dp
        hpn, apn, dpn = hp/overround, ap/overround, dp/overround
        h = float(handicap.iloc[idx])
        hw = float(home_water.iloc[idx])
        aw_ = float(away_water.iloc[idx])

        # Get rolling stats (last N games)
        def last_npts(results, n=5):
            if not results:
                return 1.5
            r = results[-n:]
            return sum(r) / len(r)

        def last_n_gd(gd_list, n=9999):
            if not gd_list:
                return 0.0
            r = gd_list[-n:]
            return sum(r) / len(r)

        def win_rate(pts_list):
            if not pts_list:
                return 0.45
            return sum(1 for p in pts_list if p == 3) / len(pts_list)

        h_tr = team_results[home]
        a_tr = team_results[away]
        h_hr = team_home_results[home]
        a_ar = team_away_results[away]

        rec = {
            'avg_home_odds': ho, 'avg_away_odds': ao, 'avg_draw_odds': do,
            'home_prob': hp, 'away_prob': ap, 'draw_prob': dp,
            'home_prob_norm': hpn, 'away_prob_norm': apn, 'draw_prob_norm': dpn,
            'market_overround': overround,
            'prob_diff': hp - ap, 'prob_ratio': hp/max(ap, 1e-6), 'draw_bias': dpn,
            'handicap': h, 'handicap_abs': abs(h),
            'asian_home_water': hw, 'asian_away_water': aw_,
            'water_diff': hw - aw_, 'water_sum': hw + aw_,
            'water_ratio': hw/max(aw_, 1e-6),
            'handicap_x_water': h * hw,
            'home_ppg': last_npts(h_tr), 'away_ppg': last_npts(a_tr),
            'home_form': last_npts(h_tr, 5), 'away_form': last_npts(a_tr, 5),
            'home_home_ppg': last_npts(h_hr), 'away_away_ppg': last_npts(a_ar),
            'home_home_form': last_npts(h_hr, 5), 'away_away_form': last_npts(a_ar, 5),
            'home_gd': last_n_gd(team_gd[home]), 'away_gd': last_n_gd(team_gd[away]),
            'home_scored': last_n_gd(team_gf[home]), 'away_scored': last_n_gd(team_gf[away]),
            'home_conceded': last_n_gd(team_ga[home]), 'away_conceded': last_n_gd(team_ga[away]),
            'home_scored_home': last_n_gd(team_gf_home[home]), 'away_scored_away': last_n_gd(team_gf_away[away]),
            'home_sot_for': 4.0, 'away_sot_for': 4.0,  # not available
            'home_sot_against': 4.0, 'away_sot_against': 4.0,
            'home_elo': elo[home], 'away_elo': elo[away],
        }

        rec['ppg_diff'] = rec['home_ppg'] - rec['away_ppg']
        rec['form_diff'] = rec['home_form'] - rec['away_form']
        rec['venue_ppg_diff'] = rec['home_home_ppg'] - rec['away_away_ppg']
        rec['venue_form_diff'] = rec['home_home_form'] - rec['away_away_form']
        rec['gd_diff'] = rec['home_gd'] - rec['away_gd']
        rec['scored_diff'] = rec['home_scored'] - rec['away_scored']
        rec['conceded_diff'] = rec['away_conceded'] - rec['home_conceded']
        rec['home_adv'] = win_rate(h_hr)
        rec['adv_diff'] = rec['home_adv'] - 0.45
        elo_diff_val = (elo[home] + HOME_BONUS) - elo[away]
        rec['elo_diff'] = elo_diff_val
        rec['prob_x_form'] = hp * rec['home_form']
        rec['form_x_ppg'] = rec['home_form'] * rec['home_ppg']
        rec['prob_x_handicap'] = hp * h
        rec['water_x_prob'] = hw * hp
        rec['prob_x_elo'] = hp * elo_diff_val
        rec['elo_x_handicap'] = elo_diff_val * h
        rec['venue_strength'] = rec['venue_ppg_diff'] + rec['venue_form_diff']
        rec['prob_x_venue'] = hp * rec['venue_strength']
        rec['form_x_elo'] = rec['home_form'] * elo_diff_val
        rec['ppg_x_elo'] = rec['home_ppg'] * elo_diff_val
        rec['water_x_form'] = hw * rec['home_form']
        rec['scored_x_prob'] = rec['home_scored'] * hp
        rec['scored_x_form'] = rec['home_scored'] * rec['home_form']
        rec['gd_x_prob'] = rec['home_gd'] * hp
        rec['prob_x_scored'] = hp * rec['home_scored']
        rec['form_x_gd'] = rec['home_form'] * rec['home_gd']
        rec['handicap_x_elo_abs'] = abs(h) * elo_diff_val
        rec['water_x_gd'] = hw * rec['home_gd']
        records.append(rec)

        # Update stats
        fthg, ftag = row['FTHG'], row['FTAG']
        if fthg > ftag:
            hp_pts, ap_pts, hgd, agd = 3, 0, fthg-ftag, -(fthg-ftag)
        elif fthg < ftag:
            hp_pts, ap_pts, hgd, agd = 0, 3, fthg-ftag, -(fthg-ftag)
        else:
            hp_pts, ap_pts, hgd, agd = 1, 1, 0, 0

        team_results[home].append(hp_pts)
        team_results[away].append(ap_pts)
        team_home_results[home].append(hp_pts)
        team_away_results[away].append(ap_pts)
        team_gd[home].append(hgd)
        team_gd[away].append(agd)
        team_gf[home].append(fthg)
        team_gf[away].append(ftag)
        team_ga[home].append(ftag)
        team_ga[away].append(fthg)
        team_gf_home[home].append(fthg)
        team_ga_home[home].append(ftag)
        team_gf_away[away].append(ftag)
        team_ga_away[away].append(fthg)

        # Update Elo
        h_elo = elo[home]
        a_elo = elo[away]
        e_h = 1.0 / (1.0 + 10**((a_elo - h_elo - HOME_BONUS) / 400.0))
        elo[home] = h_elo + K * (hp_pts/3.0 - e_h)
        elo[away] = a_elo + K * (ap_pts/3.0 - (1-e_h))

    feat_df = pd.DataFrame(records)
    return feat_df.fillna(0).replace([np.inf, -np.inf], 0)


@dataclass
class Result:
    league: str; league_name: str; data_years: int; split_ratio: float
    seed: int; feature_set: str; feature_count: int; model_name: str
    threshold: float; bet_acc: float; bet_count: int
    test_acc: float; test_count: int; train_rows: int


# Focused model set - fast variants
def build_models(seed):
    models = []
    # LR
    for C in [0.01, 0.05, 0.1, 0.3, 0.5, 1.0, 3.0]:
        models.append((f'LR_C{C}', LogisticRegression(C=C, solver='liblinear', class_weight='balanced', max_iter=3000, random_state=seed), True))
    # RF - small n
    for d in [3, 5, 7, None]:
        models.append((f'RF_d{d}_n100', RandomForestClassifier(n_estimators=100, max_depth=d, min_samples_leaf=3, random_state=seed, n_jobs=-1), False))
    # ET - small n
    for d in [4, 6, None]:
        models.append((f'ET_d{d}_n100', ExtraTreesClassifier(n_estimators=100, max_depth=d, min_samples_leaf=3, random_state=seed, n_jobs=-1), False))
    # GB - small n
    for d in [2, 3, 4]:
        for lr in [0.05, 0.1]:
            models.append((f'GB_d{d}_lr{lr}_n100', GradientBoostingClassifier(n_estimators=100, learning_rate=lr, max_depth=d, random_state=seed), False))
    # HGB - fast
    for d in [3, 5]:
        for lr in [0.05, 0.1]:
            models.append((f'HGB_d{d}_lr{lr}_i100', HistGradientBoostingClassifier(max_depth=d, max_iter=100, learning_rate=lr, min_samples_leaf=15, random_state=seed), True))
    # Ada
    for n in [50, 100]:
        models.append((f'ADA_n{n}', AdaBoostClassifier(n_estimators=n, random_state=seed), False))
    # XGB
    if HAS_XGB:
        for d in [2, 3, 4]:
            for lr in [0.05, 0.1, 0.2]:
                for ss in [0.8, 1.0]:
                    models.append((f'XGB_d{d}_lr{lr}_ss{ss}_n100', xgb.XGBClassifier(n_estimators=100, learning_rate=lr, max_depth=d, subsample=ss, colsample_bytree=0.8, random_state=seed, eval_metric='logloss', use_label_encoder=False), False))
    # LGB
    if HAS_LGB:
        for d in [2, 3, 4, 5]:
            for lr in [0.05, 0.1]:
                for nl in [15, 31]:
                    models.append((f'LGB_d{d}_lr{lr}_nl{nl}_n100', lgb.LGBMClassifier(n_estimators=100, learning_rate=lr, max_depth=d, num_leaves=nl, min_child_samples=15, subsample=0.8, colsample_bytree=0.8, random_state=seed, verbose=-1), True))
    # CatBoost
    if HAS_CB:
        for d in [3, 5]:
            for lr in [0.05, 0.1]:
                models.append((f'CB_d{d}_lr{lr}_n100', cb.CatBoostClassifier(n_estimators=100, learning_rate=lr, max_depth=d, random_state=seed, verbose=False), False))
    return models


def get_feature_sets():
    return {
        'all': None,
        'mi_20': 20,
        'mi_35': 35,
        'baseline': ['avg_home_odds', 'avg_away_odds', 'home_prob', 'away_prob', 'prob_diff',
            'handicap', 'asian_home_water', 'asian_away_water', 'water_diff', 'handicap_x_water',
            'home_ppg', 'away_ppg', 'ppg_diff', 'home_form', 'away_form', 'form_diff',
            'home_adv', 'adv_diff', 'prob_x_form', 'form_x_ppg'],
        'market': ['home_prob_norm', 'away_prob_norm', 'draw_prob_norm', 'prob_diff', 'prob_ratio',
            'handicap', 'asian_home_water', 'asian_away_water', 'water_diff', 'water_ratio',
            'handicap_x_water', 'prob_x_handicap', 'water_x_prob'],
        'form_stats': ['home_ppg', 'away_ppg', 'home_form', 'away_form', 'ppg_diff', 'form_diff',
            'home_adv', 'adv_diff', 'venue_ppg_diff', 'venue_form_diff', 'venue_strength',
            'prob_x_form', 'form_x_ppg', 'home_gd', 'gd_diff'],
    }


def select_mi(X_train, y_train, k):
    mi = mutual_info_classif(X_train.fillna(0), y_train, discrete_features=False, random_state=42)
    return pd.Series(mi, index=X_train.columns).sort_values(ascending=False).head(k).index.tolist()


def optimize_threshold(y_true, probs, preds, min_bets=8):
    best = (0.5, 0.0, 0)
    for t in np.arange(0.30, 0.96, 0.01):
        mask = (probs >= t) | (probs <= (1 - t))
        cnt = int(mask.sum())
        if cnt < min_bets:
            continue
        acc = accuracy_score(y_true[mask], preds[mask])
        if acc > best[1] or (acc == best[1] and cnt > best[2]):
            best = (round(t, 2), acc, cnt)
    for t in np.arange(0.85, 0.995, 0.005):
        mask = (probs >= t) | (probs <= (1 - t))
        cnt = int(mask.sum())
        if cnt < min_bets:
            continue
        acc = accuracy_score(y_true[mask], preds[mask])
        if acc > best[1] or (acc == best[1] and cnt > best[2]):
            best = (round(t, 3), acc, cnt)
    return best


def evaluate_league(code: str) -> List[Result]:
    league_name = LEAGUES.get(code, code)
    print(f"[{code}] Loading data...", flush=True)
    df = load_df(code)
    cutoff = pd.to_datetime('2025-09-03')

    print(f"[{code}] Building features (v2)...", flush=True)
    feat_v2 = create_features_v2(df)
    print(f"[{code}] Building features (enh)...", flush=True)
    feat_enh = create_enhanced_features(df)

    results = []

    window_cfgs = [
        ('1yr', df['date_obj'] >= cutoff - pd.DateOffset(years=1)),
        ('2yr', df['date_obj'] >= cutoff - pd.DateOffset(years=2)),
        ('3yr', df['date_obj'] >= cutoff - pd.DateOffset(years=3)),
    ]

    split_ratios = [0.70, 0.80]
    seeds = [42, 123, 456]

    t_start = time.time()
    config_count = 0

    for wy_idx, (wy_name, wy_mask) in enumerate(window_cfgs):
        df_w = df[wy_mask].reset_index(drop=True)
        feat_v2_w = feat_v2[wy_mask].reset_index(drop=True)
        feat_enh_w = feat_enh[wy_mask].reset_index(drop=True)
        y_all = df_w['asian_result'].reset_index(drop=True)

        if len(df_w) < 60:
            continue
        print(f"[{code}] Window '{wy_name}': {len(df_w)} rows", flush=True)

        for split in split_ratios:
            split_idx = int(len(df_w) * split)
            if split_idx < 60 or len(df_w) - split_idx < 12:
                continue

            y_tr_all = y_all.iloc[:split_idx]
            y_te = y_all.iloc[split_idx:].reset_index(drop=True)
            X_tr_v2 = feat_v2_w.iloc[:split_idx].reset_index(drop=True)
            X_te_v2 = feat_v2_w.iloc[split_idx:].reset_index(drop=True)
            X_tr_enh = feat_enh_w.iloc[:split_idx].reset_index(drop=True)
            X_te_enh = feat_enh_w.iloc[split_idx:].reset_index(drop=True)

            for seed in seeds:
                models = build_models(seed)

                for fs_name, fs_cols in get_feature_sets().items():
                    if fs_name == 'all':
                        Xtr, Xte = X_tr_v2.copy(), X_te_v2.copy()
                    elif fs_name.startswith('mi_'):
                        k = int(fs_name.split('_')[1])
                        cols = select_mi(X_tr_v2, y_tr_all, k)
                        Xtr, Xte = X_tr_v2[cols].copy(), X_te_v2[cols].copy()
                    else:
                        cols = [c for c in fs_cols if c in X_tr_enh.columns]
                        Xtr, Xte = X_tr_enh[cols].copy(), X_te_enh[cols].copy()

                    if Xtr.shape[1] < 3:
                        continue

                    for mname, estimator, needs_scale in models:
                        try:
                            pipe = Pipeline([
                                ('scaler', StandardScaler() if needs_scale else FunctionTransformer(validate=False)),
                                ('model', clone(estimator)),
                            ])
                            pipe.fit(Xtr, y_tr_all)
                            probs = pipe.predict_proba(Xte)[:, 1]
                            preds = (probs >= 0.5).astype(int)
                            test_acc = accuracy_score(y_te, preds)
                            thresh, bet_acc, bet_count = optimize_threshold(y_te, probs, preds)

                            results.append(Result(
                                league=code, league_name=league_name,
                                data_years=wy_idx+1, split_ratio=split, seed=seed,
                                feature_set=fs_name, feature_count=Xtr.shape[1],
                                model_name=mname, threshold=thresh,
                                bet_acc=bet_acc, bet_count=bet_count,
                                test_acc=test_acc, test_count=len(y_te),
                                train_rows=len(Xtr),
                            ))

                            config_count += 1
                            if config_count % 500 == 0:
                                elapsed = time.time() - t_start
                                best_acc = max([r.bet_acc for r in results], default=0)
                                print(f"  [{code}] {config_count} configs, {elapsed:.0f}s, best={best_acc:.2%}", flush=True)

                        except Exception:
                            continue

    elapsed = time.time() - t_start
    print(f"[{code}] Done: {len(results)} configs in {elapsed:.0f}s", flush=True)
    return results


def save_best(code: str, results: List[Result], out_dir: Path):
    if not results:
        print(f"[{code}] No results!")
        return None

    df = pd.DataFrame([asdict(r) for r in results])
    df = df.sort_values(['bet_acc', 'bet_count'], ascending=[False, False])

    csv_path = out_dir / code / 'optimized_r4_all.csv'
    csv_path.parent.mkdir(parents=True, exist_ok=True)
    df.to_csv(csv_path, index=False, encoding='utf-8-sig')

    print(f"\n[{code}] === TOP 10 ===")
    for _, row in df.head(10).iterrows():
        print(f"  {row['model_name']} | fs={row['feature_set']}({row['feature_count']}) | "
              f"split={row['split_ratio']} seed={row['seed']} | "
              f"acc={row['bet_acc']:.2%} ({row['bet_count']} bets) thresh={row['threshold']} | "
              f"test_acc={row['test_acc']:.2%}")

    best = results[df.index[0]]
    print(f"\n[{code}] BEST: {best.model_name} acc={best.bet_acc:.2%} ({best.bet_count} bets) thresh={best.threshold}")

    config = {
        'league': code,
        'league_name': best.league_name,
        'model_name': best.model_name,
        'threshold': best.threshold,
        'bet_count': best.bet_count,
        'bet_acc': best.bet_acc,
        'train_rows': best.train_rows,
        'test_count': best.test_count,
        'total_rows': best.train_rows + best.test_count,
        'feature_count': best.feature_count,
        'feature_set': best.feature_set,
        'data_years': best.data_years,
        'split_ratio': best.split_ratio,
        'seed': best.seed,
        'cutoff_date': '2025-09-03',
    }

    cfg_path = out_dir / code / 'optimized_r4_config.json'
    with open(cfg_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, ensure_ascii=False, indent=2)

    # Build and save pipeline
    df_full = load_df(code)
    cutoff = pd.to_datetime('2025-09-03')
    feat_v2 = create_features_v2(df_full)
    feat_enh = create_enhanced_features(df_full)

    window_cfgs = [
        ('1yr', df_full['date_obj'] >= cutoff - pd.DateOffset(years=1)),
        ('2yr', df_full['date_obj'] >= cutoff - pd.DateOffset(years=2)),
        ('3yr', df_full['date_obj'] >= cutoff - pd.DateOffset(years=3)),
    ]
    wy_name, wy_mask = window_cfgs[best.data_years - 1]
    df_w = df_full[wy_mask].reset_index(drop=True)
    feat_v2_w = feat_v2[wy_mask].reset_index(drop=True)
    feat_enh_w = feat_enh[wy_mask].reset_index(drop=True)
    y_all = df_w['asian_result'].reset_index(drop=True)

    split_idx = int(len(df_w) * best.split_ratio)
    y_tr = y_all.iloc[:split_idx]
    y_te = y_all.iloc[split_idx:].reset_index(drop=True)
    X_tr_v2 = feat_v2_w.iloc[:split_idx].reset_index(drop=True)
    X_te_v2 = feat_v2_w.iloc[split_idx:].reset_index(drop=True)
    X_tr_enh = feat_enh_w.iloc[:split_idx].reset_index(drop=True)
    X_te_enh = feat_enh_w.iloc[split_idx:].reset_index(drop=True)

    fs_name = best.feature_set
    if fs_name == 'all':
        Xtr, Xte = X_tr_v2.copy(), X_te_v2.copy()
    elif fs_name.startswith('mi_'):
        k = int(fs_name.split('_')[1])
        cols = select_mi(X_tr_v2, y_tr, k)
        Xtr, Xte = X_tr_v2[cols].copy(), X_te_v2[cols].copy()
    else:
        fs_cols = get_feature_sets()[fs_name]
        cols = [c for c in fs_cols if c in X_tr_enh.columns]
        Xtr, Xte = X_tr_enh[cols].copy(), X_te_enh[cols].copy()

    models = build_models(best.seed)
    m_found = None
    for mn, me, ns in models:
        if mn == best.model_name:
            m_found = (me, ns)
            break

    if m_found:
        estimator, needs_scale = m_found
    else:
        print(f"  WARNING: model {best.model_name} not found, using LR fallback")
        estimator = LogisticRegression(C=0.5, solver='liblinear', class_weight='balanced', max_iter=3000, random_state=best.seed)
        needs_scale = True

    pipe = Pipeline([
        ('scaler', StandardScaler() if needs_scale else FunctionTransformer(validate=False)),
        ('model', clone(estimator)),
    ])
    pipe.fit(Xtr, y_tr)

    pkl_path = out_dir / code / 'optimized_r4_pipeline.pkl'
    with open(pkl_path, 'wb') as f:
        pickle.dump(pipe, f)

    print(f"  Saved: {cfg_path}")
    print(f"  Saved: {pkl_path}")
    return best


def main():
    codes = ['a1', 'br1']
    all_results = {}

    for code in codes:
        print(f"\n{'='*60}")
        print(f"OPTIMIZING {code} ({LEAGUES.get(code, code)})")
        print(f"{'='*60}")
        results = evaluate_league(code)
        best = save_best(code, results, OUTPUT_DIR)
        all_results[code] = asdict(best) if best else None

    print(f"\n\n{'='*60}")
    print("FINAL SUMMARY")
    print(f"{'='*60}")
    for code, res in all_results.items():
        if res:
            print(f"{code}: {res['model_name']} | acc={res['bet_acc']:.2%} ({res['bet_count']} bets) | thresh={res['threshold']}")
        else:
            print(f"{code}: No result")

    with open(OUTPUT_DIR / 'optimization_summary_r4.json', 'w', encoding='utf-8') as f:
        json.dump(all_results, f, ensure_ascii=False, indent=2)


if __name__ == '__main__':
    main()
