//! Unit tests for Alpha-Beta bot.

use cardgame::bots::{AlphaBetaBot, AlphaBetaConfig, Bot};
use cardgame::cards::CardDatabase;
use cardgame::engine::GameEngine;
use cardgame::state::GameMode;
use cardgame::types::{CardId, PlayerId};

const DEFAULT_COMMANDER: CardId = CardId(5000);

fn load_test_db() -> CardDatabase {
    CardDatabase::load_with_commanders(
        cardgame::data_dir().join("cards/core_set"),
        cardgame::data_dir().join("commanders"),
    )
    .expect("Failed to load cards with commanders")
}

fn test_deck() -> Vec<CardId> {
    let valid_ids = [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 15, 32, 33, 40];
    (0..20)
        .map(|i| CardId(valid_ids[i % valid_ids.len()] as u16))
        .collect()
}

fn setup_game<'a>(card_db: &'a CardDatabase, seed: u64) -> GameEngine<'a> {
    let mut engine = GameEngine::new(card_db);
    engine
        .start_game_raw(
            test_deck(),
            test_deck(),
            DEFAULT_COMMANDER,
            DEFAULT_COMMANDER,
            seed,
            GameMode::default(),
        )
        .unwrap();
    engine
}

// --- Creation & Config Tests ---

#[test]
fn test_alphabeta_creation_default() {
    let card_db = load_test_db();
    let bot = AlphaBetaBot::new(&card_db, 42);
    assert_eq!(bot.name(), "AlphaBetaBot");
}

#[test]
fn test_alphabeta_creation_with_config() {
    let card_db = load_test_db();
    let config = AlphaBetaConfig::with_depth(4);
    let bot = AlphaBetaBot::with_config(&card_db, config, 42);
    assert_eq!(bot.name(), "AlphaBetaBot(d4)");
}

#[test]
fn test_alphabeta_fast_config() {
    let config = AlphaBetaConfig::fast();
    assert_eq!(config.max_depth, 4);
    assert!(!config.iterative_deepening);
    assert!(!config.aspiration_windows);
    assert!(!config.use_lmr);
}

#[test]
fn test_alphabeta_strong_config() {
    let config = AlphaBetaConfig::strong();
    assert_eq!(config.max_depth, 8);
    assert!(config.iterative_deepening);
    assert!(config.quiescence);
    assert!(config.use_lmr);
}

#[test]
fn test_alphabeta_legacy_config() {
    let config = AlphaBetaConfig::legacy(6);
    assert_eq!(config.max_depth, 6);
    assert!(!config.use_tt);
    assert!(!config.aspiration_windows);
    assert!(!config.use_lmr);
}

#[test]
fn test_alphabeta_with_name() {
    let card_db = load_test_db();
    let bot = AlphaBetaBot::new(&card_db, 42).with_name("TestBot");
    assert_eq!(bot.name(), "TestBot");
}

// --- Action Selection Tests ---

#[test]
fn test_alphabeta_selects_legal_action() {
    let card_db = load_test_db();
    let mut bot = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let engine = setup_game(&card_db, 12345);

    let action = bot.select_action_with_engine(&engine);

    // Verify action is legal
    let legal_actions = engine.get_legal_actions();
    assert!(
        legal_actions.contains(&action),
        "AlphaBeta selected illegal action: {:?}",
        action
    );
}

#[test]
fn test_alphabeta_deterministic_same_seed() {
    let card_db = load_test_db();

    let engine = setup_game(&card_db, 12345);

    let mut bot1 = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let action1 = bot1.select_action_with_engine(&engine);

    let mut bot2 = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let action2 = bot2.select_action_with_engine(&engine);

    assert_eq!(
        action1, action2,
        "Same seed should produce same action"
    );
}

#[test]
fn test_alphabeta_search_ranked() {
    let card_db = load_test_db();
    let mut bot = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let engine = setup_game(&card_db, 12345);

    let ranked = bot.search_ranked(&engine);

    // Should return at least one action
    assert!(!ranked.is_empty(), "search_ranked should return actions");

    // All actions should be legal
    let legal_actions = engine.get_legal_actions();
    for (action, _score) in &ranked {
        assert!(
            legal_actions.contains(action),
            "search_ranked returned illegal action: {:?}",
            action
        );
    }

    // Should be sorted best-first (descending score)
    for window in ranked.windows(2) {
        assert!(
            window[0].1 >= window[1].1,
            "search_ranked should be sorted descending: {} >= {}",
            window[0].1,
            window[1].1
        );
    }
}

