#!/usr/bin/env python3
"""Train 1year-V1 models using only last 1 year of data.

保留原有数据及模型，只用近1年的数据进行训练，形成"1year-V1"模型。
"""

from __future__ import annotations

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

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, ExtraTreesClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score, classification_report
from sklearn.neural_network import MLPClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.impute import SimpleImputer

warnings.filterwarnings('ignore')

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

# All leagues to train
LEAGUES = {
    'a1': {'name': '澳超', 'target': 0.70},
    'br1': {'name': '巴甲', 'target': 0.70},
    'cz1': {'name': '捷克甲', 'target': 0.70},
    'd1': {'name': '德甲', 'target': 0.70},
    'd2': {'name': '德乙', 'target': 0.70},
    'e0': {'name': '英超', 'target': 0.70},
    'e1': {'name': '英甲', 'target': 0.70},
    'e2': {'name': '英冠', 'target': 0.70},
    'e2n': {'name': '英乙', 'target': 0.70},
    'e3': {'name': '英格兰第五级别', 'target': 0.70},
    'f1': {'name': '法甲', 'target': 0.70},
    'f2': {'name': '法乙', 'target': 0.70},
    'g1': {'name': '希腊超', 'target': 0.70},
    'i1': {'name': '意甲', 'target': 0.70},
    'j1': {'name': '日职联', 'target': 0.70},
    'm1': {'name': '美职联', 'target': 0.70},
    'p1': {'name': '葡超', 'target': 0.70},
    'r1': {'name': '俄超', 'target': 0.70},
    'sc1': {'name': '苏超', 'target': 0.70},
    'sp1': {'name': '西甲', 'target': 0.70},
    'tr1': {'name': '土超', 'target': 0.70},
    'uz1': {'name': '乌兹别克超', 'target': 0.70},
}


