//! Tests migrated from inline #[cfg(test)] modules in src/execution/*.rs

use std::time::Duration;

use cardgame::execution::{
    BatchConfig, BatchResult, GameData, GameLoopConfig, GameLoopResult, GameOutcome, GameSeeds,
    MatchupBuilder, ProgressReporter, ProgressStyle,
};
use cardgame::types::PlayerId;

// ── helpers ──────────────────────────────────────────────────────────────────

fn load_test_data() -> GameData {
    GameData::load_default(true).expect("Failed to load test data")
}

// ── matchup.rs ───────────────────────────────────────────────────────────────

#[test]
fn test_matchup_creation() {
    let data = load_test_data();
    let decks: Vec<_> = data.deck_registry.decks().collect();
    assert!(decks.len() >= 2, "Need at least 2 decks for test");

    let matchup =
        cardgame::execution::UnifiedMatchup::new(decks[0].clone(), decks[1].clone());

    assert!(!matchup.id.is_empty());
    assert!(matchup.id.contains("_vs_"));
    assert!(!matchup.is_mirror());
}

#[test]
fn test_commander_access() {
    let data = load_test_data();
    let decks: Vec<_> = data.deck_registry.decks().collect();

    let matchup =
        cardgame::execution::UnifiedMatchup::new(decks[0].clone(), decks[1].clone());

    assert!(matchup.commander1().0 > 0);
    assert!(matchup.commander2().0 > 0);
    assert_eq!(matchup.commander1().0, decks[0].commander);
    assert_eq!(matchup.commander2().0, decks[1].commander);
}

#[test]
fn test_mirror_detection() {
    let data = load_test_data();
    let decks: Vec<_> = data.deck_registry.decks().collect();

    let mirror =
        cardgame::execution::UnifiedMatchup::new(decks[0].clone(), decks[0].clone());
    assert!(mirror.is_mirror());

    let non_mirror =
        cardgame::execution::UnifiedMatchup::new(decks[0].clone(), decks[1].clone());
    assert!(!non_mirror.is_mirror());
}

#[test]
fn test_reversed_matchup() {
    let data = load_test_data();
    let decks: Vec<_> = data.deck_registry.decks().collect();

    let matchup =
        cardgame::execution::UnifiedMatchup::new(decks[0].clone(), decks[1].clone());
    let reversed = matchup.reversed();

    assert_eq!(matchup.deck1.id, reversed.deck2.id);
    assert_eq!(matchup.deck2.id, reversed.deck1.id);
    assert_eq!(matchup.commander1(), reversed.commander2());
    assert_eq!(matchup.commander2(), reversed.commander1());
}

#[test]
fn test_faction_access() {
    use cardgame::decks::Faction;

    let data = load_test_data();
    let argentum_decks = data.deck_registry.decks_for_faction(Faction::Argentum);
    let symbiote_decks = data.deck_registry.decks_for_faction(Faction::Symbiote);

    assert!(!argentum_decks.is_empty(), "No Argentum decks found");
    assert!(!symbiote_decks.is_empty(), "No Symbiote decks found");

    let matchup = cardgame::execution::UnifiedMatchup::new(
        argentum_decks[0].clone(),
        symbiote_decks[0].clone(),
    );

    assert_eq!(matchup.faction1(), Some(Faction::Argentum));
    assert_eq!(matchup.faction2(), Some(Faction::Symbiote));
    assert!(!matchup.is_same_faction());
}

// ── parallel.rs ──────────────────────────────────────────────────────────────

#[test]
fn test_batch_config_builder() {
    let config = BatchConfig::new(100, 42);
    assert_eq!(config.games, 100);
    assert_eq!(config.base_seed, 42);
    assert!(!config.show_progress);

    let config = config
        .with_progress(ProgressStyle::Simple)
        .with_prefix("  ");
    assert!(config.show_progress);
    assert_eq!(config.progress_style, ProgressStyle::Simple);
    assert_eq!(config.progress_prefix, "  ");
}

#[test]
fn test_game_outcome() {
    let outcome = GameOutcome::new(Some(PlayerId::PLAYER_ONE), 25, Duration::from_secs(1));
    assert_eq!(outcome.winner, Some(PlayerId::PLAYER_ONE));
    assert_eq!(outcome.turns, 25);
    assert_eq!(outcome.duration, Duration::from_secs(1));
}

