#!/usr/bin/env python3
"""Round 2 optimization - more aggressive with ensemble methods."""

from __future__ import annotations

import json
import warnings
from pathlib import Path
from collections import defaultdict
from typing import Optional

import pandas as pd
import numpy as np
from sklearn.ensemble import (RandomForestClassifier, GradientBoostingClassifier, 
                              ExtraTreesClassifier, AdaBoostClassifier)
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.neural_network import MLPClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.impute import SimpleImputer
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
import joblib

warnings.filterwarnings('ignore')

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

TARGET_LEAGUES = {
    'd1': {'name': '德甲', 'original': 0.7143, 'round1': 0.7143},
    'e2': {'name': '英冠', 'original': 0.5732, 'round1': 0.7143},
    'tr1': {'name': '土超', 'original': 0.6316, 'round1': 0.7059},
    'e0': {'name': '英超', 'original': 0.6471, 'round1': 0.7000},
    'f1': {'name': '法甲', 'original': 0.6786, 'round1': 0.6957},
    'e3': {'name': '英格兰第五级别', 'original': 0.5750, 'round1': 0.6923},
    'e1': {'name': '英甲', 'original': 0.6522, 'round1': 0.6667},
    'br1': {'name': '巴甲', 'original': 0.6286, 'round1': 0.6571},
    'p1': {'name': '葡超', 'original': 0.5897, 'round1': 0.6250},
    'i1': {'name': '意甲', 'original': 0.5741, 'round1': 0.6111},
    'a1': {'name': '澳超', 'original': 0.5600, 'round1': 0.6000},
}


