// Analysis tool: Compare Glicko-2 vs Pure ELO ratings using historical match data
// Fixed version: unified rating, per-point scoring, correct match counts

use sqlx::SqlitePool;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use serde_json::json;
use std::fs;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct PlayerRatings {
    id: i64,
    name: String,
    glicko2_singles: f64,
    glicko2_doubles: f64,
    elo_unified: f64,
    glicko2_avg: f64,
    elo_diff: f64,
    matches_played: i32,
}

#[derive(Debug, Clone)]
struct MatchData {
    id: i64,
    match_type: String,
    team1_score: i32,
    team2_score: i32,
    team1_players: Vec<i64>,
    team2_players: Vec<i64>,
    played_at: String,
}

const K_FACTOR: f64 = 32.0;
const START_RATING: f64 = 1500.0;

/// Calculate expected score using ELO formula
fn expected_score(player_rating: f64, opponent_rating: f64) -> f64 {
    1.0 / (1.0 + 10.0_f64.powf((opponent_rating - player_rating) / 400.0))
}

/// Calculate per-point performance (points_won / total_points)
fn per_point_performance(points_won: i32, points_lost: i32) -> f64 {
    let total = points_won + points_lost;
    if total == 0 {
        0.5
    } else {
        points_won as f64 / total as f64
    }
}

/// Calculate effective opponent rating for doubles
/// Formula: Opp1 + Opp2 - Teammate
fn effective_opponent_rating(
    opp1_rating: f64,
    opp2_rating: f64,
    teammate_rating: f64,
) -> f64 {
    opp1_rating + opp2_rating - teammate_rating
}

