//! Batch execution utilities for game matches.
//!
//! Provides reusable infrastructure for running games with optional parallelism
//! and progress reporting.
//!
//! When the `parallel` feature is enabled (default), games run in parallel using rayon.
//! When disabled (e.g., for WASM builds), games run sequentially.

#[cfg(feature = "parallel")]
use rayon::prelude::*;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};

use crate::core::PlayerId;

use super::progress::{ProgressReporter, ProgressStyle};
use super::seeds::GameSeeds;

/// Outcome of a single game.
#[derive(Clone, Debug)]
pub struct GameOutcome {
    /// Winner of the game (None for draw)
    pub winner: Option<PlayerId>,
    /// Number of turns played
    pub turns: u32,
    /// Duration of the game
    pub duration: Duration,
}

impl GameOutcome {
    /// Create a new game outcome.
    pub fn new(winner: Option<PlayerId>, turns: u32, duration: Duration) -> Self {
        Self {
            winner,
            turns,
            duration,
        }
    }
}

/// Configuration for batch game execution.
#[derive(Clone, Debug)]
pub struct BatchConfig {
    /// Number of games to run
    pub games: usize,
    /// Base seed for deterministic execution
    pub base_seed: u64,
    /// Whether to show progress indicator
    pub show_progress: bool,
    /// Progress style (Simple or Rich)
    pub progress_style: ProgressStyle,
    /// Optional prefix for progress output
    pub progress_prefix: String,
}

impl BatchConfig {
    /// Create a new batch configuration.
    pub fn new(games: usize, base_seed: u64) -> Self {
        Self {
            games,
            base_seed,
            show_progress: false,
            progress_style: ProgressStyle::Rich,
            progress_prefix: String::new(),
        }
    }

    /// Enable progress reporting with the specified style.
    pub fn with_progress(mut self, style: ProgressStyle) -> Self {
        self.show_progress = true;
        self.progress_style = style;
        self
    }

    /// Set a prefix for progress output.
    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
        self.progress_prefix = prefix.into();
        self
    }
}

/// Result of a batch execution.
#[derive(Debug)]
pub struct BatchResult {
    /// Individual game outcomes
    pub outcomes: Vec<GameOutcome>,
    /// Total wall-clock time for the batch
    pub wall_clock_time: Duration,
}

impl BatchResult {
    /// Count wins for Player 1.
    pub fn p1_wins(&self) -> usize {
        self.outcomes
            .iter()
            .filter(|o| o.winner == Some(PlayerId::PLAYER_ONE))
            .count()
    }

    /// Count wins for Player 2.
    pub fn p2_wins(&self) -> usize {
        self.outcomes
            .iter()
            .filter(|o| o.winner == Some(PlayerId::PLAYER_TWO))
            .count()
    }

    /// Count draws.
    pub fn draws(&self) -> usize {
        self.outcomes.iter().filter(|o| o.winner.is_none()).count()
    }

    /// Total number of games.
    pub fn total_games(&self) -> usize {
        self.outcomes.len()
    }

    /// Total turns across all games.
    pub fn total_turns(&self) -> u32 {
        self.outcomes.iter().map(|o| o.turns).sum()
    }

    /// Average turns per game.
    pub fn avg_turns(&self) -> f64 {
        if self.outcomes.is_empty() {
            0.0
        } else {
            self.total_turns() as f64 / self.outcomes.len() as f64
        }
    }

    /// Total CPU time (sum of individual game durations).
    pub fn cpu_time(&self) -> Duration {
        self.outcomes.iter().map(|o| o.duration).sum()
    }
}

