//! Arena CLI - Run matches between bots.
//!
//! Usage:
//!   cargo run --release --bin arena -- --bot1 random --bot2 random --games 100
//!   cargo run --release --bin arena -- --bot1 greedy --bot2 greedy --games 100
//!   cargo run --release --bin arena -- --bot1 mcts --bot2 greedy --seed 12345 --debug
//!   cargo run --release --bin arena -- --bot1 alphabeta --bot2 mcts --ab-depth 8 --games 50
//!   cargo run --release --bin arena -- --deck1 broodmother_pack --deck2 architect_fortify
//!
//! Agent types (with auto-loaded specialist weights):
//!   cargo run --release --bin arena -- --bot1 agent-argentum --bot2 agent-symbiote
//!   cargo run --release --bin arena -- --bot1 agent-generalist --bot2 agent-obsidion

use std::path::PathBuf;
use std::process;

use clap::Parser;

use cardgame::arena::{
    load_deck, run_match_parallel, run_match_sequential, validate_faction_deck_binding,
    ActionLogger, EloTracker, MatchConfig, SequentialConfig,
};
use cardgame::bots::{AlphaBetaConfig, BotType, MctsConfig};
use cardgame::core::state::GameMode;
use cardgame::execution::{configure_thread_pool, parse_bot_type_or_exit, resolve_seed, GameData};

/// Arena - Run matches between card game bots
#[derive(Parser, Debug)]
#[command(name = "arena")]
#[command(about = "Run matches between card game bots", long_about = None)]
struct Args {
    /// Bot 1 type (random, greedy, mcts, alphabeta, agent-*, neural:<path>)
    #[arg(long, default_value = "random")]
    bot1: String,

    /// Bot 2 type (random, greedy, mcts, alphabeta, agent-*, neural:<path>)
    #[arg(long, default_value = "random")]
    bot2: String,

    /// Deck 1 ID (use --list-decks to see available decks)
    #[arg(long)]
    deck1: Option<String>,

    /// Deck 2 ID (use --list-decks to see available decks)
    #[arg(long)]
    deck2: Option<String>,

    /// Number of games to play
    #[arg(long, short = 'n', default_value = "100")]
    games: usize,

    /// Random seed (uses system time if not specified)
    #[arg(long, short = 's')]
    seed: Option<u64>,

    /// Enable debug logging
    #[arg(long, short = 'd')]
    debug: bool,

    /// Verbose debug output (includes state snapshots)
    #[arg(long, short = 'v')]
    verbose: bool,

    /// Log file path (defaults to stdout if not specified)
    #[arg(long)]
    log_file: Option<PathBuf>,

    /// Path to card database
    #[arg(long, default_value = "data/cards/core_set")]
    cards: PathBuf,

    /// Path to commander definitions directory
    #[arg(long, default_value = "data/commanders")]
    commanders: PathBuf,

    /// Path to deck definitions directory
    #[arg(long, default_value = "data/decks")]
    decks: PathBuf,

    /// List available decks and exit
    #[arg(long)]
    list_decks: bool,

    /// Show progress bar during match
    #[arg(long)]
    progress: bool,

    /// Custom weights file for bot 1 (TOML format, for greedy/mcts/alphabeta)
    #[arg(long)]
    weights1: Option<PathBuf>,

    /// Custom weights file for bot 2 (TOML format, for greedy/mcts/alphabeta)
    #[arg(long)]
    weights2: Option<PathBuf>,

    /// Number of threads for parallel execution (0 = use all cores)
    #[arg(long, short = 'j', default_value = "0")]
    threads: usize,

    /// Disable parallel execution (run sequentially)
    #[arg(long)]
    sequential: bool,

    /// Number of parallel trees for MCTS root parallelization (1 = sequential)
    #[arg(long, default_value = "1")]
    mcts_trees: u32,

    /// Number of simulations per MCTS tree
    #[arg(long, default_value = "500")]
    mcts_sims: u32,

    /// Number of parallel rollouts per MCTS leaf (1 = sequential)
    #[arg(long, default_value = "1")]
    mcts_rollouts: u32,

    /// Alpha-Beta search depth (plies)
    #[arg(long, default_value = "6")]
    ab_depth: u32,

    /// Use legacy Alpha-Beta config (no TT, no LMR, no aspiration windows)
    #[arg(long)]
    ab_legacy: bool,

    /// Enable invariant checking after every action (forces sequential mode, slower)
    #[arg(long)]
    invariants: bool,

    /// Enable combat keyword resolution tracing (forces sequential mode)
    #[arg(long)]
    trace_combat: bool,

    /// Enable effect queue tracing (forces sequential mode)
    #[arg(long)]
    trace_effects: bool,

    /// Enable all tracing (combat + effects, forces sequential mode)
    #[arg(long)]
    trace_all: bool,

    /// Game mode: essence-war (default) or attrition
    #[arg(long, default_value = "essence-war")]
    mode: String,

    /// Export game replays to directory (forces sequential mode)
    #[arg(long)]
    export_replays: Option<PathBuf>,
}