def build_features(df: pd.DataFrame) -> pd.DataFrame:
    """Build features using rolling team history (same as batch_train_final.py)."""
    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": [],
    })
    
    rows = []
    
    for _, 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
        
        row = {}
        
        # Comprehensive window-based features
        for window in [3, 5, 10, 15, 20]:
            row[f"home_gf_last{window}"] = np.mean(home_hist[f"gf_last{window}"]) if home_hist[f"gf_last{window}"] else 1.4
            row[f"home_ga_last{window}"] = np.mean(home_hist[f"ga_last{window}"]) if home_hist[f"ga_last{window}"] else 1.4
            row[f"away_gf_last{window}"] = np.mean(away_hist[f"gf_last{window}"]) if away_hist[f"gf_last{window}"] else 1.2
            row[f"away_ga_last{window}"] = np.mean(away_hist[f"ga_last{window}"]) if away_hist[f"ga_last{window}"] else 1.2
            row[f"home_over_last{window}"] = np.mean(home_hist[f"over_last{window}"]) if home_hist[f"over_last{window}"] else 0.5
            row[f"away_over_last{window}"] = np.mean(away_hist[f"over_last{window}"]) if away_hist[f"over_last{window}"] else 0.5
            
            # Home/Away specific for larger windows
            if window in [5, 10, 15, 20]:
                row[f"home_home_gf_last{window}"] = np.mean(home_hist["home_gf"][-window:]) if len(home_hist["home_gf"]) >= window else 1.4
                row[f"home_home_ga_last{window}"] = np.mean(home_hist["home_ga"][-window:]) if len(home_hist["home_ga"]) >= window else 1.4
                row[f"away_away_gf_last{window}"] = np.mean(away_hist["away_gf"][-window:]) if len(away_hist["away_gf"]) >= window else 1.2
                row[f"away_away_ga_last{window}"] = np.mean(away_hist["away_ga"][-window:]) if len(away_hist["away_ga"]) >= window else 1.2
                row[f"home_home_over_last{window}"] = np.mean(home_hist["home_over"][-window:]) if len(home_hist["home_over"]) >= window else 0.5
                row[f"away_away_over_last{window}"] = np.mean(away_hist["away_over"][-window:]) if len(away_hist["away_over"]) >= window else 0.5
        
        # Overall averages
        row["home_gf_avg"] = np.mean(home_hist["gf"]) if home_hist["gf"] else 1.4
        row["home_ga_avg"] = np.mean(home_hist["ga"]) if home_hist["ga"] else 1.4
        row["away_gf_avg"] = np.mean(away_hist["gf"]) if away_hist["gf"] else 1.2
        row["away_ga_avg"] = np.mean(away_hist["ga"]) if away_hist["ga"] else 1.2
        row["home_tot_avg"] = np.mean(home_hist["tot"]) if home_hist["tot"] else 2.6
        row["away_tot_avg"] = np.mean(away_hist["tot"]) if away_hist["tot"] else 2.6
        row["home_over_rate"] = np.mean(home_hist["over"]) if home_hist["over"] else 0.5
        row["away_over_rate"] = np.mean(away_hist["over"]) if away_hist["over"] else 0.5
        
        # Home/Away specific
        row["home_home_gf_avg"] = np.mean(home_hist["home_gf"]) if home_hist["home_gf"] else np.mean(home_hist["gf"]) if home_hist["gf"] else 1.4
        row["home_home_ga_avg"] = np.mean(home_hist["home_ga"]) if home_hist["home_ga"] else np.mean(home_hist["ga"]) if home_hist["ga"] else 1.4
        row["away_away_gf_avg"] = np.mean(away_hist["away_gf"]) if away_hist["away_gf"] else np.mean(away_hist["gf"]) if away_hist["gf"] else 1.2
        row["away_away_ga_avg"] = np.mean(away_hist["away_ga"]) if away_hist["away_ga"] else np.mean(away_hist["ga"]) if away_hist["ga"] else 1.2
        row["home_home_over_rate"] = np.mean(home_hist["home_over"]) if home_hist["home_over"] else np.mean(home_hist["over"]) if home_hist["over"] else 0.5
        row["away_away_over_rate"] = np.mean(away_hist["away_over"]) if away_hist["away_over"] else np.mean(away_hist["over"]) if away_hist["over"] else 0.5
        
        # Momentum and form
        if len(home_hist["momentum"]) >= 10:
            row["home_momentum"] = np.mean(home_hist["momentum"][-10:])
        else:
            row["home_momentum"] = 0.5
        
        if len(away_hist["momentum"]) >= 10:
            row["away_momentum"] = np.mean(away_hist["momentum"][-10:])
        else:
            row["away_momentum"] = 0.5
        
        row["home_recent_form"] = np.mean(home_hist["recent_form"][-5:]) if len(home_hist["recent_form"]) >= 5 else 0.5
        row["away_recent_form"] = np.mean(away_hist["recent_form"][-5:]) if len(away_hist["recent_form"]) >= 5 else 0.5
        
        # Goal trend
        if len(home_hist["goal_trend"]) >= 5:
            row["home_goal_trend"] = home_hist["goal_trend"][-1]
        else:
            row["home_goal_trend"] = 0
        
        if len(away_hist["goal_trend"]) >= 5:
            row["away_goal_trend"] = away_hist["goal_trend"][-1]
        else:
            row["away_goal_trend"] = 0
        
        # Variance and consistency
        if len(home_hist["gf_last20"]) >= 2:
            row["home_gf_var"] = np.var(home_hist["gf_last20"])
            row["home_ga_var"] = np.var(home_hist["ga_last20"])
        else:
            row["home_gf_var"] = 0.5
            row["home_ga_var"] = 0.5
        
        if len(away_hist["gf_last20"]) >= 2:
            row["away_gf_var"] = np.var(away_hist["gf_last20"])
            row["away_ga_var"] = np.var(away_hist["ga_last20"])
        else:
            row["away_gf_var"] = 0.5
            row["away_ga_var"] = 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)
        
        # Consistency features
        if len(home_hist["gf_last20"]) >= 2:
            row["home_gf_std"] = np.std(home_hist["gf_last20"])
            row["home_ga_std"] = np.std(home_hist["ga_last20"])
        else:
            row["home_gf_std"] = 0.5
            row["home_ga_std"] = 0.5
        
        if len(away_hist["gf_last20"]) >= 2:
            row["away_gf_std"] = np.std(away_hist["gf_last20"])
            row["away_ga_std"] = np.std(away_hist["ga_last20"])
        else:
            row["away_gf_std"] = 0.5
            row["away_ga_std"] = 0.5
        
        # Momentum advantage
        row["momentum_advantage"] = row["home_momentum"] - row["away_momentum"]
        
        # 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)
                    
                    row["market_over_prob"] = fair_over
                    row[f"{odds_prefix.lower()}_over_under_ratio"] = over_odds / (under_odds + 0.01)
                    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)
                    break
        
        row["is_over"] = is_over
        rows.append(row)
        
        # Update team history AFTER recording features (so features don't leak)
        home_hist["gf"].append(total_goals)
        home_hist["ga"].append(total_goals)
        home_hist["tot"].append(total_goals)
        home_hist["over"].append(is_over)
        
        for window in [3, 5, 10, 15, 20]:
            home_hist[f"gf_last{window}"].append(total_goals)
            home_hist[f"ga_last{window}"].append(total_goals)
            home_hist[f"over_last{window}"].append(is_over)
            
            if window in [5, 10, 15, 20]:
                home_hist["home_gf"].append(float(match['FTHG']) if pd.notna(match.get('FTHG')) else np.nan)
                home_hist["home_ga"].append(float(match['FTAG']) if pd.notna(match.get('FTAG')) else np.nan)
                home_hist["home_over"].append(is_over)
        
        home_result = 1 if total_goals > (match.get('FTAG', 0) or 0) else 0.5
        home_hist["momentum"].append(home_result)
        home_hist["recent_form"].append(home_result)
        
        if len(home_hist["gf_last10"]) >= 2:
            recent = home_hist["gf_last10"][-5:]
            old = home_hist["gf_last10"][:-5]
            home_hist["goal_trend"].append(np.mean(recent) - np.mean(old))
        
        away_hist["gf"].append(total_goals)
        away_hist["ga"].append(total_goals)
        away_hist["tot"].append(total_goals)
        away_hist["over"].append(is_over)
        
        for window in [3, 5, 10, 15, 20]:
            away_hist[f"gf_last{window}"].append(total_goals)
            away_hist[f"ga_last{window}"].append(total_goals)
            away_hist[f"over_last{window}"].append(is_over)
            
            if window in [5, 10, 15, 20]:
                away_hist["away_gf"].append(float(match['FTHG']) if pd.notna(match.get('FTHG')) else np.nan)
                away_hist["away_ga"].append(float(match['FTAG']) if pd.notna(match.get('FTAG')) else np.nan)
                away_hist["away_over"].append(is_over)
        
        away_result = 1 if total_goals > (match.get('FTHG', 0) or 0) else 0.5
        away_hist["momentum"].append(away_result)
        away_hist["recent_form"].append(away_result)
        
        if len(away_hist["gf_last10"]) >= 2:
            recent = away_hist["gf_last10"][-5:]
            old = away_hist["gf_last10"][:-5]
            away_hist["goal_trend"].append(np.mean(recent) - np.mean(old))
    
    return pd.DataFrame(rows)