/// Run a batch of games.
///
/// When the `parallel` feature is enabled, games run in parallel using rayon.
/// When disabled, games run sequentially (suitable for WASM).
///
/// # Arguments
/// * `config` - Batch execution configuration
/// * `run_game` - Closure that takes `GameSeeds` and returns `GameOutcome`
///
/// # Returns
/// A `BatchResult` containing all outcomes and timing information.
///
/// # Example
/// ```ignore
/// let config = BatchConfig::new(100, 42).with_progress(ProgressStyle::Rich);
/// let result = run_batch_parallel(&config, |seeds| {
///     // Run game with seeds.game, seeds.bot1, seeds.bot2
///     GameOutcome::new(Some(PlayerId::PLAYER_ONE), 25, Duration::from_millis(50))
/// });
/// ```
#[cfg(feature = "parallel")]
pub fn run_batch_parallel<F>(config: &BatchConfig, run_game: F) -> BatchResult
where
    F: Fn(GameSeeds) -> GameOutcome + Send + Sync,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games in parallel
    let outcomes: Vec<GameOutcome> = (0..config.games)
        .into_par_iter()
        .map(|i| {
            let seeds = GameSeeds::for_game(config.base_seed, i);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResult {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games sequentially (WASM-compatible fallback).
#[cfg(not(feature = "parallel"))]
pub fn run_batch_parallel<F>(config: &BatchConfig, run_game: F) -> BatchResult
where
    F: Fn(GameSeeds) -> GameOutcome,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games sequentially
    let outcomes: Vec<GameOutcome> = (0..config.games)
        .map(|i| {
            let seeds = GameSeeds::for_game(config.base_seed, i);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResult {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games for a specific matchup.
///
/// Similar to `run_batch_parallel` but uses matchup-aware seed derivation.
///
/// # Arguments
/// * `config` - Batch execution configuration
/// * `matchup_index` - Index of this matchup (for seed derivation)
/// * `reverse_direction` - Whether this is the reverse direction
/// * `run_game` - Closure that takes `GameSeeds` and returns `GameOutcome`
#[cfg(feature = "parallel")]
pub fn run_batch_parallel_matchup<F>(
    config: &BatchConfig,
    matchup_index: usize,
    reverse_direction: bool,
    run_game: F,
) -> BatchResult
where
    F: Fn(GameSeeds) -> GameOutcome + Send + Sync,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games in parallel
    let outcomes: Vec<GameOutcome> = (0..config.games)
        .into_par_iter()
        .map(|i| {
            let seeds =
                GameSeeds::for_matchup(config.base_seed, matchup_index, i, reverse_direction);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResult {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games for a specific matchup (sequential fallback).
#[cfg(not(feature = "parallel"))]
pub fn run_batch_parallel_matchup<F>(
    config: &BatchConfig,
    matchup_index: usize,
    reverse_direction: bool,
    run_game: F,
) -> BatchResult
where
    F: Fn(GameSeeds) -> GameOutcome,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games sequentially
    let outcomes: Vec<GameOutcome> = (0..config.games)
        .map(|i| {
            let seeds =
                GameSeeds::for_matchup(config.base_seed, matchup_index, i, reverse_direction);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResult {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Configure the global rayon thread pool.
///
/// # Arguments
/// * `threads` - Number of threads (0 = use rayon's default)
///
/// # Returns
/// The actual number of threads configured.
///
/// Note: This is a no-op when the `parallel` feature is disabled.
#[cfg(feature = "parallel")]
pub fn configure_thread_pool(threads: usize) -> usize {
    if threads > 0 {
        rayon::ThreadPoolBuilder::new()
            .num_threads(threads)
            .build_global()
            .ok(); // Ignore if already initialized
        threads
    } else {
        rayon::current_num_threads()
    }
}

/// No-op thread pool configuration for non-parallel builds.
#[cfg(not(feature = "parallel"))]
pub fn configure_thread_pool(_threads: usize) -> usize {
    1 // Single-threaded
}

// ============================================================================
// Metrics-aware batch execution
// ============================================================================

use super::metrics::{AggregatedMetrics, GameMetricsData};

/// Extended game outcome with full metrics.
///
/// This type combines the basic `GameOutcome` with comprehensive per-game
/// metrics for detailed analysis.
#[derive(Clone, Debug)]
pub struct GameOutcomeWithMetrics {
    /// Full metrics data (includes winner, turns, duration, and detailed metrics).
    pub metrics: GameMetricsData,
}

impl GameOutcomeWithMetrics {
    /// Create a new outcome with metrics.
    pub fn new(metrics: GameMetricsData) -> Self {
        Self { metrics }
    }

    /// Create from basic outcome (minimal metrics).
    pub fn from_outcome(winner: Option<PlayerId>, turns: u32, duration: Duration, seed: u64) -> Self {
        Self {
            metrics: GameMetricsData::from_outcome(winner, turns, duration, seed),
        }
    }

    /// Get the basic game outcome.
    pub fn outcome(&self) -> GameOutcome {
        GameOutcome {
            winner: self.metrics.winner,
            turns: self.metrics.turns,
            duration: self.metrics.duration,
        }
    }
}

/// Batch result with full metrics.
#[derive(Debug)]
pub struct BatchResultWithMetrics {
    /// Individual game outcomes with metrics.
    pub outcomes: Vec<GameOutcomeWithMetrics>,
    /// Total wall-clock time for the batch.
    pub wall_clock_time: Duration,
}

impl BatchResultWithMetrics {
    /// Count wins for Player 1.
    pub fn p1_wins(&self) -> usize {
        self.outcomes
            .iter()
            .filter(|o| o.metrics.p1_won())
            .count()
    }

    /// Count wins for Player 2.
    pub fn p2_wins(&self) -> usize {
        self.outcomes
            .iter()
            .filter(|o| o.metrics.p2_won())
            .count()
    }

    /// Count draws.
    pub fn draws(&self) -> usize {
        self.outcomes
            .iter()
            .filter(|o| o.metrics.is_draw())
            .count()
    }

    /// Total number of games.
    pub fn total_games(&self) -> usize {
        self.outcomes.len()
    }

    /// Total turns across all games.
    pub fn total_turns(&self) -> u32 {
        self.outcomes.iter().map(|o| o.metrics.turns).sum()
    }

    /// Average turns per game.
    pub fn avg_turns(&self) -> f64 {
        if self.outcomes.is_empty() {
            0.0
        } else {
            self.total_turns() as f64 / self.outcomes.len() as f64
        }
    }

    /// Total CPU time (sum of individual game durations).
    pub fn cpu_time(&self) -> Duration {
        self.outcomes.iter().map(|o| o.metrics.duration).sum()
    }

    /// Aggregate all metrics.
    pub fn aggregate(&self) -> AggregatedMetrics {
        let metrics: Vec<_> = self.outcomes.iter().map(|o| o.metrics.clone()).collect();
        AggregatedMetrics::from_games(&metrics)
    }

    /// Convert to basic BatchResult (loses detailed metrics).
    pub fn to_basic_result(&self) -> BatchResult {
        BatchResult {
            outcomes: self.outcomes.iter().map(|o| o.outcome()).collect(),
            wall_clock_time: self.wall_clock_time,
        }
    }
}

/// Run a batch of games collecting full metrics.
///
/// Similar to `run_batch_parallel` but the closure returns `GameOutcomeWithMetrics`,
/// enabling detailed analysis of game statistics.
///
/// # Example
///
/// ```ignore
/// let config = BatchConfig::new(100, 42).with_progress(ProgressStyle::Rich);
/// let result = run_batch_parallel_with_metrics(&config, |seeds| {
///     let metrics = run_game_with_metrics(seeds);
///     GameOutcomeWithMetrics::new(metrics)
/// });
///
/// let aggregated = result.aggregate();
/// println!("P1 win rate: {:.1}%", aggregated.p1_win_rate() * 100.0);
/// ```
#[cfg(feature = "parallel")]
pub fn run_batch_parallel_with_metrics<F>(
    config: &BatchConfig,
    run_game: F,
) -> BatchResultWithMetrics
where
    F: Fn(GameSeeds) -> GameOutcomeWithMetrics + Send + Sync,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games in parallel
    let outcomes: Vec<GameOutcomeWithMetrics> = (0..config.games)
        .into_par_iter()
        .map(|i| {
            let seeds = GameSeeds::for_game(config.base_seed, i);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResultWithMetrics {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games collecting full metrics (sequential fallback).
#[cfg(not(feature = "parallel"))]
pub fn run_batch_parallel_with_metrics<F>(
    config: &BatchConfig,
    run_game: F,
) -> BatchResultWithMetrics
where
    F: Fn(GameSeeds) -> GameOutcomeWithMetrics,
{
    let start_time = Instant::now();

    // Set up progress reporting
    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    // Run games sequentially
    let outcomes: Vec<GameOutcomeWithMetrics> = (0..config.games)
        .map(|i| {
            let seeds = GameSeeds::for_game(config.base_seed, i);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    // Finish progress reporting
    if let Some(p) = progress {
        p.finish();
    }

    BatchResultWithMetrics {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games for a matchup with full metrics.
///
/// Uses matchup-aware seed derivation for deterministic, reproducible results.
#[cfg(feature = "parallel")]
pub fn run_batch_parallel_matchup_with_metrics<F>(
    config: &BatchConfig,
    matchup_index: usize,
    reverse_direction: bool,
    run_game: F,
) -> BatchResultWithMetrics
where
    F: Fn(GameSeeds) -> GameOutcomeWithMetrics + Send + Sync,
{
    let start_time = Instant::now();

    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    let outcomes: Vec<GameOutcomeWithMetrics> = (0..config.games)
        .into_par_iter()
        .map(|i| {
            let seeds =
                GameSeeds::for_matchup(config.base_seed, matchup_index, i, reverse_direction);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    if let Some(p) = progress {
        p.finish();
    }

    BatchResultWithMetrics {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}

/// Run a batch of games for a matchup with full metrics (sequential fallback).
#[cfg(not(feature = "parallel"))]
pub fn run_batch_parallel_matchup_with_metrics<F>(
    config: &BatchConfig,
    matchup_index: usize,
    reverse_direction: bool,
    run_game: F,
) -> BatchResultWithMetrics
where
    F: Fn(GameSeeds) -> GameOutcomeWithMetrics,
{
    let start_time = Instant::now();

    let progress = if config.show_progress {
        Some(
            ProgressReporter::new(config.games)
                .with_style(config.progress_style)
                .with_prefix(config.progress_prefix.clone())
                .start(),
        )
    } else {
        None
    };

    let counter = progress.as_ref().map(|p| p.counter());

    let outcomes: Vec<GameOutcomeWithMetrics> = (0..config.games)
        .map(|i| {
            let seeds =
                GameSeeds::for_matchup(config.base_seed, matchup_index, i, reverse_direction);
            let outcome = run_game(seeds);
            if let Some(ref c) = counter {
                c.fetch_add(1, Ordering::Relaxed);
            }
            outcome
        })
        .collect();

    if let Some(p) = progress {
        p.finish();
    }

    BatchResultWithMetrics {
        outcomes,
        wall_clock_time: start_time.elapsed(),
    }
}