#[tokio::main]
async fn main() {
    println!("🔄 ELO System Analysis Tool (v2 - Unified Rating)");
    println!("==================================================\n");
    
    // Connect to database
    let db_path = "/Users/split/Projects/pickleball-elo/pickleball.db";
    let pool = match sqlx::sqlite::SqlitePoolOptions::new()
        .max_connections(5)
        .connect(&format!("sqlite://{}?mode=rwc", db_path))
        .await
    {
        Ok(p) => p,
        Err(e) => {
            eprintln!("❌ Failed to connect to database: {}", e);
            return;
        }
    };
    
    println!("📊 Reading match history...");
    
    // Fetch all players with current Glicko-2 ratings
    let players: Vec<(i64, String, f64, f64)> = sqlx::query_as(
        "SELECT id, name, singles_rating, doubles_rating FROM players ORDER BY name"
    )
    .fetch_all(&pool)
    .await
    .unwrap_or_default();
    
    println!("✅ Found {} players", players.len());
    
    // Fetch all matches ordered by time
    let matches: Vec<(i64, String, i32, i32, String)> = sqlx::query_as(
        "SELECT id, match_type, team1_score, team2_score, COALESCE(timestamp, datetime('now')) as played_at 
         FROM matches ORDER BY timestamp, id"
    )
    .fetch_all(&pool)
    .await
    .unwrap_or_default();
    
    println!("✅ Found {} matches", matches.len());
    
    // Build match data with participants
    let mut all_matches: Vec<MatchData> = vec![];
    let mut match_counts: HashMap<i64, i32> = HashMap::new();
    
    for (match_id, match_type, team1_score, team2_score, played_at) in &matches {
        let team1_players: Vec<i64> = sqlx::query_scalar(
            "SELECT player_id FROM match_participants WHERE match_id = ? AND team = 1"
        )
        .bind(match_id)
        .fetch_all(&pool)
        .await
        .unwrap_or_default();
        
        let team2_players: Vec<i64> = sqlx::query_scalar(
            "SELECT player_id FROM match_participants WHERE match_id = ? AND team = 2"
        )
        .bind(match_id)
        .fetch_all(&pool)
        .await
        .unwrap_or_default();
        
        // Count matches per player
        for pid in &team1_players {
            *match_counts.entry(*pid).or_insert(0) += 1;
        }
        for pid in &team2_players {
            *match_counts.entry(*pid).or_insert(0) += 1;
        }
        
        all_matches.push(MatchData {
            id: *match_id,
            match_type: match_type.clone(),
            team1_score: *team1_score,
            team2_score: *team2_score,
            team1_players,
            team2_players,
            played_at: played_at.clone(),
        });
    }
    
    // Initialize UNIFIED ELO ratings (everyone starts at 1500)
    let mut elo_ratings: HashMap<i64, f64> = HashMap::new();
    for (player_id, _, _, _) in &players {
        elo_ratings.insert(*player_id, START_RATING);
    }
    
    println!("\n🔢 Replaying {} matches through new ELO system...\n", all_matches.len());
    
    // Replay all matches chronologically
    for (i, match_data) in all_matches.iter().enumerate() {
        let is_doubles = match_data.match_type == "doubles" || match_data.team1_players.len() > 1;
        let team1_won = match_data.team1_score > match_data.team2_score;
        
        // Calculate per-point performance
        let team1_performance = per_point_performance(match_data.team1_score, match_data.team2_score);
        let team2_performance = per_point_performance(match_data.team2_score, match_data.team1_score);
        
        // Collect current ratings BEFORE updates
        let team1_ratings: Vec<(i64, f64)> = match_data.team1_players.iter()
            .map(|pid| (*pid, elo_ratings.get(pid).copied().unwrap_or(START_RATING)))
            .collect();
        let team2_ratings: Vec<(i64, f64)> = match_data.team2_players.iter()
            .map(|pid| (*pid, elo_ratings.get(pid).copied().unwrap_or(START_RATING)))
            .collect();
        
        // Calculate and apply rating changes for Team 1
        for (player_id, player_rating) in &team1_ratings {
            let effective_opp = if is_doubles && team2_ratings.len() >= 2 && team1_ratings.len() >= 2 {
                // Find teammate
                let teammate_rating = team1_ratings.iter()
                    .find(|(pid, _)| pid != player_id)
                    .map(|(_, r)| *r)
                    .unwrap_or(START_RATING);
                
                effective_opponent_rating(
                    team2_ratings[0].1,
                    team2_ratings[1].1,
                    teammate_rating,
                )
            } else if is_doubles && team2_ratings.len() >= 2 {
                // Singles vs doubles scenario or missing teammate
                (team2_ratings[0].1 + team2_ratings[1].1) / 2.0
            } else {
                // Singles
                team2_ratings.get(0).map(|(_, r)| *r).unwrap_or(START_RATING)
            };
            
            let expected = expected_score(*player_rating, effective_opp);
            let rating_change = K_FACTOR * (team1_performance - expected);
            let new_rating = player_rating + rating_change;
            
            elo_ratings.insert(*player_id, new_rating);
        }
        
        // Calculate and apply rating changes for Team 2
        for (player_id, player_rating) in &team2_ratings {
            let effective_opp = if is_doubles && team1_ratings.len() >= 2 && team2_ratings.len() >= 2 {
                // Find teammate
                let teammate_rating = team2_ratings.iter()
                    .find(|(pid, _)| pid != player_id)
                    .map(|(_, r)| *r)
                    .unwrap_or(START_RATING);
                
                effective_opponent_rating(
                    team1_ratings[0].1,
                    team1_ratings[1].1,
                    teammate_rating,
                )
            } else if is_doubles && team1_ratings.len() >= 2 {
                (team1_ratings[0].1 + team1_ratings[1].1) / 2.0
            } else {
                team1_ratings.get(0).map(|(_, r)| *r).unwrap_or(START_RATING)
            };
            
            let expected = expected_score(*player_rating, effective_opp);
            let rating_change = K_FACTOR * (team2_performance - expected);
            let new_rating = player_rating + rating_change;
            
            elo_ratings.insert(*player_id, new_rating);
        }
        
        // Progress indicator every 5 matches
        if (i + 1) % 5 == 0 || i == all_matches.len() - 1 {
            println!("   Processed {}/{} matches", i + 1, all_matches.len());
        }
    }
    
    println!("\n✅ ELO ratings calculated\n");
    
    // Build comparison data
    let mut comparisons: Vec<PlayerRatings> = vec![];
    for (player_id, name, glicko2_singles, glicko2_doubles) in &players {
        let elo_unified = elo_ratings.get(player_id).copied().unwrap_or(START_RATING);
        let glicko2_avg = (glicko2_singles + glicko2_doubles) / 2.0;
        let matches = match_counts.get(player_id).copied().unwrap_or(0);
        
        comparisons.push(PlayerRatings {
            id: *player_id,
            name: name.clone(),
            glicko2_singles: *glicko2_singles,
            glicko2_doubles: *glicko2_doubles,
            elo_unified,
            glicko2_avg,
            elo_diff: elo_unified - glicko2_avg,
            matches_played: matches,
        });
    }
    
    // Sort by ELO rating (highest first)
    comparisons.sort_by(|a, b| b.elo_unified.partial_cmp(&a.elo_unified).unwrap());
    
    // Print summary
    println!("📊 Final Ratings Comparison:");
    println!("┌────────────────────────────┬────────────┬────────────┬────────────┬─────────┐");
    println!("│ Player                     │ Glicko Avg │ ELO Unified│ Difference │ Matches │");
    println!("├────────────────────────────┼────────────┼────────────┼────────────┼─────────┤");
    for p in &comparisons {
        let diff_str = if p.elo_diff >= 0.0 {
            format!("+{:.0}", p.elo_diff)
        } else {
            format!("{:.0}", p.elo_diff)
        };
        println!("│ {:26} │ {:>10.0} │ {:>10.0} │ {:>10} │ {:>7} │",
            p.name, p.glicko2_avg, p.elo_unified, diff_str, p.matches_played);
    }
    println!("└────────────────────────────┴────────────┴────────────┴────────────┴─────────┘");
    
    // Write JSON report
    let json_output = json!({
        "metadata": {
            "timestamp": chrono::Local::now().to_rfc3339(),
            "total_players": players.len(),
            "total_matches": all_matches.len(),
            "k_factor": K_FACTOR,
            "start_rating": START_RATING,
        },
        "system_description": {
            "old": "Glicko-2 with separate singles/doubles ratings, RD, volatility",
            "new": "Pure ELO with unified rating, per-point scoring, effective opponent formula",
        },
        "players": comparisons.iter().map(|p| json!({
            "id": p.id,
            "name": p.name,
            "glicko2": {
                "singles": (p.glicko2_singles * 10.0).round() / 10.0,
                "doubles": (p.glicko2_doubles * 10.0).round() / 10.0,
                "average": (p.glicko2_avg * 10.0).round() / 10.0,
            },
            "elo_unified": (p.elo_unified * 10.0).round() / 10.0,
            "difference": (p.elo_diff * 10.0).round() / 10.0,
            "matches_played": p.matches_played,
        })).collect::<Vec<_>>()
    });
    
    let json_path = "/Users/split/Projects/pickleball-elo/docs/rating-comparison.json";
    fs::write(json_path, serde_json::to_string_pretty(&json_output).unwrap()).ok();
    println!("\n💾 JSON report saved to: {}", json_path);
    
    // Write Markdown report
    let mut md = String::from("# Rating System Comparison: Glicko-2 vs Pure ELO\n\n");
    md.push_str("## Overview\n\n");
    md.push_str("This analysis replays all historical matches through the new ELO system to compare ratings.\n\n");
    md.push_str("**Key differences:**\n");
    md.push_str("- **Old system:** Glicko-2 with separate singles/doubles ratings, RD, volatility\n");
    md.push_str("- **New system:** Pure ELO with unified rating, per-point scoring, effective opponent formula\n\n");
    
    md.push_str("## Summary\n\n");
    md.push_str(&format!("- **Total Players:** {}\n", players.len()));
    md.push_str(&format!("- **Total Matches Replayed:** {}\n", all_matches.len()));
    md.push_str(&format!("- **K-Factor:** {}\n", K_FACTOR));
    md.push_str(&format!("- **Analysis Date:** {}\n\n", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")));
    
    md.push_str("## Ratings Comparison (Sorted by New ELO)\n\n");
    md.push_str("| Rank | Player | Glicko-2 Avg | New ELO | Diff | Matches |\n");
    md.push_str("|:----:|--------|-------------:|--------:|-----:|--------:|\n");
    
    for (i, player) in comparisons.iter().enumerate() {
        let diff_str = format!("{:+.0}", player.elo_diff);
        md.push_str(&format!(
            "| {} | {} | {:.0} | {:.0} | {} | {} |\n",
            i + 1,
            player.name,
            player.glicko2_avg,
            player.elo_unified,
            diff_str,
            player.matches_played
        ));
    }
    
    md.push_str("\n## Key Insights\n\n");
    
    // Find biggest movers
    let mut by_diff: Vec<_> = comparisons.iter().collect();
    by_diff.sort_by(|a, b| b.elo_diff.partial_cmp(&a.elo_diff).unwrap());
    
    md.push_str("### Biggest Winners (rating increased)\n\n");
    for player in by_diff.iter().take(3).filter(|p| p.elo_diff > 0.0) {
        md.push_str(&format!(
            "- **{}**: {:+.0} points (Glicko avg {:.0} → ELO {:.0})\n",
            player.name, player.elo_diff, player.glicko2_avg, player.elo_unified
        ));
    }
    
    md.push_str("\n### Biggest Losers (rating decreased)\n\n");
    for player in by_diff.iter().rev().take(3).filter(|p| p.elo_diff < 0.0) {
        md.push_str(&format!(
            "- **{}**: {:+.0} points (Glicko avg {:.0} → ELO {:.0})\n",
            player.name, player.elo_diff, player.glicko2_avg, player.elo_unified
        ));
    }
    
    md.push_str("\n## Why Ratings Changed\n\n");
    md.push_str("The new system differs in several ways:\n\n");
    md.push_str("1. **Per-point scoring**: Instead of just win/loss, we use `points_won / total_points`. ");
    md.push_str("Winning 11-9 gives less credit than winning 11-2.\n\n");
    md.push_str("2. **Effective opponent formula**: In doubles, your effective opponent is calculated as ");
    md.push_str("`Opp1 + Opp2 - Teammate`. This means:\n");
    md.push_str("   - Strong teammate → lower effective opponent → less credit for winning\n");
    md.push_str("   - Weak teammate → higher effective opponent → more credit for winning\n\n");
    md.push_str("3. **Unified rating**: Singles and doubles contribute to one rating instead of two.\n\n");
    
    let md_path = "/Users/split/Projects/pickleball-elo/docs/rating-comparison.md";
    fs::write(md_path, md).ok();
    println!("💾 Markdown report saved to: {}", md_path);
    
    println!("\n✅ Analysis complete!");
}