def build_features(df: pd.DataFrame) -> pd.DataFrame:
    """Build comprehensive features."""
    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": [],
    })
    
    rows = []
    
    for _, match in df.iterrows():
        home = str(match["HomeTeam"])
        away = str(match["AwayTeam"])
        home_hist = team_hist[home]
        away_hist = team_hist[away]
        
        fthg = float(match.get('FTHG', 0) or 0)
        ftag = float(match.get('FTAG', 0) or 0)
        total_goals = fthg + ftag
        is_over = 1 if total_goals > 2.5 else 0
        
        row = {}
        
        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
            
            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
        
        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
        
        row["home_home_gf_avg"] = np.mean(home_hist["home_gf"]) if home_hist["home_gf"] else row["home_gf_avg"]
        row["home_home_ga_avg"] = np.mean(home_hist["home_ga"]) if home_hist["home_ga"] else row["home_ga_avg"]
        row["away_away_gf_avg"] = np.mean(away_hist["away_gf"]) if away_hist["away_gf"] else row["away_gf_avg"]
        row["away_away_ga_avg"] = np.mean(away_hist["away_ga"]) if away_hist["away_ga"] else row["away_ga_avg"]
        row["home_home_over_rate"] = np.mean(home_hist["home_over"]) if home_hist["home_over"] else row["home_over_rate"]
        row["away_away_over_rate"] = np.mean(away_hist["away_over"]) if away_hist["away_over"] else row["away_over_rate"]
        
        row["home_momentum"] = np.mean(home_hist["momentum"][-10:]) if len(home_hist["momentum"]) >= 10 else 0.5
        row["away_momentum"] = np.mean(away_hist["momentum"][-10:]) if len(away_hist["momentum"]) >= 10 else 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
        row["home_goal_trend"] = home_hist["goal_trend"][-1] if home_hist["goal_trend"] else 0
        row["away_goal_trend"] = away_hist["goal_trend"][-1] if away_hist["goal_trend"] else 0
        
        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"])
            row["home_gf_std"] = np.std(home_hist["gf_last20"])
            row["home_ga_std"] = np.std(home_hist["ga_last20"])
        else:
            row["home_gf_var"] = 0.5
            row["home_ga_var"] = 0.5
            row["home_gf_std"] = 0.5
            row["home_ga_std"] = 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"])
            row["away_gf_std"] = np.std(away_hist["gf_last20"])
            row["away_ga_std"] = np.std(away_hist["ga_last20"])
        else:
            row["away_gf_var"] = 0.5
            row["away_ga_var"] = 0.5
            row["away_gf_std"] = 0.5
            row["away_ga_std"] = 0.5
        
        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"]
        
        home_clean_rate = np.mean(home_hist["home_clean"]) if home_hist["home_clean"] else 0.3
        away_clean_rate = np.mean(away_hist["away_clean"]) if away_hist["away_clean"] else 0.3
        row["home_clean_sheet_rate"] = home_clean_rate
        row["away_clean_sheet_rate"] = away_clean_rate
        
        home_btts_rate = np.mean(home_hist["btts_yes"]) if home_hist["btts_yes"] else 0.45
        away_btts_rate = np.mean(away_hist["btts_yes"]) if away_hist["btts_yes"] else 0.45
        row["home_btts_rate"] = home_btts_rate
        row["away_btts_rate"] = away_btts_rate
        row["combined_btts_rate"] = (home_btts_rate + away_btts_rate) / 2
        
        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
        
        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)
        
        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["home_clean"].append(1 if ftag == 0 else 0)
        home_hist["btts_yes"].append(1 if fthg > 0 and ftag > 0 else 0)
        home_hist["btts_no"].append(1 if fthg == 0 or ftag == 0 else 0)
        
        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(fthg)
                home_hist["home_ga"].append(ftag)
                home_hist["home_over"].append(is_over)
        
        home_result = 1 if total_goals > ftag 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)
        away_hist["away_clean"].append(1 if fthg == 0 else 0)
        away_hist["btts_yes"].append(1 if fthg > 0 and ftag > 0 else 0)
        away_hist["btts_no"].append(1 if fthg == 0 or ftag == 0 else 0)
        
        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(fthg)
                away_hist["away_ga"].append(ftag)
                away_hist["away_over"].append(is_over)
        
        away_result = 1 if total_goals > fthg 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 get_models():
    """Return many base 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=800, max_depth=30, min_samples_leaf=1, max_features=0.6, random_state=42, n_jobs=-1, class_weight='balanced'))])),
        ('RF_3', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', RandomForestClassifier(n_estimators=1000, max_depth=35, min_samples_leaf=1, max_features='log2', random_state=42, n_jobs=-1, class_weight='balanced'))])),
        ('RF_4', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', RandomForestClassifier(n_estimators=600, max_depth=20, min_samples_leaf=3, max_features=0.5, random_state=123, n_jobs=-1, class_weight='balanced'))])),
        ('RF_5', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', RandomForestClassifier(n_estimators=700, max_depth=15, min_samples_leaf=2, max_features=0.7, random_state=42, n_jobs=-1))])),
        ('ET_1', 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'))])),
        ('ET_2', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', ExtraTreesClassifier(n_estimators=800, max_depth=30, min_samples_leaf=1, max_features=0.7, random_state=42, n_jobs=-1, class_weight='balanced'))])),
        ('ET_3', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', ExtraTreesClassifier(n_estimators=1000, max_depth=35, min_samples_leaf=1, max_features='log2', 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))])),
        ('GB_3', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', GradientBoostingClassifier(n_estimators=300, max_depth=5, learning_rate=0.1, subsample=0.9, random_state=42))])),
        ('GB_4', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', GradientBoostingClassifier(n_estimators=500, max_depth=7, learning_rate=0.05, subsample=0.85, random_state=42))])),
        ('MLP_1', 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))])),
        ('MLP_2', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', MinMaxScaler()), ('clf', MLPClassifier(hidden_layer_sizes=(128, 64, 32), max_iter=800, alpha=0.005, learning_rate='adaptive', random_state=42, batch_size=32))])),
        ('MLP_3', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', MinMaxScaler()), ('clf', MLPClassifier(hidden_layer_sizes=(512, 256, 128), max_iter=600, alpha=0.001, learning_rate='adaptive', random_state=42, batch_size=128))])),
        ('MLP_4', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', MinMaxScaler()), ('clf', MLPClassifier(hidden_layer_sizes=(200, 100, 50), max_iter=700, alpha=0.008, learning_rate='adaptive', random_state=42))])),
        ('LR_1', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', StandardScaler()), ('clf', LogisticRegression(C=10.0, penalty='l2', max_iter=2000, random_state=42, class_weight='balanced'))])),
        ('LR_2', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', StandardScaler()), ('clf', LogisticRegression(C=100.0, penalty='l2', max_iter=3000, random_state=42, class_weight='balanced'))])),
        ('LR_3', Pipeline([('imputer', SimpleImputer(strategy='mean')), ('scaler', StandardScaler()), ('clf', LogisticRegression(C=1.0, penalty='l2', max_iter=2000, random_state=42, class_weight='balanced'))])),
        ('SVM_1', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()), ('clf', SVC(kernel='rbf', C=1.0, gamma='scale', probability=True, random_state=42, class_weight='balanced'))])),
        ('SVM_2', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()), ('clf', SVC(kernel='rbf', C=10.0, gamma='scale', probability=True, random_state=42, class_weight='balanced'))])),
        ('SVM_3', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()), ('clf', SVC(kernel='linear', C=1.0, probability=True, random_state=42, class_weight='balanced'))])),
        ('ADA_1', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', AdaBoostClassifier(n_estimators=200, learning_rate=0.1, random_state=42))])),
        ('ADA_2', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', AdaBoostClassifier(n_estimators=300, learning_rate=0.05, random_state=42))])),
        ('NB_1', Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()), ('clf', GaussianNB())])),
    ]
    return models


def optimize_league(league_code: str, league_config: dict, cutoff_date: pd.Timestamp) -> Optional[dict]:
    """Optimize a single league."""
    print(f"\n{'='*70}")
    print(f"Round 2: {league_code} ({league_config['name']})")
    print(f"Current: {league_config['round1']:.2%} -> Target: 75%")
    print(f"{'='*70}")
    
    history_path = HISTORICAL_DIR / f'{league_code}_history.csv'
    if not history_path.exists():
        return None
    
    df = pd.read_csv(history_path, low_memory=False)
    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)
    
    df_1year = df[df['Date_dt'] >= cutoff_date].copy()
    if len(df_1year) < 100:
        return None
    
    warmup_df = df[df['Date_dt'] < cutoff_date].tail(500)
    full_df = pd.concat([warmup_df, df_1year], ignore_index=True)
    
    features_df = build_features(full_df)
    features_1year = features_df.iloc[-len(df_1year):].reset_index(drop=True)
    
    target_col = 'is_over'
    feature_cols = [c for c in features_1year.columns if c != target_col]
    nan_ratio = features_1year[feature_cols].isna().mean()
    valid_features = [c for c, ratio in zip(feature_cols, nan_ratio) if ratio < 0.6]
    
    X = features_1year[valid_features].fillna(0)
    y = features_1year[target_col].astype(int)
    
    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)}, Over rate: {y.mean():.2%}")
    
    models = get_models()
    best_config = None
    best_test_acc = 0.0
    best_model = None
    
    for model_name, pipeline in models:
        try:
            pipeline.fit(X_train, y_train)
            
            if hasattr(pipeline[-1], 'predict_proba'):
                probs = pipeline.predict_proba(X_test)[:, 1]
                
                for threshold in np.arange(0.25, 0.95, 0.01):
                    mask = (probs >= threshold) | (probs <= (1.0 - threshold))
                    bet_count = int(mask.sum())
                    
                    if bet_count < 8:
                        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),
                            '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()),
                            'improvement': float(bet_acc - league_config['original']),
                            'round1_acc': league_config['round1'],
                            'round2_improvement': float(bet_acc - league_config['round1']),
                        }
        except Exception as e:
            continue
    
    # Try ensemble
    print("  Testing ensembles...")
    try:
        rf = Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', RandomForestClassifier(n_estimators=500, max_depth=25, min_samples_leaf=2, 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, random_state=42, n_jobs=-1, class_weight='balanced'))])
        gb = Pipeline([('imputer', SimpleImputer(strategy='median')), ('scaler', RobustScaler()), ('clf', GradientBoostingClassifier(n_estimators=300, max_depth=5, learning_rate=0.1, subsample=0.9, random_state=42))])
        
        rf.fit(X_train, y_train)
        et.fit(X_train, y_train)
        gb.fit(X_train, y_train)
        
        probs_rf = rf.predict_proba(X_test)[:, 1]
        probs_et = et.predict_proba(X_test)[:, 1]
        probs_gb = gb.predict_proba(X_test)[:, 1]
        
        for weight in [(0.4, 0.4, 0.2), (0.3, 0.3, 0.4), (0.5, 0.3, 0.2), (0.2, 0.5, 0.3), (0.33, 0.33, 0.34)]:
            probs_ens = weight[0] * probs_rf + weight[1] * probs_et + weight[2] * probs_gb
            
            for threshold in np.arange(0.35, 0.85, 0.02):
                mask = (probs_ens >= threshold) | (probs_ens <= (1.0 - threshold))
                bet_count = int(mask.sum())
                if bet_count < 8:
                    continue
                preds = (probs_ens >= 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_name': f'ENS_{weight}',
                        'threshold': float(threshold),
                        'bet_count': bet_count,
                        'bet_acc': float(bet_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()),
                        'improvement': float(bet_acc - league_config['original']),
                        'round1_acc': league_config['round1'],
                        'round2_improvement': float(bet_acc - league_config['round1']),
                    }
    except Exception as e:
        print(f"    Ensemble error: {e}")
    
    if not best_config:
        return None
    
    print(f"\n  Best: {best_config['model_name']}, Threshold={best_config['threshold']:.2f}")
    print(f"  Accuracy: {best_config['bet_acc']:.2%}, Bet count: {best_config['bet_count']}")
    print(f"  Round1: {best_config['round1_acc']:.2%} -> Round2: {best_config['bet_acc']:.2%} ({best_config['round2_improvement']:+.2%})")
    
    # Save
    OPTIMIZED_DIR.mkdir(parents=True, exist_ok=True)
    model_dir = OPTIMIZED_DIR / league_code
    model_dir.mkdir(parents=True, exist_ok=True)
    
    model_path = model_dir / 'optimized_r2_pipeline.pkl'
    config_path = model_dir / 'optimized_r2_config.json'
    
    joblib.dump(best_model, model_path)
    
    with open(config_path, 'w', encoding='utf-8') as f:
        json.dump(best_config, f, ensure_ascii=False, indent=2)
    
    print(f"  Saved: {model_path}")
    
    return best_config


def main():
    print("="*70)
    print("1year-V1 Round 2 Optimization")
    print("Target: Get all leagues to 75%+ accuracy")
    print("="*70)
    
    # Fixed cutoff date as specified
    cutoff_date = pd.Timestamp('2025-09-03')
    print(f"\nCutoff date: {cutoff_date.date()}")
    
    results = []
    
    for league_code, league_config in TARGET_LEAGUES.items():
        result = optimize_league(league_code, league_config, cutoff_date)
        if result:
            results.append(result)
    
    # Summary
    print(f"\n{'='*70}")
    print("ROUND 2 OPTIMIZATION SUMMARY")
    print(f"{'='*70}")
    
    if results:
        results_sorted = sorted(results, key=lambda x: x['bet_acc'], reverse=True)
        
        print(f"\n{'联赛':<14} {'模型':<10} {'阈值':<6} {'投注数':<8} {'R1准确率':<10} {'R2准确率':<10} {'状态':<6}")
        print("-" * 80)
        
        for r in results_sorted:
            status = "✓" if r['bet_acc'] >= 0.75 else "✗"
            print(f"{r['league']:<14} {r['model_name']:<10} {r['threshold']:.2f}    {r['bet_count']:<8} {r['round1_acc']:.2%}     {r['bet_acc']:.2%}     {status}")
        
        print("-" * 80)
        
        # Stats
        avg_acc = np.mean([r['bet_acc'] for r in results])
        avg_r1 = np.mean([r['round1_acc'] for r in results])
        reached_75 = sum(1 for r in results if r['bet_acc'] >= 0.75)
        avg_improvement = np.mean([r['round2_improvement'] for r in results])
        
        print(f"\n优化结果:")
        print(f"  联赛数: {len(results)}")
        print(f"  达到75%+的联赛: {reached_75}/{len(results)}")
        print(f"  R1平均准确率: {avg_r1:.2%}")
        print(f"  R2平均准确率: {avg_acc:.2%}")
        print(f"  平均提升: {avg_improvement:+.2%}")
        
        # Save summary
        summary_path = OPTIMIZED_DIR / 'optimization_summary_r2.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 optimized!")


if __name__ == '__main__':
    main()