#[test]
fn test_batch_result_stats() {
    let outcomes = vec![
        GameOutcome::new(
            Some(PlayerId::PLAYER_ONE),
            20,
            Duration::from_millis(100),
        ),
        GameOutcome::new(
            Some(PlayerId::PLAYER_TWO),
            25,
            Duration::from_millis(150),
        ),
        GameOutcome::new(None, 30, Duration::from_millis(200)),
        GameOutcome::new(
            Some(PlayerId::PLAYER_ONE),
            22,
            Duration::from_millis(120),
        ),
    ];

    let result = BatchResult {
        outcomes,
        wall_clock_time: Duration::from_millis(200),
    };

    assert_eq!(result.total_games(), 4);
    assert_eq!(result.p1_wins(), 2);
    assert_eq!(result.p2_wins(), 1);
    assert_eq!(result.draws(), 1);
    assert_eq!(result.total_turns(), 97);
    assert!((result.avg_turns() - 24.25).abs() < 0.01);
    assert_eq!(result.cpu_time(), Duration::from_millis(570));
}

#[test]
fn test_run_batch_parallel() {
    use cardgame::execution::run_batch_parallel;

    let config = BatchConfig::new(10, 42);
    let result = run_batch_parallel(&config, |seeds| {
        let winner = if seeds.game % 2 == 0 {
            Some(PlayerId::PLAYER_ONE)
        } else {
            Some(PlayerId::PLAYER_TWO)
        };
        GameOutcome::new(winner, 20, Duration::from_millis(10))
    });

    assert_eq!(result.total_games(), 10);
    assert_eq!(result.p1_wins(), 5);
    assert_eq!(result.p2_wins(), 5);
}

// ── game_loop.rs ─────────────────────────────────────────────────────────────

#[test]
fn test_config_defaults() {
    let config = GameLoopConfig::default();
    assert_eq!(config.max_actions, cardgame::execution::MAX_ACTIONS_PER_GAME);
    assert_eq!(config.seed, 0);
}

#[test]
fn test_config_with_seed() {
    let config = GameLoopConfig::new(12345);
    assert_eq!(config.seed, 12345);
    assert_eq!(config.max_actions, cardgame::execution::MAX_ACTIONS_PER_GAME);
}

#[test]
fn test_config_max_actions_clamp() {
    let config = GameLoopConfig::default().with_max_actions(0);
    assert_eq!(config.max_actions, 1);

    let config = GameLoopConfig::default().with_max_actions(100_000);
    assert_eq!(config.max_actions, 10_000);

    let config = GameLoopConfig::default().with_max_actions(500);
    assert_eq!(config.max_actions, 500);
}

#[test]
fn test_result_accessors() {
    let completed = GameLoopResult::Completed {
        winner: Some(PlayerId::PLAYER_ONE),
        turns: 15,
        actions: 42,
    };
    assert!(completed.is_completed());
    assert_eq!(completed.winner(), Some(PlayerId::PLAYER_ONE));
    assert_eq!(completed.actions(), 42);

    let exceeded = GameLoopResult::ActionLimitExceeded {
        actions: 1000,
        seed: 999,
    };
    assert!(!exceeded.is_completed());
    assert_eq!(exceeded.winner(), None);
    assert_eq!(exceeded.actions(), 1000);
}

// ── seeds.rs ─────────────────────────────────────────────────────────────────

#[test]
fn test_game_seeds_for_game() {
    let seeds = GameSeeds::for_game(42, 0);
    assert_eq!(seeds.game, 42);
    assert_eq!(seeds.bot1, 42);

    let seeds = GameSeeds::for_game(42, 5);
    assert_eq!(seeds.game, 47);
    assert_eq!(seeds.bot1, 47);
}

#[test]
fn test_game_seeds_determinism() {
    let seeds1 = GameSeeds::for_game(12345, 100);
    let seeds2 = GameSeeds::for_game(12345, 100);
    assert_eq!(seeds1, seeds2);

    let seeds1 = GameSeeds::for_matchup(12345, 2, 50, true);
    let seeds2 = GameSeeds::for_matchup(12345, 2, 50, true);
    assert_eq!(seeds1, seeds2);
}

#[test]
fn test_from_game_seed() {
    let game_seed = 999;
    let seeds = GameSeeds::from_game_seed(game_seed);
    assert_eq!(seeds.game, game_seed);
    assert_eq!(seeds.bot1, game_seed);
}

#[test]
fn test_seed_space_separation() {
    let base = 42u64;
    let matchup0_max = GameSeeds::for_matchup(base, 0, 999, true);
    let matchup1_min = GameSeeds::for_matchup(base, 1, 0, false);

    assert!(
        matchup0_max.game < matchup1_min.game,
        "Matchup seed spaces should not overlap"
    );
}

// ── progress.rs ──────────────────────────────────────────────────────────────

