#!/usr/bin/env python3
"""Train league-specific goal-number models with feature selection and hyperparameter tuning.

Each league gets its own:
1. Feature selection (different feature combinations)
2. Hyperparameter tuning
3. Model selection
4. Target: 80%+ bet accuracy
"""

from __future__ import annotations

import json
from collections import defaultdict
from pathlib import Path
from typing import List, Dict, Tuple, Optional

import joblib
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split, GridSearchCV, TimeSeriesSplit
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, RobustScaler, MinMaxScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.impute import SimpleImputer

REPO_ROOT = Path(__file__).resolve().parents[1]
HISTORICAL_DIR = REPO_ROOT / 'historical_data'
OUTPUT_DIR = REPO_ROOT / 'experiments' / 'goal_number_v3'

LEAGUES = {
    'a1': {'name': '澳超', 'min_rows': 1200},
    'br1': {'name': '巴甲', 'min_rows': 3000},
    'd2': {'name': '德乙', 'min_rows': 2400},
    'e0': {'name': '英超', 'min_rows': 2900},
    'f1': {'name': '法甲', 'min_rows': 1280},
    'f2': {'name': '法乙', 'min_rows': 2800},
    'g1': {'name': '希腊超', 'min_rows': 1870},
    'j1': {'name': '日职联', 'min_rows': 2620},
    'm1': {'name': '美职联', 'min_rows': 3500},
    'r1': {'name': '俄超', 'min_rows': 1870},
    'uz1': {'name': '乌兹别克超', 'min_rows': 780},
}


def mean_or_default(values: List[float], default: float) -> float:
    return float(np.mean(values)) if values else float(default)