#[test]
fn test_alphabeta_stats_populated() {
    let card_db = load_test_db();
    let mut bot = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let engine = setup_game(&card_db, 12345);

    bot.select_action_with_engine(&engine);

    let stats = bot.last_stats();
    assert!(stats.nodes_visited > 0, "Should visit nodes");
    assert!(stats.best_score.is_finite(), "Best score should be finite");
}

// --- Bot Trait Tests ---

// Note: AlphaBeta's select_action() panics by design — it requires engine access.
// Use select_action_with_engine() instead. This is tested throughout other tests.

#[test]
fn test_alphabeta_reset() {
    let card_db = load_test_db();
    let mut bot = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    let engine = setup_game(&card_db, 12345);

    // Run a search to populate internal state
    bot.select_action_with_engine(&engine);
    assert!(bot.last_stats().nodes_visited > 0);

    // Reset should clear state
    bot.reset();
}

#[test]
fn test_alphabeta_clone_box() {
    let card_db = load_test_db();
    let bot = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 42);
    // clone_box returns a Box<dyn Bot> — verify it doesn't panic
    let cloned = bot.clone_box();
    // The cloned bot should have a name (may differ from original due to impl details)
    assert!(!cloned.name().is_empty());
}

// --- Game Play Tests ---

#[test]
fn test_alphabeta_plays_full_game() {
    let card_db = load_test_db();
    let mut bot1 = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 1);
    let mut bot2 = AlphaBetaBot::with_config(&card_db, AlphaBetaConfig::fast(), 2);

    let mut engine = setup_game(&card_db, 99);

    let mut turns = 0;
    while !engine.is_game_over() && turns < 500 {
        let action = if engine.current_player() == PlayerId::PLAYER_ONE {
            bot1.select_action_with_engine(&engine)
        } else {
            bot2.select_action_with_engine(&engine)
        };
        engine.apply_action(action).unwrap();
        turns += 1;
    }

    assert!(engine.is_game_over(), "Game should end within 500 turns");
    assert!(turns > 1, "Game should last more than 1 turn");
}

// --- Depth Configuration Tests ---

#[test]
fn test_deeper_search_visits_more_nodes() {
    let card_db = load_test_db();
    let engine = setup_game(&card_db, 12345);

    let mut bot_shallow = AlphaBetaBot::with_config(
        &card_db,
        AlphaBetaConfig { max_depth: 2, iterative_deepening: false, use_tt: false, aspiration_windows: false, use_lmr: false, ..AlphaBetaConfig::fast() },
        42,
    );
    bot_shallow.select_action_with_engine(&engine);
    let shallow_nodes = bot_shallow.last_stats().nodes_visited;

    let mut bot_deep = AlphaBetaBot::with_config(
        &card_db,
        AlphaBetaConfig { max_depth: 4, iterative_deepening: false, use_tt: false, aspiration_windows: false, use_lmr: false, ..AlphaBetaConfig::fast() },
        42,
    );
    bot_deep.select_action_with_engine(&engine);
    let deep_nodes = bot_deep.last_stats().nodes_visited;

    assert!(
        deep_nodes > shallow_nodes,
        "Deeper search should visit more nodes: {} > {}",
        deep_nodes,
        shallow_nodes
    );
}

// --- TT Integration Tests ---

#[test]
fn test_alphabeta_tt_produces_hits() {
    let card_db = load_test_db();
    // Use iterative deepening which naturally revisits positions
    let config = AlphaBetaConfig {
        max_depth: 4,
        use_tt: true,
        tt_size_mb: 4,
        iterative_deepening: true,
        aspiration_windows: false,
        use_lmr: false,
        ..AlphaBetaConfig::default()
    };
    let mut bot = AlphaBetaBot::with_config(&card_db, config, 42);
    let engine = setup_game(&card_db, 12345);

    bot.select_action_with_engine(&engine);

    let stats = bot.last_stats();
    // With iterative deepening, TT should get hits from earlier iterations
    assert!(
        stats.tt_hits > 0 || stats.tt_cutoffs > 0,
        "Iterative deepening with TT should produce hits or cutoffs (hits={}, cutoffs={})",
        stats.tt_hits,
        stats.tt_cutoffs
    );
}

#[test]
fn test_alphabeta_without_tt_no_tt_stats() {
    let card_db = load_test_db();
    let config = AlphaBetaConfig {
        max_depth: 3,
        use_tt: false,
        iterative_deepening: false,
        aspiration_windows: false,
        use_lmr: false,
        ..AlphaBetaConfig::fast()
    };
    let mut bot = AlphaBetaBot::with_config(&card_db, config, 42);
    let engine = setup_game(&card_db, 12345);

    bot.select_action_with_engine(&engine);

    let stats = bot.last_stats();
    assert_eq!(stats.tt_hits, 0);
    assert_eq!(stats.tt_cutoffs, 0);
}