#[test]
fn test_progress_counter() {
    let reporter = ProgressReporter::new(100);
    assert_eq!(reporter.completed(), 0);

    reporter.increment();
    assert_eq!(reporter.completed(), 1);

    reporter.increment();
    reporter.increment();
    assert_eq!(reporter.completed(), 3);
}

#[test]
fn test_progress_counter_arc() {
    use std::sync::atomic::Ordering;

    let reporter = ProgressReporter::new(100);
    let counter = reporter.counter();

    counter.fetch_add(5, Ordering::Relaxed);
    assert_eq!(reporter.completed(), 5);
}

#[test]
fn test_maybe_progress_disabled() {
    use cardgame::execution::maybe_progress;

    let reporter = maybe_progress(false, 100, ProgressStyle::Simple);
    assert!(reporter.is_none());
}

#[test]
fn test_maybe_progress_enabled() {
    use cardgame::execution::maybe_progress;

    let reporter = maybe_progress(true, 100, ProgressStyle::Simple);
    assert!(reporter.is_some());
    reporter.unwrap().finish_silent();
}

#[test]
fn test_progress_with_prefix() {
    // Builder method should not panic
    let _reporter = ProgressReporter::new(100).with_prefix("  ");
}

#[test]
fn test_progress_styles() {
    // Builder methods should not panic
    let _simple = ProgressReporter::new(100).with_style(ProgressStyle::Simple);
    let _rich = ProgressReporter::new(100).with_style(ProgressStyle::Rich);
}

// ── data_loader.rs ───────────────────────────────────────────────────────────

#[test]
fn test_load_default() {
    let result = GameData::load_default(true);
    assert!(
        result.is_ok(),
        "Failed to load default game data: {:?}",
        result.err()
    );

    let data = result.unwrap();
    assert!(data.card_count() > 0, "No cards loaded");
    assert!(data.deck_count() > 0, "No decks loaded");
    assert!(data.commander_count() > 0, "No commanders loaded");
}

#[test]
fn test_expected_counts() {
    let data = GameData::load_default(true).unwrap();
    assert_eq!(data.deck_count(), 12, "Expected 12 decks");
    assert_eq!(data.commander_count(), 12, "Expected 12 commanders");
    assert!(data.card_count() >= 300, "Expected at least 300 cards");
}

// ── matchup_builder.rs ───────────────────────────────────────────────────────

#[test]
fn test_build_all_matchups() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);

    let matchups = builder.build_all_matchups();
    assert_eq!(matchups.len(), 66, "Expected 66 unique matchups");

    for matchup in &matchups {
        assert!(!matchup.is_mirror(), "Found mirror match: {}", matchup.id);
    }

    let mut ids: Vec<_> = matchups.iter().map(|m| &m.id).collect();
    ids.sort();
    ids.dedup();
    assert_eq!(ids.len(), matchups.len(), "Found duplicate matchups");
}

#[test]
fn test_build_inter_faction_matchups() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);

    let matchups = builder.build_inter_faction_matchups();
    assert_eq!(matchups.len(), 48, "Expected 48 inter-faction matchups");

    for matchup in &matchups {
        assert!(
            !matchup.is_same_faction(),
            "Found same-faction match in inter-faction: {}",
            matchup.id
        );
    }
}

#[test]
fn test_build_intra_faction_matchups() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);

    let matchups = builder.build_intra_faction_matchups();
    assert_eq!(matchups.len(), 18, "Expected 18 intra-faction matchups");

    for matchup in &matchups {
        assert!(
            matchup.is_same_faction(),
            "Found cross-faction match in intra-faction: {}",
            matchup.id
        );
    }
}

#[test]
fn test_build_faction_matchups() {
    use cardgame::decks::Faction;

    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);

    let cross = builder.build_faction_matchups(Faction::Argentum, Faction::Symbiote);
    assert_eq!(cross.len(), 16, "Expected 16 Argentum vs Symbiote matchups");

    let same = builder.build_faction_matchups(Faction::Argentum, Faction::Argentum);
    assert_eq!(same.len(), 6, "Expected 6 Argentum vs Argentum matchups");

    for matchup in &same {
        assert!(
            !matchup.is_mirror(),
            "Found mirror in same-faction matchups"
        );
    }
}

#[test]
fn test_filter_by_deck_id() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);
    let all = builder.build_all_matchups();

    let filtered = MatchupBuilder::filter_matchups(all, "broodmother");
    assert!(!filtered.is_empty(), "Expected to find broodmother matchups");
    for matchup in &filtered {
        let has_brood =
            matchup.deck1.id.contains("broodmother") || matchup.deck2.id.contains("broodmother");
        assert!(has_brood, "Filtered matchup should contain broodmother");
    }
}