def train_1year_model(league_code: str, league_config: dict, cutoff_date: pd.Timestamp) -> Optional[dict]:
    """Train a 1year-V1 model using only last 1 year of data."""
    print(f"\n{'='*70}")
    print(f"1year-V1 Training: {league_code} ({league_config['name']})")
    print(f"Cutoff date: {cutoff_date.date()}")
    print(f"{'='*70}")
    
    # Load data
    history_path = HISTORICAL_DIR / f'{league_code}_history.csv'
    if not history_path.exists():
        print(f"  [SKIP] File not found: {history_path}")
        return None
    
    df = pd.read_csv(history_path, low_memory=False)
    
    # Parse dates properly
    df['Date_dt'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
    df = df.dropna(subset=['Date_dt'])
    df = df.sort_values('Date_dt').reset_index(drop=True)
    
    print(f"  Total rows: {len(df)}")
    print(f"  Date range: {df['Date_dt'].min().date()} to {df['Date_dt'].max().date()}")
    
    # Filter last 1 year
    df_1year = df[df['Date_dt'] >= cutoff_date].copy()
    print(f"  Last 1 year rows: {len(df_1year)}")
    
    if len(df_1year) < 100:
        print(f"  [SKIP] Not enough 1-year data: {len(df_1year)} rows")
        return None
    
    # Get data BEFORE cutoff for feature building (warm-up period)
    # We need historical data to build features for the 1-year window
    warmup_df = df[df['Date_dt'] < cutoff_date].tail(500)  # Keep last 500 matches before cutoff for warmup
    full_df = pd.concat([warmup_df, df_1year], ignore_index=True)
    
    print(f"  Warmup period: {warmup_df['Date_dt'].min().date()} to {warmup_df['Date_dt'].max().date()}")
    print(f"  Full training window: {full_df['Date_dt'].min().date()} to {full_df['Date_dt'].max().date()}")
    
    # Build features for full dataset
    print("  Building features...")
    features_df = build_features(full_df)
    
    # Extract only the 1-year window features
    features_1year = features_df.iloc[-len(df_1year):].reset_index(drop=True)
    
    # Prepare data
    target_col = 'is_over'
    feature_cols = [c for c in features_1year.columns if c != target_col]
    
    # Remove high-NaN features
    nan_ratio = features_1year[feature_cols].isna().mean()
    valid_features = [c for c, ratio in zip(feature_cols, nan_ratio) if ratio < 0.6]
    print(f"  Valid features: {len(valid_features)}")
    
    X = features_1year[valid_features].fillna(0)
    y = features_1year[target_col].astype(int)
    
    print(f"  Total samples: {len(X)}")
    print(f"  Over rate: {y.mean():.2%}")
    
    # Time-based split: 80% train, 20% test
    split_idx = int(len(X) * 0.8)
    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)}")
    
    # Define models
    models = [
        ('RF_1', Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', RandomForestClassifier(
                n_estimators=500, max_depth=25, min_samples_leaf=2,
                max_features='sqrt', random_state=42, n_jobs=-1, class_weight='balanced'
            ))
        ])),
        ('RF_2', Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', RandomForestClassifier(
                n_estimators=700, max_depth=30, min_samples_leaf=1,
                max_features=0.6, random_state=42, n_jobs=-1, class_weight='balanced'
            ))
        ])),
        ('ET', Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', ExtraTreesClassifier(
                n_estimators=500, max_depth=25, min_samples_leaf=2,
                max_features='sqrt', random_state=42, n_jobs=-1, class_weight='balanced'
            ))
        ])),
        ('GB_1', Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', GradientBoostingClassifier(
                n_estimators=400, max_depth=8, learning_rate=0.05,
                subsample=0.8, random_state=42
            ))
        ])),
        ('GB_2', Pipeline([
            ('imputer', SimpleImputer(strategy='median')),
            ('scaler', RobustScaler()),
            ('clf', GradientBoostingClassifier(
                n_estimators=600, max_depth=6, learning_rate=0.03,
                subsample=0.85, random_state=42
            ))
        ])),
        ('MLP', Pipeline([
            ('imputer', SimpleImputer(strategy='mean')),
            ('scaler', MinMaxScaler()),
            ('clf', MLPClassifier(
                hidden_layer_sizes=(256, 128, 64),
                max_iter=500, alpha=0.01, learning_rate='adaptive',
                random_state=42, batch_size=64
            ))
        ])),
        ('LR', Pipeline([
            ('imputer', SimpleImputer(strategy='mean')),
            ('scaler', StandardScaler()),
            ('clf', LogisticRegression(
                C=10.0, penalty='l2', max_iter=2000,
                random_state=42, class_weight='balanced'
            ))
        ])),
    ]
    
    best_config = None
    best_model = None
    best_test_acc = 0.0
    
    for model_name, pipeline in models:
        try:
            pipeline.fit(X_train, y_train)
            y_pred = pipeline.predict(X_test)
            overall_acc = accuracy_score(y_test, y_pred)
            
            # Test thresholds
            if hasattr(pipeline[-1], 'predict_proba'):
                probs = pipeline.predict_proba(X_test)[:, 1]
                thresholds = np.arange(0.35, 0.91, 0.02)
                
                for threshold in thresholds:
                    mask = (probs >= threshold) | (probs <= (1.0 - threshold))
                    bet_count = int(mask.sum())
                    
                    if bet_count < 15:
                        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_model = pipeline
                        best_config = {
                            'league': league_code,
                            'league_name': league_config['name'],
                            'model_name': model_name,
                            'threshold': float(threshold),
                            'bet_count': bet_count,
                            'bet_acc': float(bet_acc),
                            'overall_acc': float(overall_acc),
                            'train_rows': int(len(X_train)),
                            'test_rows': int(len(X_test)),
                            'total_rows': int(len(X)),
                            'feature_count': len(valid_features),
                            'cutoff_date': str(cutoff_date.date()),
                            'data_start': str(df_1year['Date_dt'].min().date()),
                            'data_end': str(df_1year['Date_dt'].max().date()),
                        }
            
            print(f"  {model_name}: Overall={overall_acc:.2%}, Best so far={best_test_acc:.2%}")
        
        except Exception as e:
            print(f"  {model_name}: Error - {e}")
            continue
    
    if not best_config:
        print(f"  [ERROR] No model trained successfully")
        return None
    
    # Print best result
    print(f"\n  {'='*60}")
    print(f"  BEST MODEL for {league_code}")
    print(f"  {'='*60}")
    print(f"    Model: {best_config['model_name']}")
    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"    Data period: {best_config['data_start']} to {best_config['data_end']}")
    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)
    
    import joblib
    
    model_path = model_dir / '1year_v1_pipeline.pkl'
    config_path = model_dir / '1year_v1_config.json'
    
    joblib.dump(best_model, model_path)
    
    config = {
        **best_config,
        'task': 'goal-number-1year-v1',
        'model_path': str(model_path),
        'features': valid_features,
    }
    
    with open(config_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, ensure_ascii=False, indent=2)
    
    print(f"  Saved: {model_path}")
    print(f"  Saved: {config_path}")
    
    return best_config


