//! Unified game metrics for analysis across all binaries.
//!
//! Provides comprehensive per-game metrics that combine outcome data with
//! detailed gameplay analysis. This replaces the fragmented metrics collection
//! that was duplicated across validate and diagnose binaries.

use std::time::Duration;

use serde::Serialize;

use crate::core::PlayerId;

/// Comprehensive per-game metrics for analysis.
///
/// This struct combines basic outcome data (winner, turns, duration) with
/// detailed gameplay metrics (board advantage, resource efficiency, combat
/// statistics, tempo indicators).
///
/// Used by both validation and diagnostic pipelines to ensure consistent
/// metrics collection across all binaries.
#[derive(Clone, Debug, Default, Serialize)]
pub struct GameMetricsData {
    // === Outcome ===
    /// Winner of the game (None for draw).
    pub winner: Option<PlayerId>,
    /// Total turns played.
    pub turns: u32,
    /// Game duration.
    #[serde(with = "duration_millis")]
    pub duration: Duration,
    /// Random seed used for this game.
    pub seed: u64,

    // === First Events ===
    /// Turn when first damage was dealt to either player.
    pub first_blood_turn: Option<u32>,
    /// Player who dealt first damage.
    pub first_blood_player: Option<PlayerId>,
    /// Turn when first creature died.
    pub first_creature_death_turn: Option<u32>,

    // === Board Advantage ===
    /// Average board advantage score (positive = P1 ahead).
    ///
    /// Calculated as average of (life_diff + attack_diff + health_diff) / 3
    /// across all turns.
    pub avg_board_advantage: f64,
    /// Number of turns P1 was ahead (score > threshold).
    pub turns_p1_ahead: u32,
    /// Number of turns P2 was ahead (score < -threshold).
    pub turns_p2_ahead: u32,
    /// Number of turns roughly even (|score| <= threshold).
    pub turns_even: u32,

    // === Resource Efficiency ===
    /// Total essence spent by P1.
    pub p1_essence_spent: u32,
    /// Total essence spent by P2.
    pub p2_essence_spent: u32,
    /// Board impact generated by P1 (sum of attack + health of creatures played).
    pub p1_board_impact: i32,
    /// Board impact generated by P2.
    pub p2_board_impact: i32,

    // === Combat Efficiency ===
    /// Face damage dealt by P1 to P2's commander.
    pub p1_face_damage: u32,
    /// Face damage dealt by P2 to P1's commander.
    pub p2_face_damage: u32,
    /// Creatures killed by P1.
    pub p1_creatures_killed: u32,
    /// Creatures killed by P2.
    pub p2_creatures_killed: u32,
    /// Creatures lost by P1.
    pub p1_creatures_lost: u32,
    /// Creatures lost by P2.
    pub p2_creatures_lost: u32,

    // === Tempo ===
    /// Turn when P1 played their first creature.
    pub p1_first_creature_turn: Option<u32>,
    /// Turn when P2 played their first creature.
    pub p2_first_creature_turn: Option<u32>,

    // === Actions ===
    /// Total actions taken by P1.
    pub p1_actions: u32,
    /// Total actions taken by P2.
    pub p2_actions: u32,
}

impl GameMetricsData {
    /// Create minimal metrics from just outcome (for backward compatibility).
    ///
    /// This is useful when you only need basic outcome data without
    /// detailed metrics collection.
    pub fn from_outcome(
        winner: Option<PlayerId>,
        turns: u32,
        duration: Duration,
        seed: u64,
    ) -> Self {
        Self {
            winner,
            turns,
            duration,
            seed,
            ..Default::default()
        }
    }

    /// Calculate P1's resource efficiency (board impact per essence spent).
    pub fn p1_efficiency(&self) -> f64 {
        if self.p1_essence_spent == 0 {
            0.0
        } else {
            self.p1_board_impact as f64 / self.p1_essence_spent as f64
        }
    }

    /// Calculate P2's resource efficiency (board impact per essence spent).
    pub fn p2_efficiency(&self) -> f64 {
        if self.p2_essence_spent == 0 {
            0.0
        } else {
            self.p2_board_impact as f64 / self.p2_essence_spent as f64
        }
    }

    /// Calculate P1's trade ratio (creatures killed / creatures lost).
    ///
    /// Returns:
    /// - `None` if no creatures were killed or lost (no trades)
    /// - `Some(f64::INFINITY)` if killed creatures but lost none (perfect)
    /// - `Some(ratio)` otherwise
    pub fn p1_trade_ratio(&self) -> Option<f64> {
        match (self.p1_creatures_killed, self.p1_creatures_lost) {
            (0, 0) => None,                    // No trades occurred
            (_, 0) => Some(f64::INFINITY), // Perfect: killed without losing
            (kills, losses) => Some(kills as f64 / losses as f64),
        }
    }

