//! Regression tests with golden file comparison.
//!
//! These tests capture known-good game outcomes and verify they remain consistent
//! across code changes. This catches unintended behavioral changes in the engine.
//!
//! Golden data format:
//! - Seed: deterministic random seed
//! - Expected winner, turn count, final life totals
//! - Key action sequence checkpoints
//!
//! Note: Uses GameEngine directly instead of GameRunner due to a known bug in
//! GameRunner that causes games to hit the 1000 action limit.

mod common;

use cardgame::actions::Action;
use cardgame::bots::{Bot, GreedyBot};
use cardgame::cards::CardDatabase;
use cardgame::decks::DeckRegistry;
use cardgame::engine::GameEngine;
use cardgame::state::GameMode;
use cardgame::types::{CardId, PlayerId};
use common::load_real_card_db;

/// Default commander for tests (The High Artificer).
const DEFAULT_COMMANDER: cardgame::types::CardId = cardgame::types::CardId(5000);

/// Golden test case - a deterministic game with expected outcomes
struct GoldenTestCase {
    name: &'static str,
    seed: u64,
    deck1_id: &'static str,
    deck2_id: &'static str,
    expected_winner: Option<u8>, // 0 = P1, 1 = P2, None = draw/timeout
    expected_turns: u32,
    expected_p1_final_life: i16,
    expected_p2_final_life: i16,
    expected_action_count: usize,
}

/// Collection of golden test cases capturing known-good game behavior.
/// These were generated by running deterministic Greedy vs Greedy games.
///
/// Note: Some games hit the 1000-action safety limit, indicating potential
/// issues with certain game states (this is expected behavior for the safety mechanism).
/// Note: These values were regenerated after the Core Set card ID migration (2026-01-14).
/// Cards now use faction-specific ID ranges (Argentum 1000+, Symbiote 2000+, etc.).
/// Using GameEngine directly instead of GameRunner due to a known GameRunner bug.
/// Note: Golden values updated for v0.8.0 (added CommanderInsight action type)
const GOLDEN_TESTS: &[GoldenTestCase] = &[
    GoldenTestCase {
        name: "greedy_mirror_seed_100",
        seed: 100,
        deck1_id: "broodmother_pack",
        deck2_id: "broodmother_pack",
        expected_winner: Some(1), // P2 wins
        expected_turns: 16,
        expected_p1_final_life: -1,
        expected_p2_final_life: 14,
        expected_action_count: 66,
    },
    GoldenTestCase {
        name: "greedy_mirror_seed_200",
        seed: 200,
        deck1_id: "broodmother_pack",
        deck2_id: "broodmother_pack",
        expected_winner: Some(0), // P1 wins
        expected_turns: 13,
        expected_p1_final_life: 20,
        expected_p2_final_life: 0,
        expected_action_count: 50,
    },
    GoldenTestCase {
        name: "greedy_mirror_seed_600",
        seed: 600,
        deck1_id: "broodmother_pack",
        deck2_id: "broodmother_pack",
        expected_winner: Some(1), // P2 wins
        expected_turns: 22,
        expected_p1_final_life: -1,
        expected_p2_final_life: 11,
        expected_action_count: 99,
    },
];

fn load_test_resources() -> (CardDatabase, DeckRegistry) {
    let card_db = load_real_card_db();
    let deck_registry = DeckRegistry::load_from_directory(cardgame::data_dir().join("decks"))
        .expect("Failed to load decks");
    (card_db, deck_registry)
}

/// Result struct for tracking game outcome (similar to GameResult but simpler)
struct TestGameResult {
    winner: Option<PlayerId>,
    turns: u32,
    action_count: usize,
    actions: Vec<Action>,
}