#[test]
fn test_filter_by_faction_pair() {
    use cardgame::decks::Faction;

    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);
    let all = builder.build_all_matchups();

    let filtered = MatchupBuilder::filter_matchups(all, "argentum-symbiote");
    assert_eq!(filtered.len(), 16, "Expected 16 Argentum-Symbiote matchups");

    for matchup in &filtered {
        let factions = (matchup.faction1(), matchup.faction2());
        let valid = factions == (Some(Faction::Argentum), Some(Faction::Symbiote))
            || factions == (Some(Faction::Symbiote), Some(Faction::Argentum));
        assert!(valid, "Filtered matchup should be Argentum vs Symbiote");
    }
}

#[test]
fn test_filter_by_single_faction() {
    use cardgame::decks::Faction;

    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);
    let all = builder.build_all_matchups();

    let filtered = MatchupBuilder::filter_matchups(all, "argentum");
    assert!(!filtered.is_empty());
    for matchup in &filtered {
        let has_argentum = matchup.faction1() == Some(Faction::Argentum)
            || matchup.faction2() == Some(Faction::Argentum);
        assert!(has_argentum, "Filtered matchup should involve Argentum");
    }
}

#[test]
fn test_matchup_summary() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);

    let summary = builder.matchup_summary();
    assert_eq!(summary.total_decks, 12);
    assert_eq!(summary.total_matchups, 66);
    assert_eq!(summary.inter_faction_matchups, 48);
    assert_eq!(summary.intra_faction_matchups, 18);
}

#[test]
fn test_case_insensitive_filter() {
    let data = load_test_data();
    let builder = MatchupBuilder::new(&data.deck_registry);
    let all = builder.build_all_matchups();

    let filtered_lower = MatchupBuilder::filter_matchups(all.clone(), "argentum");
    let filtered_upper = MatchupBuilder::filter_matchups(all.clone(), "ARGENTUM");
    let filtered_mixed = MatchupBuilder::filter_matchups(all, "ArGenTuM");

    assert_eq!(filtered_lower.len(), filtered_upper.len());
    assert_eq!(filtered_lower.len(), filtered_mixed.len());
}

// ── metrics.rs ───────────────────────────────────────────────────────────────

#[test]
fn test_game_metrics_from_outcome() {
    use cardgame::execution::GameMetricsData;

    let metrics = GameMetricsData::from_outcome(
        Some(PlayerId::PLAYER_ONE),
        25,
        Duration::from_millis(100),
        42,
    );

    assert!(metrics.p1_won());
    assert!(!metrics.p2_won());
    assert!(!metrics.is_draw());
    assert_eq!(metrics.turns, 25);
    assert_eq!(metrics.seed, 42);
}

#[test]
fn test_trade_ratio() {
    use cardgame::execution::GameMetricsData;

    let mut metrics = GameMetricsData {
        p1_creatures_killed: 4,
        p1_creatures_lost: 2,
        ..Default::default()
    };

    assert!((metrics.p1_trade_ratio().unwrap() - 2.0).abs() < 0.01);

    metrics.p1_creatures_lost = 0;
    assert!(metrics.p1_trade_ratio().unwrap().is_infinite());

    metrics.p1_creatures_killed = 0;
    assert!(metrics.p1_trade_ratio().is_none());
}

#[test]
fn test_aggregation() {
    use cardgame::execution::{AggregatedMetrics, GameMetricsData};

    let games = vec![
        GameMetricsData {
            winner: Some(PlayerId::PLAYER_ONE),
            turns: 20,
            first_blood_player: Some(PlayerId::PLAYER_ONE),
            p1_essence_spent: 30,
            p1_board_impact: 60,
            ..Default::default()
        },
        GameMetricsData {
            winner: Some(PlayerId::PLAYER_TWO),
            turns: 25,
            first_blood_player: Some(PlayerId::PLAYER_TWO),
            p1_essence_spent: 25,
            p1_board_impact: 50,
            ..Default::default()
        },
        GameMetricsData {
            winner: None,
            turns: 30,
            first_blood_player: Some(PlayerId::PLAYER_ONE),
            p1_essence_spent: 35,
            p1_board_impact: 70,
            ..Default::default()
        },
    ];

    let agg = AggregatedMetrics::from_games(&games);

    assert_eq!(agg.total_games, 3);
    assert_eq!(agg.p1_wins, 1);
    assert_eq!(agg.p2_wins, 1);
    assert_eq!(agg.draws, 1);
    assert_eq!(agg.total_turns, 75);
    assert!((agg.avg_turns() - 25.0).abs() < 0.01);
    assert_eq!(agg.p1_first_blood_count, 2);
    assert_eq!(agg.p2_first_blood_count, 1);
    assert!((agg.p1_first_blood_rate() - 0.666).abs() < 0.01);
}