fn main() {
    env_logger::init();
    let args = Args::parse();

    // Load game data using unified loader
    let game_data = match GameData::load_with_overrides(
        Some(&args.cards),
        Some(&args.commanders),
        Some(&args.decks),
        None, // weights handled separately
        true, // quiet loading
    ) {
        Ok(data) => data,
        Err(e) => {
            // Only warn if decks were specifically requested
            if args.deck1.is_some() || args.deck2.is_some() || args.list_decks {
                eprintln!("Error loading game data: {}", e);
                process::exit(1);
            }
            // Fall back to loading just cards and commanders for default decks
            match GameData::load_with_overrides(
                Some(&args.cards),
                Some(&args.commanders),
                None, // no decks
                None, // no weights
                true,
            ) {
                Ok(data) => data,
                Err(e) => {
                    eprintln!("Error loading card database: {}", e);
                    process::exit(1);
                }
            }
        }
    };

    // Handle --list-decks
    if args.list_decks {
        println!("Available decks:");
        if game_data.deck_registry.is_empty() {
            println!("  (no decks found in {:?})", args.decks);
        } else {
            for deck in game_data.deck_registry.decks() {
                // Show commander name if available
                let commander_info = game_data
                    .card_db
                    .get_commander(cardgame::types::CardId(deck.commander))
                    .map(|c| format!(" [{}]", c.name))
                    .unwrap_or_default();
                println!(
                    "  {} - {} ({} cards){}",
                    deck.id,
                    deck.name,
                    deck.size(),
                    commander_info
                );
                if !deck.tags.is_empty() {
                    println!("    Tags: {}", deck.tags.join(", "));
                }
            }
        }
        return;
    }

    // Parse bot types using shared helper
    let bot1_type = parse_bot_type_or_exit(&args.bot1);
    let bot2_type = parse_bot_type_or_exit(&args.bot2);

    // Create seed (use provided or generate from time)
    let seed = resolve_seed(args.seed);

    // Load decks (now returns DeckDefinition with commander)
    let deck1 = match load_deck(args.deck1.as_deref(), &game_data.deck_registry, &game_data.card_db, "1") {
        Ok(d) => d,
        Err(e) => {
            eprintln!("{}", e);
            process::exit(1);
        }
    };
    let deck2 = match load_deck(args.deck2.as_deref(), &game_data.deck_registry, &game_data.card_db, "2") {
        Ok(d) => d,
        Err(e) => {
            eprintln!("{}", e);
            process::exit(1);
        }
    };
    let (deck1_name, deck2_name) = (deck1.name.clone(), deck2.name.clone());

    // Validate faction-deck binding for specialist agents
    if let Some(warning) = validate_faction_deck_binding(
        &bot1_type,
        args.deck1.as_deref(),
        &game_data.deck_registry,
        "Bot 1",
    ) {
        eprintln!("{}", warning);
    }
    if let Some(warning) = validate_faction_deck_binding(
        &bot2_type,
        args.deck2.as_deref(),
        &game_data.deck_registry,
        "Bot 2",
    ) {
        eprintln!("{}", warning);
    }

    // Get current working directory for weight resolution
    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));

    // Resolve weights for both bots (with archetype fallback based on deck playstyle)
    let weights1 = match cardgame::bots::resolve_weights_with_archetype_verbose(
        &bot1_type,
        args.weights1.as_deref(),
        Some(deck1.playstyle.as_str()),
        &cwd,
        "Bot 1",
    ) {
        Ok(w) => w,
        Err(e) => {
            eprintln!("Error loading weights for Bot 1: {}", e);
            process::exit(1);
        }
    };
    let weights2 = match cardgame::bots::resolve_weights_with_archetype_verbose(
        &bot2_type,
        args.weights2.as_deref(),
        Some(deck2.playstyle.as_str()),
        &cwd,
        "Bot 2",
    ) {
        Ok(w) => w,
        Err(e) => {
            eprintln!("Error loading weights for Bot 2: {}", e);
            process::exit(1);
        }
    };

    // Create logger if needed
    let mut logger = if args.debug || args.verbose {
        let l = if let Some(ref path) = args.log_file {
            match ActionLogger::to_file(path, args.verbose) {
                Ok(l) => l,
                Err(e) => {
                    eprintln!("Error creating log file {:?}: {}", path, e);
                    process::exit(1);
                }
            }
        } else {
            ActionLogger::stdout(args.verbose)
        };
        Some(l)
    } else {
        None
    };

    // Configure thread pool
    let num_threads = configure_thread_pool(args.threads);

    // Determine tracing options
    let trace_combat = args.trace_combat || args.trace_all;
    let trace_effects = args.trace_effects || args.trace_all;
    let tracing_enabled = trace_combat || trace_effects;

    // Can't parallelize with logging, invariant checking, tracing, or replay export
    let parallel = !args.sequential && logger.is_none() && !args.invariants && !tracing_enabled && args.export_replays.is_none();

    // Print match info
    println!("Arena Match");
    println!("===========");
    println!(
        "Bot 1: {} ({}){}",
        bot1_type.name(),
        deck1_name,
        weights1
            .as_ref()
            .map(|w| format!(" [weights: {}]", w.name))
            .unwrap_or_default()
    );
    println!(
        "Bot 2: {} ({}){}",
        bot2_type.name(),
        deck2_name,
        weights2
            .as_ref()
            .map(|w| format!(" [weights: {}]", w.name))
            .unwrap_or_default()
    );
    println!("Games: {}", args.games);
    println!("Base seed: {}", seed);
    if parallel {
        println!("Threads: {} (parallel)", num_threads);
    } else {
        let mut mode_notes = Vec::new();
        if logger.is_some() {
            mode_notes.push("logging");
        }
        if args.invariants {
            mode_notes.push("invariants");
        }
        if trace_combat {
            mode_notes.push("combat-trace");
        }
        if trace_effects {
            mode_notes.push("effect-trace");
        }
        if args.export_replays.is_some() {
            mode_notes.push("replay-export");
        }
        let note = if mode_notes.is_empty() {
            String::new()
        } else {
            format!(" ({})", mode_notes.join(", "))
        };
        println!("Mode: sequential{}", note);
    }
    println!();

    // Create MCTS config
    let mcts_config = MctsConfig {
        simulations: args.mcts_sims,
        exploration: 1.414,
        max_rollout_depth: 100,
        parallel_trees: args.mcts_trees,
        leaf_rollouts: args.mcts_rollouts,
        ..MctsConfig::default()
    };

    // Create Alpha-Beta config
    let alphabeta_config = if args.ab_legacy {
        AlphaBetaConfig::legacy(args.ab_depth)
    } else {
        AlphaBetaConfig::with_depth(args.ab_depth)
    };

    // Print MCTS config if using MCTS
    if bot1_type.uses_mcts() || bot2_type.uses_mcts() {
        println!(
            "MCTS: {} sims x {} trees x {} rollouts/leaf",
            mcts_config.simulations, mcts_config.parallel_trees, mcts_config.leaf_rollouts
        );
    }

    // Print Alpha-Beta config if using Alpha-Beta
    if bot1_type == BotType::AlphaBeta || bot2_type == BotType::AlphaBeta {
        let mode = if args.ab_legacy { "legacy" } else { "optimized (TT+LMR+Aspiration)" };
        println!("Alpha-Beta: depth {}, mode: {}", alphabeta_config.max_depth, mode);
    }

    // Parse game mode (essence-war is default, accept legacy names for compatibility)
    let game_mode = match args.mode.to_lowercase().as_str() {
        "attrition" => GameMode::Attrition,
        "essence-war" | "essencewar" | "essence-duel" | "essenceduel" | "duel" => GameMode::EssenceWar,
        other => {
            eprintln!("Unknown game mode: '{}'. Use 'attrition' or 'essence-war'.", other);
            process::exit(1);
        }
    };

    if game_mode == GameMode::EssenceWar {
        println!("Mode: Essence War (first to 50 essence extracted wins)");
    }

    // Build match configuration
    let config = MatchConfig::new(bot1_type, bot2_type, deck1, deck2, args.games, seed)
        .with_weights1(weights1)
        .with_weights2(weights2)
        .with_mcts_config(mcts_config)
        .with_alphabeta_config(alphabeta_config)
        .with_progress(args.progress)
        .with_game_mode(game_mode);

    // Run the match
    let stats = if parallel {
        match run_match_parallel(&game_data.card_db, &config) {
            Ok(s) => s,
            Err(e) => {
                eprintln!("Error: {}", e);
                process::exit(1);
            }
        }
    } else {
        let seq_config = SequentialConfig::new()
            .with_invariants(args.invariants)
            .with_combat_tracing(trace_combat)
            .with_effect_tracing(trace_effects)
            .with_replay_dir(args.export_replays.clone());
        match run_match_sequential(&game_data.card_db, &config, &seq_config, &mut logger) {
            Ok(s) => s,
            Err(e) => {
                eprintln!("Error: {}", e);
                process::exit(1);
            }
        }
    };

    // Print results
    println!("{}", stats.summary());

    // Update ELO ratings if both decks were specified
    if let (Some(ref deck1_id), Some(ref deck2_id)) = (&args.deck1, &args.deck2) {
        let elo_path = PathBuf::from("data/ratings/deck_elo.json");
        let mut elo = EloTracker::load_or_create(&elo_path);

        // Update ratings based on match results
        let (delta1, delta2) = elo.update_match(deck1_id, deck2_id, &stats.overall);

        // Save updated ratings
        if let Err(e) = elo.save() {
            eprintln!("Warning: Failed to save ELO ratings: {}", e);
        } else if delta1 != 0 || delta2 != 0 {
            // Print brief summary if there were changes
            println!();
            println!("ELO: {}", elo.format_summary(deck1_id, deck2_id, delta1, delta2));
        }
    }
}