def build_all_features(df: pd.DataFrame) -> pd.DataFrame:
    """Build all possible features for feature selection."""
    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": [],
        "momentum": [],
    })
    
    rows = []
    
    for idx, match in df.iterrows():
        home = str(match["HomeTeam"])
        away = str(match["AwayTeam"])
        home_hist = team_hist[home]
        away_hist = team_hist[away]
        
        total_goals = float(match.get('FTHG', 0) or 0) + float(match.get('FTAG', 0) or 0)
        is_over = 1 if total_goals > 2.5 else 0
        
        # Basic stats
        row = {}
        
        # Window-based averages
        for window in [3, 5, 10]:
            row[f"home_gf_last{window}"] = mean_or_default(home_hist[f"gf_last{window}"], 1.4)
            row[f"home_ga_last{window}"] = mean_or_default(home_hist[f"ga_last{window}"], 1.4)
            row[f"away_gf_last{window}"] = mean_or_default(away_hist[f"gf_last{window}"], 1.2)
            row[f"away_ga_last{window}"] = mean_or_default(away_hist[f"ga_last{window}"], 1.2)
            row[f"home_over_last{window}"] = mean_or_default(home_hist[f"over_last{window}"], 0.5)
            row[f"away_over_last{window}"] = mean_or_default(away_hist[f"over_last{window}"], 0.5)
        
        # Overall averages
        row["home_gf_avg"] = mean_or_default(home_hist["gf"], 1.4)
        row["home_ga_avg"] = mean_or_default(home_hist["ga"], 1.4)
        row["away_gf_avg"] = mean_or_default(away_hist["gf"], 1.2)
        row["away_ga_avg"] = mean_or_default(away_hist["ga"], 1.2)
        row["home_tot_avg"] = mean_or_default(home_hist["tot"], 2.6)
        row["away_tot_avg"] = mean_or_default(away_hist["tot"], 2.6)
        row["home_over_rate"] = mean_or_default(home_hist["over"], 0.5)
        row["away_over_rate"] = mean_or_default(away_hist["over"], 0.5)
        
        # Home/Away specific
        row["home_home_gf_avg"] = mean_or_default(home_hist["home_gf"], mean_or_default(home_hist["gf"], 1.4))
        row["home_home_ga_avg"] = mean_or_default(home_hist["home_ga"], mean_or_default(home_hist["ga"], 1.4))
        row["away_away_gf_avg"] = mean_or_default(away_hist["away_gf"], mean_or_default(away_hist["gf"], 1.2))
        row["away_away_ga_avg"] = mean_or_default(away_hist["away_ga"], mean_or_default(away_hist["ga"], 1.2))
        row["home_home_over_rate"] = mean_or_default(home_hist["home_over"], mean_or_default(home_hist["over"], 0.5))
        row["away_away_over_rate"] = mean_or_default(away_hist["away_over"], mean_or_default(away_hist["over"], 0.5))
        
        # Variance/consistency
        if len(home_hist["gf_last10"]) >= 2:
            row["home_gf_var"] = np.var(home_hist["gf_last10"])
            row["home_ga_var"] = np.var(home_hist["ga_last10"])
        else:
            row["home_gf_var"] = 0.5
            row["home_ga_var"] = 0.5
        
        if len(away_hist["gf_last10"]) >= 2:
            row["away_gf_var"] = np.var(away_hist["gf_last10"])
            row["away_ga_var"] = np.var(away_hist["ga_last10"])
        else:
            row["away_gf_var"] = 0.5
            row["away_ga_var"] = 0.5
        
        # Momentum
        row["home_momentum"] = np.mean(home_hist["momentum"][-5:]) if len(home_hist["momentum"]) >= 5 else 0.5
        row["away_momentum"] = np.mean(away_hist["momentum"][-5:]) if len(away_hist["momentum"]) >= 5 else 0.5
        
        # Expected goals
        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
        
        # Difference features
        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"]
        
        # Form ratio
        row["form_ratio"] = row["home_momentum"] / (row["away_momentum"] + 0.01)
        
        # Odds features (if available)
        for odds_prefix in ['B365', 'Avg', 'PS', 'Max']:
            over_col = f'{odds_prefix}>2.5'
            under_col = f'{odds_prefix}<2.5'
            
            if over_col in df.columns and under_col in df.columns:
                over_odds = float(match.get(over_col, np.nan))
                under_odds = float(match.get(under_col, np.nan))
                
                if pd.notna(over_odds) and pd.notna(under_odds):
                    row[f"{odds_prefix.lower()}_over_odds"] = over_odds
                    row[f"{odds_prefix.lower()}_under_odds"] = under_odds
                    
                    over_prob = 1.0 / over_odds
                    under_prob = 1.0 / under_odds
                    row[f"{odds_prefix.lower()}_over_prob"] = over_prob
                    row[f"{odds_prefix.lower()}_under_prob"] = under_prob
                    
                    fair_over = over_prob / (over_prob + under_prob)
                    row[f"{odds_prefix.lower()}_fair_over"] = fair_over
                    row[f"{odds_prefix.lower()}_margin"] = (over_prob + under_prob - 1.0)
                    
                    # Only use one odds source to reduce multicollinearity
                    break
        
        # 1X2 odds
        for odds_prefix in ['B365', 'Avg', 'PS', 'Max']:
            home_col = f'{odds_prefix}H'
            draw_col = f'{odds_prefix}D'
            away_col = f'{odds_prefix}A'
            
            if home_col in df.columns and draw_col in df.columns and away_col in df.columns:
                home_odds = float(match.get(home_col, np.nan))
                draw_odds = float(match.get(draw_col, np.nan))
                away_odds = float(match.get(away_col, np.nan))
                
                if pd.notna(home_odds) and pd.notna(draw_odds) and pd.notna(away_odds):
                    row[f"{odds_prefix.lower()}_home_odds"] = home_odds
                    row[f"{odds_prefix.lower()}_draw_odds"] = draw_odds
                    row[f"{odds_prefix.lower()}_away_odds"] = away_odds
                    
                    home_prob = 1.0 / home_odds
                    draw_prob = 1.0 / draw_odds
                    away_prob = 1.0 / away_odds
                    prob_sum = home_prob + draw_prob + away_prob
                    
                    if prob_sum > 0:
                        row[f"{odds_prefix.lower()}_home_prob"] = home_prob / prob_sum
                        row[f"{odds_prefix.lower()}_draw_prob"] = draw_prob / prob_sum
                        row[f"{odds_prefix.lower()}_away_prob"] = away_prob / prob_sum
                        row[f"{odds_prefix.lower()}_home_away_gap"] = (home_prob - away_prob)
                        row[f"{odds_prefix.lower()}_home_draw_gap"] = (home_prob - draw_prob)
                        row[f"{odds_prefix.lower()}_draw_away_gap"] = (draw_prob - away_prob)
                    
                    # Only use one odds source
                    break
        
        row["is_over"] = is_over
        rows.append(row)
        
        # Update team history
        home_hist["gf"].append(total_goals)
        home_hist["ga"].append(total_goals)
        home_hist["tot"].append(total_goals)
        home_hist["over"].append(is_over)
        home_hist["gf_last3"].append(total_goals)
        home_hist["ga_last3"].append(total_goals)
        home_hist["gf_last5"].append(total_goals)
        home_hist["ga_last5"].append(total_goals)
        home_hist["gf_last10"].append(total_goals)
        home_hist["ga_last10"].append(total_goals)
        home_hist["over_last3"].append(is_over)
        home_hist["over_last5"].append(is_over)
        home_hist["over_last10"].append(is_over)
        
        home_result = 1 if total_goals > (match.get('FTAG', 0) or 0) else 0.5
        home_hist["momentum"].append(home_result)
        
        if pd.notna(match.get('FTHG')) and pd.notna(match.get('FTAG')):
            home_hist["home_gf"].append(float(match['FTHG']))
            home_hist["home_ga"].append(float(match['FTAG']))
            home_hist["home_over"].append(is_over)
        
        away_hist["gf"].append(total_goals)
        away_hist["ga"].append(total_goals)
        away_hist["tot"].append(total_goals)
        away_hist["over"].append(is_over)
        away_hist["gf_last3"].append(total_goals)
        away_hist["ga_last3"].append(total_goals)
        away_hist["gf_last5"].append(total_goals)
        away_hist["ga_last5"].append(total_goals)
        away_hist["gf_last10"].append(total_goals)
        away_hist["ga_last10"].append(total_goals)
        away_hist["over_last3"].append(is_over)
        away_hist["over_last5"].append(is_over)
        away_hist["over_last10"].append(is_over)
        
        away_result = 1 if total_goals > (match.get('FTHG', 0) or 0) else 0.5
        away_hist["momentum"].append(away_result)
        
        if pd.notna(match.get('FTHG')) and pd.notna(match.get('FTAG')):
            away_hist["away_gf"].append(float(match['FTHG']))
            away_hist["away_ga"].append(float(match['FTAG']))
            away_hist["away_over"].append(is_over)
    
    return pd.DataFrame(rows)