#[test]
fn test_merge_aggregations() {
    use cardgame::execution::{AggregatedMetrics, GameMetricsData};

    let games1 = vec![GameMetricsData {
        winner: Some(PlayerId::PLAYER_ONE),
        turns: 20,
        ..Default::default()
    }];

    let games2 = vec![GameMetricsData {
        winner: Some(PlayerId::PLAYER_TWO),
        turns: 25,
        ..Default::default()
    }];

    let mut agg1 = AggregatedMetrics::from_games(&games1);
    let agg2 = AggregatedMetrics::from_games(&games2);

    agg1.merge(&agg2);

    assert_eq!(agg1.total_games, 2);
    assert_eq!(agg1.p1_wins, 1);
    assert_eq!(agg1.p2_wins, 1);
    assert_eq!(agg1.total_turns, 45);
}

// ── cli.rs ───────────────────────────────────────────────────────────────────

#[test]
fn test_parse_bot_type_valid() {
    use cardgame::execution::parse_bot_type;

    assert!(parse_bot_type("random").is_ok());
    assert!(parse_bot_type("greedy").is_ok());
    assert!(parse_bot_type("mcts").is_ok());
    assert!(parse_bot_type("alphabeta").is_ok());
    assert!(parse_bot_type("alpha-beta").is_ok());
    assert!(parse_bot_type("ab").is_ok());
    assert!(parse_bot_type("agent-argentum").is_ok());
    assert!(parse_bot_type("agent-generalist").is_ok());
}

#[test]
fn test_parse_bot_type_invalid() {
    use cardgame::execution::parse_bot_type;

    assert!(parse_bot_type("invalid").is_err());
    assert!(parse_bot_type("").is_err());
}

#[test]
fn test_resolve_seed_with_value() {
    use cardgame::execution::resolve_seed;
    assert_eq!(resolve_seed(Some(12345)), 12345);
}

#[test]
fn test_resolve_seed_without_value() {
    use cardgame::execution::resolve_seed;
    let seed = resolve_seed(None);
    assert!(seed > 1_000_000_000);
}

#[test]
fn test_mcts_args_default() {
    use cardgame::execution::MctsArgs;
    let args = MctsArgs::default();
    assert_eq!(args.mcts_sims, 500);
    assert_eq!(args.mcts_trees, 1);
    assert_eq!(args.mcts_rollouts, 1);
}

#[test]
fn test_mcts_args_to_config() {
    use cardgame::execution::MctsArgs;
    let args = MctsArgs {
        mcts_sims: 1000,
        mcts_trees: 4,
        mcts_rollouts: 2,
    };
    let config = args.to_config();
    assert_eq!(config.simulations, 1000);
    assert_eq!(config.parallel_trees, 4);
    assert_eq!(config.leaf_rollouts, 2);
}

#[test]
fn test_alphabeta_args_default() {
    use cardgame::execution::AlphaBetaArgs;
    let args = AlphaBetaArgs::default();
    assert_eq!(args.ab_depth, 6);
}

#[test]
fn test_data_paths_default() {
    use cardgame::execution::DataPaths;
    use std::path::PathBuf;

    let paths = DataPaths::default();
    assert_eq!(paths.cards, PathBuf::from("data/cards/core_set"));
    assert_eq!(paths.commanders, PathBuf::from("data/commanders"));
    assert_eq!(paths.decks, PathBuf::from("data/decks"));
    assert_eq!(paths.weights, PathBuf::from("data/weights"));
}

#[test]
fn test_data_paths_builder() {
    use cardgame::execution::DataPaths;
    use std::path::PathBuf;

    let paths = DataPaths::new()
        .with_cards("custom/cards")
        .with_decks("custom/decks");
    assert_eq!(paths.cards, PathBuf::from("custom/cards"));
    assert_eq!(paths.decks, PathBuf::from("custom/decks"));
    assert_eq!(paths.commanders, PathBuf::from("data/commanders"));
}

#[test]
fn test_execution_settings_builder() {
    use cardgame::execution::ExecutionSettings;
    let settings = ExecutionSettings::default()
        .with_seed(999)
        .with_threads(4)
        .with_progress(true);
    assert_eq!(settings.seed, 999);
    assert_eq!(settings.threads, 4);
    assert!(settings.show_progress);
}