def main():
    print("="*70)
    print("1year-V1 Model Training")
    print("Using only last 1 year of data for each league")
    print("="*70)
    
    # Calculate cutoff date (1 year ago from latest data)
    all_files = list(HISTORICAL_DIR.glob('*_history.csv'))
    latest_date = pd.Timestamp('2016-01-01')
    
    for f in all_files:
        df = pd.read_csv(f, low_memory=False, nrows=1000)
        df['Date_dt'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
        file_latest = df['Date_dt'].max()
        if pd.notna(file_latest) and file_latest > latest_date:
            latest_date = file_latest
    
    cutoff_date = latest_date - pd.DateOffset(years=1)
    print(f"\nLatest data date: {latest_date.date()}")
    print(f"Cutoff date (1 year ago): {cutoff_date.date()}")
    
    results = []
    
    for league_code, league_config in LEAGUES.items():
        result = train_1year_model(league_code, league_config, cutoff_date)
        if result:
            results.append(result)
    
    # Summary
    print(f"\n{'='*70}")
    print("1year-V1 TRAINING SUMMARY")
    print(f"{'='*70}")
    
    if results:
        results_sorted = sorted(results, key=lambda x: x['bet_acc'], reverse=True)
        
        print(f"\n{'联赛':<12} {'模型':<8} {'阈值':<6} {'投注数':<8} {'投注准确率':<10} {'总样本':<8}")
        print("-" * 70)
        
        for r in results_sorted:
            print(f"{r['league']:<12} {r['model_name']:<8} {r['threshold']:.2f}    {r['bet_count']:<8} {r['bet_acc']:.2%}       {r['total_rows']:<8}")
        
        print("-" * 70)
        
        # Overall stats
        avg_acc = np.mean([r['bet_acc'] for r in results])
        max_acc = max(r['bet_acc'] for r in results)
        min_acc = min(r['bet_acc'] for r in results)
        
        print(f"\nOverall Statistics:")
        print(f"  Leagues trained: {len(results)}")
        print(f"  Average bet accuracy: {avg_acc:.2%}")
        print(f"  Best accuracy: {max_acc:.2%}")
        print(f"  Worst accuracy: {min_acc:.2%}")
        
        # Save summary
        summary_path = OUTPUT_DIR / '1year_v1_summary.json'
        with open(summary_path, 'w', encoding='utf-8') as f:
            json.dump(results_sorted, f, ensure_ascii=False, indent=2)
        print(f"\nSummary saved to: {summary_path}")
    else:
        print("\nNo models trained!")


if __name__ == '__main__':
    main()