def get_feature_groups(df: pd.DataFrame) -> List[List[str]]:
    """Define different feature groups for feature selection."""
    all_cols = [c for c in df.columns if c != 'is_over']
    
    # Remove columns with too many NaN
    nan_ratio = df[all_cols].isna().mean()
    valid_cols = [c for c, ratio in zip(all_cols, nan_ratio) if ratio < 0.7]
    
    # Group 1: Basic stats only
    group1 = [
        'home_gf_avg', 'home_ga_avg', 'away_gf_avg', 'away_ga_avg',
        'home_tot_avg', 'away_tot_avg', 'home_over_rate', 'away_over_rate',
        'combined_over_rate'
    ]
    
    # Group 2: Basic + momentum
    group2 = group1 + ['home_momentum', 'away_momentum']
    
    # Group 3: Basic + rolling windows
    group3 = group1 + ['home_gf_last5', 'home_ga_last5', 'away_gf_last5', 'away_ga_last5']
    
    # Group 4: Basic + variance
    group4 = group1 + ['home_gf_var', 'home_ga_var', 'away_gf_var', 'away_ga_var']
    
    # Group 5: Basic + expected goals
    group5 = group1 + ['expected_goals_proxy']
    
    # Group 6: Basic + difference features
    group6 = group1 + ['home_away_gf_diff', 'home_away_ga_diff', 'home_away_tot_diff']
    
    # Group 7: Comprehensive (most features)
    group7 = [
        'home_gf_avg', 'home_ga_avg', 'away_gf_avg', 'away_ga_avg',
        'home_tot_avg', 'away_tot_avg', 'home_over_rate', 'away_over_rate',
        'home_gf_last5', 'home_ga_last5', 'away_gf_last5', 'away_ga_last5',
        'home_momentum', 'away_momentum',
        'home_gf_var', 'home_ga_var', 'away_gf_var', 'away_ga_var',
        'expected_goals_proxy', 'combined_over_rate'
    ]
    
    # Group 8: Odds features only (if available)
    odds_cols = [c for c in valid_cols if 'odds' in c.lower()]
    group8 = odds_cols[:10] if odds_cols else []
    
    # Group 9: Comprehensive + odds
    group9 = group7 + group8
    
    # Only include features that exist
    groups = []
    for group in [group1, group2, group3, group4, group5, group6, group7, group8, group9]:
        valid_group = [c for c in group if c in valid_cols and c != 'is_over']
        if valid_group:
            groups.append(valid_group)
    
    return groups