fn run_golden_test(test: &GoldenTestCase, card_db: &CardDatabase, deck_registry: &DeckRegistry) {
    let deck1 = deck_registry.get(test.deck1_id)
        .unwrap_or_else(|| panic!("Deck {} not found", test.deck1_id));
    let deck2 = deck_registry.get(test.deck2_id)
        .unwrap_or_else(|| panic!("Deck {} not found", test.deck2_id));

    // Convert u16 to CardId
    let deck1_cards: Vec<CardId> = deck1.cards.iter().map(|&id| CardId(id)).collect();
    let deck2_cards: Vec<CardId> = deck2.cards.iter().map(|&id| CardId(id)).collect();

    // Run game with deterministic seed using GameEngine directly
    // (GameRunner has a bug that causes games to hit 1000 action limit)
    let mut bot1 = GreedyBot::new(card_db, test.seed);
    let mut bot2 = GreedyBot::new(card_db, test.seed + 1);

    let mut engine = GameEngine::new(card_db);
    engine.start_game_raw(deck1_cards, deck2_cards, DEFAULT_COMMANDER, DEFAULT_COMMANDER, test.seed, GameMode::default()).unwrap();

    let mut actions = Vec::new();
    let max_actions = 1000;

    while !engine.is_game_over() && actions.len() < max_actions {
        let current_player = engine.current_player();
        let action = if current_player == PlayerId::PLAYER_ONE {
            bot1.select_action_with_engine(&engine)
        } else {
            bot2.select_action_with_engine(&engine)
        };

        actions.push(action);
        if engine.apply_action(action).is_err() {
            break;
        }
    }

    let p1_life = engine.state.players[0].life;
    let p2_life = engine.state.players[1].life;

    let result = TestGameResult {
        winner: engine.winner(),
        turns: engine.turn_number() as u32,
        action_count: actions.len(),
        actions,
    };

    // Verify outcomes match golden data
    let actual_winner = match result.winner {
        Some(PlayerId::PLAYER_ONE) => Some(0),
        Some(PlayerId::PLAYER_TWO) => Some(1),
        _ => None,
    };

    println!("Test '{}' results:", test.name);
    println!("  Winner: {:?} (expected {:?})", actual_winner, test.expected_winner);
    println!("  Turns: {} (expected {})", result.turns, test.expected_turns);
    println!("  P1 life: {} (expected {})", p1_life, test.expected_p1_final_life);
    println!("  P2 life: {} (expected {})", p2_life, test.expected_p2_final_life);
    println!("  Actions: {} (expected {})", result.action_count, test.expected_action_count);

    // Core invariants that must match exactly
    assert_eq!(
        actual_winner, test.expected_winner,
        "Test '{}': Winner mismatch - got {:?}, expected {:?}",
        test.name, actual_winner, test.expected_winner
    );

    assert_eq!(
        result.turns, test.expected_turns,
        "Test '{}': Turn count mismatch - got {}, expected {}",
        test.name, result.turns, test.expected_turns
    );

    assert_eq!(
        p1_life, test.expected_p1_final_life,
        "Test '{}': P1 final life mismatch - got {}, expected {}",
        test.name, p1_life, test.expected_p1_final_life
    );

    assert_eq!(
        p2_life, test.expected_p2_final_life,
        "Test '{}': P2 final life mismatch - got {}, expected {}",
        test.name, p2_life, test.expected_p2_final_life
    );

    // Action count should be close (within 10% tolerance for minor changes)
    let action_diff = (result.action_count as i64 - test.expected_action_count as i64).abs();
    let tolerance = (test.expected_action_count as f64 * 0.1).ceil() as i64;
    assert!(
        action_diff <= tolerance,
        "Test '{}': Action count changed significantly - got {}, expected {} (tolerance {})",
        test.name, result.action_count, test.expected_action_count, tolerance
    );
}

#[test]
fn test_golden_greedy_mirror_seed_100() {
    let (card_db, deck_registry) = load_test_resources();
    run_golden_test(&GOLDEN_TESTS[0], &card_db, &deck_registry);
}

#[test]
fn test_golden_greedy_mirror_seed_200() {
    let (card_db, deck_registry) = load_test_resources();
    run_golden_test(&GOLDEN_TESTS[1], &card_db, &deck_registry);
}

#[test]
fn test_golden_greedy_mirror_seed_600() {
    let (card_db, deck_registry) = load_test_resources();
    run_golden_test(&GOLDEN_TESTS[2], &card_db, &deck_registry);
}