    /// Calculate P2's trade ratio (creatures killed / creatures lost).
    ///
    /// Returns:
    /// - `None` if no creatures were killed or lost (no trades)
    /// - `Some(f64::INFINITY)` if killed creatures but lost none (perfect)
    /// - `Some(ratio)` otherwise
    pub fn p2_trade_ratio(&self) -> Option<f64> {
        match (self.p2_creatures_killed, self.p2_creatures_lost) {
            (0, 0) => None,                    // No trades occurred
            (_, 0) => Some(f64::INFINITY), // Perfect: killed without losing
            (kills, losses) => Some(kills as f64 / losses as f64),
        }
    }

    /// Check if P1 won.
    pub fn p1_won(&self) -> bool {
        self.winner == Some(PlayerId::PLAYER_ONE)
    }

    /// Check if P2 won.
    pub fn p2_won(&self) -> bool {
        self.winner == Some(PlayerId::PLAYER_TWO)
    }

    /// Check if the game was a draw.
    pub fn is_draw(&self) -> bool {
        self.winner.is_none()
    }

    /// Check if P1 got first blood.
    pub fn p1_first_blood(&self) -> bool {
        self.first_blood_player == Some(PlayerId::PLAYER_ONE)
    }

    /// Check if P2 got first blood.
    pub fn p2_first_blood(&self) -> bool {
        self.first_blood_player == Some(PlayerId::PLAYER_TWO)
    }
}

/// Aggregated metrics across multiple games.
///
/// Used for computing summary statistics from a batch of game results.
#[derive(Clone, Debug, Default, Serialize)]
pub struct AggregatedMetrics {
    /// Total games played.
    pub total_games: u32,
    /// P1 wins.
    pub p1_wins: u32,
    /// P2 wins.
    pub p2_wins: u32,
    /// Draws.
    pub draws: u32,

    /// Total turns across all games.
    pub total_turns: u32,
    /// Total duration across all games.
    #[serde(with = "duration_millis")]
    pub total_duration: Duration,

    // === First Blood ===
    /// Games where P1 got first blood.
    pub p1_first_blood_count: u32,
    /// Games where P2 got first blood.
    pub p2_first_blood_count: u32,

    // === Board Advantage ===
    /// Sum of avg_board_advantage across all games.
    pub total_board_advantage: f64,
    /// Total turns P1 was ahead.
    pub total_turns_p1_ahead: u32,
    /// Total turns P2 was ahead.
    pub total_turns_p2_ahead: u32,
    /// Total turns even.
    pub total_turns_even: u32,

    // === Resources ===
    /// Total essence spent by P1.
    pub total_p1_essence: u32,
    /// Total essence spent by P2.
    pub total_p2_essence: u32,
    /// Total board impact by P1.
    pub total_p1_board_impact: i64,
    /// Total board impact by P2.
    pub total_p2_board_impact: i64,

    // === Combat ===
    /// Total face damage by P1.
    pub total_p1_face_damage: u32,
    /// Total face damage by P2.
    pub total_p2_face_damage: u32,
    /// Total creatures killed by P1.
    pub total_p1_kills: u32,
    /// Total creatures killed by P2.
    pub total_p2_kills: u32,
    /// Total creatures lost by P1.
    pub total_p1_losses: u32,
    /// Total creatures lost by P2.
    pub total_p2_losses: u32,

    // === Game Lengths ===
    /// Individual game lengths for percentile calculation.
    #[serde(skip)]
    pub game_lengths: Vec<u32>,
}

impl AggregatedMetrics {
    /// Create aggregated metrics from a collection of game metrics.
    pub fn from_games(games: &[GameMetricsData]) -> Self {
        let mut agg = Self::default();

        for game in games {
            agg.total_games += 1;

            match game.winner {
                Some(PlayerId::PLAYER_ONE) => agg.p1_wins += 1,
                Some(PlayerId::PLAYER_TWO) => agg.p2_wins += 1,
                None => agg.draws += 1,
                _ => {}
            }

            agg.total_turns += game.turns;
            agg.total_duration += game.duration;
            agg.game_lengths.push(game.turns);

            // First blood
            if game.p1_first_blood() {
                agg.p1_first_blood_count += 1;
            } else if game.p2_first_blood() {
                agg.p2_first_blood_count += 1;
            }

            // Board advantage
            agg.total_board_advantage += game.avg_board_advantage;
            agg.total_turns_p1_ahead += game.turns_p1_ahead;
            agg.total_turns_p2_ahead += game.turns_p2_ahead;
            agg.total_turns_even += game.turns_even;

            // Resources
            agg.total_p1_essence += game.p1_essence_spent;
            agg.total_p2_essence += game.p2_essence_spent;
            agg.total_p1_board_impact += game.p1_board_impact as i64;
            agg.total_p2_board_impact += game.p2_board_impact as i64;

            // Combat
            agg.total_p1_face_damage += game.p1_face_damage;
            agg.total_p2_face_damage += game.p2_face_damage;
            agg.total_p1_kills += game.p1_creatures_killed;
            agg.total_p2_kills += game.p2_creatures_killed;
            agg.total_p1_losses += game.p1_creatures_lost;
            agg.total_p2_losses += game.p2_creatures_lost;
        }

        agg
    }

