#!/usr/bin/env python3
"""Train goal-number models for all serving leagues (except R1).

Each league gets its own independent model trained on its own historical data.
"""

from __future__ import annotations

import json
import math
from collections import defaultdict
from pathlib import Path
from typing import List

import joblib
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

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

# Serving leagues (excluding R1 which is already done)
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},
    '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_features(df: pd.DataFrame) -> pd.DataFrame:
    """Build features from match history."""
    team_hist = defaultdict(lambda: {
        "gf": [], "ga": [], "tot": [], "over": [],
        "home_gf": [], "home_ga": [], "home_over": [],
        "away_gf": [], "away_ga": [], "away_over": [],
        "gf_last5": [], "ga_last5": [], "over_last5": [],
    })
    
    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 = {
            "home_gf_avg": mean_or_default(home_hist["gf"], 1.4),
            "home_ga_avg": mean_or_default(home_hist["ga"], 1.4),
            "away_gf_avg": mean_or_default(away_hist["gf"], 1.2),
            "away_ga_avg": mean_or_default(away_hist["ga"], 1.2),
            "home_tot_avg": mean_or_default(home_hist["tot"], 2.6),
            "away_tot_avg": mean_or_default(away_hist["tot"], 2.6),
            "home_over_rate": mean_or_default(home_hist["over"], 0.5),
            "away_over_rate": mean_or_default(away_hist["over"], 0.5),
            "home_home_gf_avg": mean_or_default(home_hist["home_gf"], mean_or_default(home_hist["gf"], 1.4)),
            "home_home_ga_avg": mean_or_default(home_hist["home_ga"], mean_or_default(home_hist["ga"], 1.4)),
            "away_away_gf_avg": mean_or_default(away_hist["away_gf"], mean_or_default(away_hist["gf"], 1.2)),
            "away_away_ga_avg": mean_or_default(away_hist["away_ga"], mean_or_default(away_hist["ga"], 1.2)),
            "home_home_over_rate": mean_or_default(home_hist["home_over"], mean_or_default(home_hist["over"], 0.5)),
            "away_away_over_rate": mean_or_default(away_hist["away_over"], mean_or_default(away_hist["over"], 0.5)),
            "home_gf_last5": mean_or_default(home_hist["gf_last5"], mean_or_default(home_hist["gf"], 1.4)),
            "home_ga_last5": mean_or_default(home_hist["ga_last5"], mean_or_default(home_hist["ga"], 1.4)),
            "away_gf_last5": mean_or_default(away_hist["gf_last5"], mean_or_default(away_hist["gf"], 1.2)),
            "away_ga_last5": mean_or_default(away_hist["ga_last5"], mean_or_default(away_hist["ga"], 1.2)),
            "home_over_last5": mean_or_default(home_hist["over_last5"], mean_or_default(home_hist["over"], 0.5)),
            "away_over_last5": mean_or_default(away_hist["over_last5"], mean_or_default(away_hist["over"], 0.5)),
            "expected_goals_proxy": float(np.nanmean([
                mean_or_default(home_hist["gf"], 1.4),
                mean_or_default(home_hist["home_gf"], mean_or_default(home_hist["gf"], 1.4)),
                mean_or_default(away_hist["ga"], 1.2),
                mean_or_default(away_hist["away_ga"], mean_or_default(away_hist["ga"], 1.2)),
                mean_or_default(away_hist["gf"], 1.2),
                mean_or_default(away_hist["away_gf"], mean_or_default(away_hist["gf"], 1.2)),
                mean_or_default(home_hist["ga"], 1.4),
                mean_or_default(home_hist["home_ga"], mean_or_default(home_hist["ga"], 1.4)),
            ])),
            "combined_over_rate": float(np.nanmean([mean_or_default(home_hist["over"], 0.5), mean_or_default(away_hist["over"], 0.5)])),
            "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_last5"].append(total_goals)
        home_hist["ga_last5"].append(total_goals)
        home_hist["over_last5"].append(is_over)
        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_last5"].append(total_goals)
        away_hist["ga_last5"].append(total_goals)
        away_hist["over_last5"].append(is_over)
        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 train_league_model(league_code: str, league_config: dict) -> dict | None:
    """Train a goal-number model for a specific league."""
    print(f"\n{'='*60}")
    print(f"Training model for {league_code} ({league_config['name']})")
    print(f"{'='*60}")
    
    # 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
    features_df = build_features(df)
    print(f"  Built {len(features_df)} feature rows")
    
    # Prepare data
    feature_cols = [col for col in features_df.columns if col != 'is_over']
    X = features_df[feature_cols].fillna(0)
    y = features_df['is_over'].astype(int)
    
    print(f"  Over rate: {y.mean():.2%}")
    
    # Split data (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)}")
    
    # Test different models
    models = {
        'LogisticRegression': Pipeline([
            ('imputer', SimpleImputer(strategy='mean')),
            ('scaler', StandardScaler()),
            ('clf', LogisticRegression(max_iter=1000, random_state=42))
        ]),
        'RandomForest': Pipeline([
            ('imputer', SimpleImputer(strategy='mean')),
            ('scaler', StandardScaler()),
            ('clf', RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42, n_jobs=-1))
        ]),
    }
    
    best_model = None
    best_config = None
    best_test_acc = 0.0
    
    print("\n  Training models...")
    for model_name, pipeline in models.items():
        pipeline.fit(X_train, y_train)
        
        # Overall accuracy
        y_pred = pipeline.predict(X_test)
        overall_acc = accuracy_score(y_test, y_pred)
        
        # Test different thresholds
        probs = pipeline.predict_proba(X_test)[:, 1]
        thresholds = np.arange(0.45, 0.81, 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 or (bet_acc == best_test_acc and bet_count > best_config.get('bet_count', 0)):
                best_test_acc = bet_acc
                best_model = pipeline
                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),
                    'rows': int(len(features_df)),
                    'train_rows': int(len(X_train)),
                    'test_rows': int(len(X_test)),
                    'features': feature_cols,
                }
    
    if best_model is None or best_config is None:
        print(f"  [ERROR] No model met minimum bet count requirement (25)")
        return None
    
    print(f"\n  BEST MODEL:")
    print(f"    Model: {best_config['model']}")
    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%}")
    
    # 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'
    
    joblib.dump(best_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 goal-number models for all serving leagues."""
    print("="*60)
    print("Training goal-number models for all serving leagues")
    print("="*60)
    
    results = []
    
    for league_code, league_config in LEAGUES.items():
        result = train_league_model(league_code, league_config)
        if result:
            results.append(result)
    
    # Summary
    print(f"\n{'='*60}")
    print("SUMMARY")
    print(f"{'='*60}")
    
    if results:
        print(f"\nTrained {len(results)} league models:\n")
        
        for result in sorted(results, key=lambda x: x['bet_acc'], reverse=True):
            print(f"  {result['league']} ({result['league_name']}):")
            print(f"    Model: {result['model']}")
            print(f"    Threshold: {result['threshold']:.2f}")
            print(f"    Bet count: {result['bet_count']}")
            print(f"    Bet accuracy: {result['bet_acc']:.2%}")
            print(f"    Overall accuracy: {result['overall_acc']:.2%}")
        
        # Save summary
        summary_path = OUTPUT_DIR / 'all_leagues_summary.json'
        with open(summary_path, 'w', encoding='utf-8') as f:
            json.dump(results, f, ensure_ascii=False, indent=2)
        print(f"\nSummary saved to: {summary_path}")
        
        # Find best model
        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%}")
    else:
        print("\nNo models trained!")
    
    print(f"\n{'='*60}\n")


if __name__ == '__main__':
    main()