/// Generate golden test data by running games and printing expected values.
/// Run with: cargo test generate_golden_data -- --ignored --nocapture
#[test]
#[ignore]
fn generate_golden_data() {
    let (card_db, deck_registry) = load_test_resources();

    let test_seeds = [100u64, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
    let deck_pairs = [
        ("broodmother_pack", "broodmother_pack"),
        ("broodmother_pack", "broodmother_pack"),
        ("architect_fortify", "architect_fortify"),
        ("broodmother_pack", "architect_fortify"),
        ("architect_fortify", "broodmother_pack"),
        ("broodmother_pack", "broodmother_pack"),
        ("architect_fortify", "architect_fortify"),
        ("broodmother_pack", "architect_fortify"),
        ("architect_fortify", "broodmother_pack"),
        ("broodmother_pack", "architect_fortify"),
    ];

    println!("\n=== GOLDEN TEST DATA ===\n");

    for (i, (&seed, (deck1_id, deck2_id))) in test_seeds.iter().zip(deck_pairs.iter()).enumerate() {
        let deck1 = deck_registry.get(deck1_id).unwrap();
        let deck2 = deck_registry.get(deck2_id).unwrap();

        let deck1_cards: Vec<CardId> = deck1.cards.iter().map(|&id| CardId(id)).collect();
        let deck2_cards: Vec<CardId> = deck2.cards.iter().map(|&id| CardId(id)).collect();

        let mut bot1 = GreedyBot::new(&card_db, seed);
        let mut bot2 = GreedyBot::new(&card_db, seed + 1);

        let mut engine = GameEngine::new(&card_db);
        engine.start_game_raw(deck1_cards, deck2_cards, DEFAULT_COMMANDER, DEFAULT_COMMANDER, seed, GameMode::default()).unwrap();

        let mut action_count = 0;
        let max_actions = 1000;

        while !engine.is_game_over() && action_count < max_actions {
            let current_player = engine.current_player();
            let action = if current_player == PlayerId::PLAYER_ONE {
                bot1.select_action_with_engine(&engine)
            } else {
                bot2.select_action_with_engine(&engine)
            };

            if engine.apply_action(action).is_err() {
                break;
            }
            action_count += 1;
        }

        let winner = match engine.winner() {
            Some(PlayerId::PLAYER_ONE) => "Some(0)",
            Some(PlayerId::PLAYER_TWO) => "Some(1)",
            _ => "None",
        };

        println!("GoldenTestCase {{");
        println!("    name: \"test_case_{}\",", i);
        println!("    seed: {},", seed);
        println!("    deck1_id: \"{}\",", deck1_id);
        println!("    deck2_id: \"{}\",", deck2_id);
        println!("    expected_winner: {},", winner);
        println!("    expected_turns: {},", engine.turn_number());
        println!("    expected_p1_final_life: {},", engine.state.players[0].life);
        println!("    expected_p2_final_life: {},", engine.state.players[1].life);
        println!("    expected_action_count: {},", action_count);
        println!("}},\n");
    }

    println!("=== END GOLDEN TEST DATA ===\n");
}

/// Test that re-running the same game produces identical results (determinism check)
#[test]
fn test_regression_determinism() {
    let (card_db, deck_registry) = load_test_resources();

    for test in GOLDEN_TESTS {
        let deck1 = deck_registry.get(test.deck1_id).unwrap();
        let deck2 = deck_registry.get(test.deck2_id).unwrap();

        let deck1_cards: Vec<CardId> = deck1.cards.iter().map(|&id| CardId(id)).collect();
        let deck2_cards: Vec<CardId> = deck2.cards.iter().map(|&id| CardId(id)).collect();

        // Run same game twice using GameEngine directly
        let run_game = |seed: u64| -> TestGameResult {
            let mut bot1 = GreedyBot::new(&card_db, seed);
            let mut bot2 = GreedyBot::new(&card_db, seed + 1);

            let mut engine = GameEngine::new(&card_db);
            engine.start_game_raw(deck1_cards.clone(), deck2_cards.clone(), DEFAULT_COMMANDER, DEFAULT_COMMANDER, seed, GameMode::default()).unwrap();

            let mut actions = Vec::new();
            let max_actions = 1000;

            while !engine.is_game_over() && actions.len() < max_actions {
                let current_player = engine.current_player();
                let action = if current_player == PlayerId::PLAYER_ONE {
                    bot1.select_action_with_engine(&engine)
                } else {
                    bot2.select_action_with_engine(&engine)
                };

                actions.push(action);
                if engine.apply_action(action).is_err() {
                    break;
                }
            }

            TestGameResult {
                winner: engine.winner(),
                turns: engine.turn_number() as u32,
                action_count: actions.len(),
                actions,
            }
        };

        let result1 = run_game(test.seed);
        let result2 = run_game(test.seed);

        assert_eq!(
            result1.winner, result2.winner,
            "Test '{}': Determinism violated - winner differs between runs",
            test.name
        );
        assert_eq!(
            result1.turns, result2.turns,
            "Test '{}': Determinism violated - turn count differs between runs",
            test.name
        );
        assert_eq!(
            result1.action_count, result2.action_count,
            "Test '{}': Determinism violated - action count differs between runs",
            test.name
        );

        // Verify all actions match
        for (i, (a1, a2)) in result1.actions.iter().zip(result2.actions.iter()).enumerate() {
            assert_eq!(
                a1, a2,
                "Test '{}': Action {} differs between runs",
                test.name, i
            );
        }
    }
}

/// Verify that all golden test seeds produce valid game outcomes
#[test]
fn test_regression_all_games_valid() {
    let (card_db, deck_registry) = load_test_resources();

    for test in GOLDEN_TESTS {
        let deck1 = deck_registry.get(test.deck1_id).unwrap();
        let deck2 = deck_registry.get(test.deck2_id).unwrap();

        let deck1_cards: Vec<CardId> = deck1.cards.iter().map(|&id| CardId(id)).collect();
        let deck2_cards: Vec<CardId> = deck2.cards.iter().map(|&id| CardId(id)).collect();

        let mut bot1 = GreedyBot::new(&card_db, test.seed);
        let mut bot2 = GreedyBot::new(&card_db, test.seed + 1);

        let mut engine = GameEngine::new(&card_db);
        engine.start_game_raw(deck1_cards, deck2_cards, DEFAULT_COMMANDER, DEFAULT_COMMANDER, test.seed, GameMode::default()).unwrap();

        let mut actions = Vec::new();
        let max_actions = 1000;

        while !engine.is_game_over() && actions.len() < max_actions {
            let current_player = engine.current_player();
            let action = if current_player == PlayerId::PLAYER_ONE {
                bot1.select_action_with_engine(&engine)
            } else {
                bot2.select_action_with_engine(&engine)
            };

            actions.push(action);
            if engine.apply_action(action).is_err() {
                break;
            }
        }

        let turns = engine.turn_number() as u32;

        // Verify game produced a valid outcome
        assert!(
            turns > 0,
            "Test '{}': Game should have at least 1 turn",
            test.name
        );
        // Games can end at turn 31 when hitting the turn limit (turn increments before check)
        assert!(
            turns <= 31,
            "Test '{}': Game should not exceed 31 turns",
            test.name
        );
        assert!(
            !actions.is_empty(),
            "Test '{}': Game should have at least 1 action",
            test.name
        );

        // All actions should be valid types
        for (i, action) in actions.iter().enumerate() {
            match action {
                Action::PlayCard { hand_index, slot } => {
                    assert!(*hand_index < 20, "Test '{}': Action {}: Invalid hand index", test.name, i);
                    assert!(slot.0 < 5, "Test '{}': Action {}: Invalid slot", test.name, i);
                }
                Action::Attack { attacker, defender } => {
                    assert!(attacker.0 < 5, "Test '{}': Action {}: Invalid attacker", test.name, i);
                    assert!(defender.0 < 5, "Test '{}': Action {}: Invalid defender", test.name, i);
                }
                Action::UseAbility { slot, ability_index, .. } => {
                    assert!(slot.0 < 5, "Test '{}': Action {}: Invalid ability slot", test.name, i);
                    assert!(*ability_index < 5, "Test '{}': Action {}: Invalid ability index", test.name, i);
                }
                Action::CommanderInsight => {}
                Action::EndTurn => {}
            }
        }
    }
}