def train_league_model_v3(league_code: str, league_config: dict) -> dict | None:
    """Train a league-specific model with feature selection and hyperparameter tuning."""
    print(f"\n{'='*70}")
    print(f"Training optimized model for {league_code} ({league_config['name']})")
    print(f"{'='*70}")
    
    # Load historical data
    history_path = HISTORICAL_DIR / f'{league_code}_history.csv'
    if not history_path.exists():
        print(f"  [SKIP] History file not found: {history_path}")
        return None
    
    df = pd.read_csv(history_path)
    print(f"  Loaded {len(df)} historical matches")
    
    if len(df) < league_config['min_rows']:
        print(f"  [SKIP] Not enough data: {len(df)} < {league_config['min_rows']}")
        return None
    
    # Build features
    print("  Building all features...")
    features_df = build_all_features(df)
    print(f"  Built {len(features_df)} feature rows with {len(features_df.columns)} features")
    
    # Get feature groups
    print("  Generating feature groups...")
    original_feature_groups = get_feature_groups(features_df)
    print(f"  Generated {len(original_feature_groups)} feature groups")
    
    # Time-based split
    if 'Date' in df.columns:
        df['Date_dt'] = pd.to_datetime(df['Date'], errors='coerce')
        df_sorted = df.sort_values('Date_dt').reset_index(drop=True)
        split_idx = int(len(df_sorted) * 0.8)
        
        features_sorted = features_df.iloc[df_sorted.index].reset_index(drop=True)
    else:
        from sklearn.model_selection import train_test_split
        split_idx = int(len(features_df) * 0.8)
        features_sorted = features_df
        df_sorted = df
    
    # Apply feature groups to sorted data
    feature_groups = []
    for group in original_feature_groups:
        valid_group = [c for c in group if c in features_sorted.columns]
        if valid_group:
            feature_groups.append(valid_group)
    
    print(f"  Generated {len(feature_groups)} feature groups")
    
    best_config = None
    best_model = None
    best_test_acc = 0.0
    
    print(f"\n  Testing {len(feature_groups)} feature groups with hyperparameter tuning...")
    
    for group_idx, feature_cols in enumerate(feature_groups, 1):
        print(f"\n  Feature Group {group_idx}: {len(feature_cols)} features")
        
        if len(feature_cols) < 5:
            print(f"    [SKIP] Not enough features")
            continue
        
        # Prepare data
        X = features_sorted[feature_cols].fillna(0)
        y = features_sorted['is_over'].astype(int)
        
        X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:]
        y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:]
        
        print(f"    Train: {len(X_train)}, Test: {len(X_test)}")
        print(f"    Over rate: {y.mean():.2%}")
        
        # Define models with hyperparameter grids
        model_configs = []
        
        # RandomForest
        rf_params = {
            'clf__n_estimators': [100, 200, 300],
            'clf__max_depth': [10, 15, 20],
            'clf__min_samples_leaf': [2, 5, 10],
        }
        rf_pipe = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', RandomForestClassifier(random_state=42, n_jobs=-1, class_weight='balanced'))
        ])
        model_configs.append(('RandomForest', rf_pipe, rf_params))
        
        # ExtraTrees
        et_params = {
            'clf__n_estimators': [200, 300],
            'clf__max_depth': [15, 20],
            'clf__min_samples_leaf': [2, 5],
        }
        et_pipe = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', ExtraTreesClassifier(random_state=42, n_jobs=-1, class_weight='balanced'))
        ])
        model_configs.append(('ExtraTrees', et_pipe, et_params))
        
        # GradientBoosting
        gb_params = {
            'clf__n_estimators': [100, 200],
            'clf__max_depth': [5, 8],
            'clf__learning_rate': [0.01, 0.05, 0.1],
        }
        gb_pipe = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', GradientBoostingClassifier(random_state=42))
        ])
        model_configs.append(('GradientBoosting', gb_pipe, gb_params))
        
        # LogisticRegression
        lr_params = {
            'clf__C': [0.01, 0.1, 1.0],
        'clf__penalty': ['l2'],
            'clf__solver': ['liblinear'],
        }
        lr_pipe = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', StandardScaler()),
            ('clf', LogisticRegression(max_iter=1000, random_state=42))
        ])
        model_configs.append(('LogisticRegression', lr_pipe, lr_params))
        
        # Test models
        for model_name, pipe, param_grid in model_configs:
            try:
                print(f"\n    Testing {model_name}...")
                
                # Grid Search
                grid = GridSearchCV(
                    pipe,
                    param_grid,
                    cv=3,
                    scoring='accuracy',
                    n_jobs=-1,
                    verbose=0
                )
                
                grid.fit(X_train, y_train)
                
                # Evaluate
                best_pipe = grid.best_estimator_
                y_pred = best_pipe.predict(X_test)
                overall_acc = accuracy_score(y_test, y_pred)
                
                # Test thresholds
                probs = best_pipe.predict_proba(X_test)[:, 1]
                thresholds = np.arange(0.45, 0.86, 0.01)
                
                for threshold in thresholds:
                    mask = (probs >= threshold) | (probs <= (1.0 - threshold))
                    bet_count = int(mask.sum())
                    
                    if bet_count < 25:
                        continue
                    
                    preds = (probs >= threshold).astype(int)
                    bet_acc = accuracy_score(y_test[mask], preds[mask])
                    
                    if bet_acc > best_test_acc:
                        best_test_acc = bet_acc
                        best_config = {
                            'league': league_code,
                            'league_name': league_config['name'],
                            'model': model_name,
                            'threshold': float(threshold),
                            'bet_count': bet_count,
                            'bet_acc': float(bet_acc),
                            'overall_acc': float(overall_acc),
                            'f1_score': float(f1_score(y_test[mask], preds[mask])),
                            'precision': float(precision_score(y_test[mask], preds[mask])),
                            'recall': float(recall_score(y_test[mask], preds[mask])),
                            'rows': int(len(features_sorted)),
                            'train_rows': int(len(X_train)),
                            'test_rows': int(len(X_test)),
                            'features': feature_cols,
                            'feature_count': len(feature_cols),
                            'feature_group': group_idx,
                            'best_params': grid.best_params_,
                        }
                        
                        print(f"      New best! Threshold={threshold:.2f}, Bet Acc={bet_acc:.2%}, Bet Count={bet_count}")
                
            except Exception as e:
                print(f"    {model_name}: Error - {e}")
                continue
        
        if best_config and best_config['bet_acc'] >= 0.80:
            print(f"\n  [SUCCESS] Target reached! Accuracy: {best_config['bet_acc']:.2%}")
            break
    
    if not best_config:
        print(f"  [WARNING] No model met requirements")
        return None
    
    print(f"\n  {'='*60}")
    print(f"  FINAL MODEL")
    print(f"  {'='*60}")
    print(f"    League: {best_config['league']} ({best_config['league_name']})")
    print(f"    Model: {best_config['model']}")
    print(f"    Feature Group: {best_config['feature_group']}")
    print(f"    Threshold: {best_config['threshold']:.2f}")
    print(f"    Bet count: {best_config['bet_count']}")
    print(f"    Bet accuracy: {best_config['bet_acc']:.2%}")
    print(f"    Overall accuracy: {best_config['overall_acc']:.2%}")
    print(f"    F1 score: {best_config['f1_score']:.2%}")
    print(f"    Features: {best_config['feature_count']}")
    print(f"    Best params: {best_config['best_params']}")
    print(f"  {'='*60}")
    
    # Save model
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    model_dir = OUTPUT_DIR / league_code
    model_dir.mkdir(parents=True, exist_ok=True)
    
    model_path = model_dir / 'goal_number_pipeline.pkl'
    config_path = model_dir / 'goal_number_config.json'
    
    # Re-train final model on full training data with best params
    best_pipe_name = best_config['model']
    if 'RandomForest' in best_pipe_name:
        final_model = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', RandomForestClassifier(**best_config['best_params'], random_state=42, n_jobs=-1))
        ])
    elif 'ExtraTrees' in best_pipe_name:
        final_model = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', ExtraTreesClassifier(**best_config['best_params'], random_state=42, n_jobs=-1))
        ])
    elif 'GradientBoosting' in best_pipe_name:
        final_model = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', GradientBoostingClassifier(**best_config['best_params'], random_state=42))
        ])
    elif 'LogisticRegression' in best_pipe_name:
        final_model = Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', StandardScaler()),
            ('clf', LogisticRegression(**best_config['best_params'], max_iter=1000, random_state=42))
        ])
    else:
        # Use best estimator from grid search
        final_model = grid.best_estimator_
    
    final_model.fit(X_train, y_train)
    
    joblib.dump(final_model, model_path)
    
    config = {
        **best_config,
        'task': 'goal-number',
        'target': 'over_under_2_5',
        'model_path': str(model_path),
    }
    
    with open(config_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, ensure_ascii=False, indent=2)
    
    print(f"  Model saved to: {model_path}")
    print(f"  Config saved to: {config_path}")
    
    return best_config