    /// P1 win rate.
    pub fn p1_win_rate(&self) -> f64 {
        if self.total_games == 0 {
            0.0
        } else {
            self.p1_wins as f64 / self.total_games as f64
        }
    }

    /// P2 win rate.
    pub fn p2_win_rate(&self) -> f64 {
        if self.total_games == 0 {
            0.0
        } else {
            self.p2_wins as f64 / self.total_games as f64
        }
    }

    /// Draw rate.
    pub fn draw_rate(&self) -> f64 {
        if self.total_games == 0 {
            0.0
        } else {
            self.draws as f64 / self.total_games as f64
        }
    }

    /// Average turns per game.
    pub fn avg_turns(&self) -> f64 {
        if self.total_games == 0 {
            0.0
        } else {
            self.total_turns as f64 / self.total_games as f64
        }
    }

    /// Average board advantage (positive = P1 favored).
    pub fn avg_board_advantage(&self) -> f64 {
        if self.total_games == 0 {
            0.0
        } else {
            self.total_board_advantage / self.total_games as f64
        }
    }

    /// P1 first blood rate.
    pub fn p1_first_blood_rate(&self) -> f64 {
        let total = self.p1_first_blood_count + self.p2_first_blood_count;
        if total == 0 {
            0.5
        } else {
            self.p1_first_blood_count as f64 / total as f64
        }
    }

    /// P1 resource efficiency.
    pub fn p1_efficiency(&self) -> f64 {
        if self.total_p1_essence == 0 {
            0.0
        } else {
            self.total_p1_board_impact as f64 / self.total_p1_essence as f64
        }
    }

    /// P2 resource efficiency.
    pub fn p2_efficiency(&self) -> f64 {
        if self.total_p2_essence == 0 {
            0.0
        } else {
            self.total_p2_board_impact as f64 / self.total_p2_essence as f64
        }
    }

    /// P1 trade ratio.
    pub fn p1_trade_ratio(&self) -> f64 {
        if self.total_p1_losses == 0 {
            self.total_p1_kills as f64
        } else {
            self.total_p1_kills as f64 / self.total_p1_losses as f64
        }
    }

    /// P2 trade ratio.
    pub fn p2_trade_ratio(&self) -> f64 {
        if self.total_p2_losses == 0 {
            self.total_p2_kills as f64
        } else {
            self.total_p2_kills as f64 / self.total_p2_losses as f64
        }
    }

    /// Merge another aggregation into this one.
    pub fn merge(&mut self, other: &Self) {
        self.total_games += other.total_games;
        self.p1_wins += other.p1_wins;
        self.p2_wins += other.p2_wins;
        self.draws += other.draws;
        self.total_turns += other.total_turns;
        self.total_duration += other.total_duration;
        self.p1_first_blood_count += other.p1_first_blood_count;
        self.p2_first_blood_count += other.p2_first_blood_count;
        self.total_board_advantage += other.total_board_advantage;
        self.total_turns_p1_ahead += other.total_turns_p1_ahead;
        self.total_turns_p2_ahead += other.total_turns_p2_ahead;
        self.total_turns_even += other.total_turns_even;
        self.total_p1_essence += other.total_p1_essence;
        self.total_p2_essence += other.total_p2_essence;
        self.total_p1_board_impact += other.total_p1_board_impact;
        self.total_p2_board_impact += other.total_p2_board_impact;
        self.total_p1_face_damage += other.total_p1_face_damage;
        self.total_p2_face_damage += other.total_p2_face_damage;
        self.total_p1_kills += other.total_p1_kills;
        self.total_p2_kills += other.total_p2_kills;
        self.total_p1_losses += other.total_p1_losses;
        self.total_p2_losses += other.total_p2_losses;
        self.game_lengths.extend(&other.game_lengths);
    }
}

/// Serde helper for serializing Duration as milliseconds.
mod duration_millis {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use std::time::Duration;

    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        duration.as_millis().serialize(serializer)
    }

    #[allow(dead_code)] // Needed for serde symmetry, used if deserializing metrics from JSON
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
    where
        D: Deserializer<'de>,
    {
        let millis = u64::deserialize(deserializer)?;
        Ok(Duration::from_millis(millis))
    }
}