def main() -> None:
    """Train optimized models for all leagues."""
    print("="*70)
    print("Training league-specific models with feature selection and tuning")
    print("Target: 80%+ bet accuracy")
    print("="*70)
    
    results = []
    
    for league_code, league_config in LEAGUES.items():
        result = train_league_model_v3(league_code, league_config)
        if result:
            results.append(result)
    
    # Summary
    print(f"\n{'='*70}")
    print("SUMMARY")
    print(f"{'='*70}")
    
    if results:
        results_sorted = sorted(results, key=lambda x: x['bet_acc'], reverse=True)
        
        print(f"\nTrained {len(results)} league models:\n")
        
        for idx, result in enumerate(results_sorted, 1):
            status = "✓" if result['bet_acc'] >= 0.80 else "✗"
            print(f"  {idx}. {result['league']} ({result['league_name']}): {status}")
            print(f"     Model: {result['model']}")
            print(f"     Feature Group: {result['feature_group']}")
            print(f"     Threshold: {result['threshold']:.2f}")
            print(f"     Bet count: {result['bet_count']}")
            print(f"     Bet accuracy: {result['bet_acc']:.2%}")
            print(f"     Features: {result['feature_count']}")
            print(f"     Best params: {result['best_params']}")
            print()
        
        summary_path = OUTPUT_DIR / 'all_leagues_summary.json'
        with open(summary_path, 'w', encoding='utf-8') as f:
            json.dump(results_sorted, f, ensure_ascii=False, indent=2)
        print(f"Summary saved to: {summary_path}")
        
        high_accuracy_count = sum(1 for r in results if r['bet_acc'] >= 0.80)
        print(f"\nModels meeting 80%+ target: {high_accuracy_count}/{len(results)}")
        
        best_result = max(results, key=lambda x: x['bet_acc'])
        print(f"\nBest model: {best_result['league']} ({best_result['league_name']}) - {best_result['bet_acc']:.2%}")
        
        if high_accuracy_count == 0:
            print(f"[WARNING] No model met 80%+ target. Best is {best_result['bet_acc']:.2%}")
        elif high_accuracy_count < len(results):
            print(f"[PARTIAL] {high_accuracy_count}/{len(results)} models met 80%+ target")
        else:
            print(f"[SUCCESS] All {len(results)} models met 80%+ target!")
    else:
        print("\nNo models trained!")


if __name__ == '__main__':
    main()
