//! Converter from action-based `GameReplay` to state-snapshot-based `StoredReplay`.
//!
//! This enables replays recorded by the MCP server (lightweight action log)
//! to be viewed in the Tauri desktop app (which needs full state snapshots).

use std::sync::Arc;

use crate::cards::CardDatabase;
use crate::client_api::GameClient;
use crate::dto::{
    action_to_info, game_event_to_dto, CardDto, CreatureDto, GameEventDto, GameStateDto,
    PlayerStateDto, SupportDto,
};
use crate::replay::types::GameReplay;
use crate::replay::stored::{ReplayMetadata, StoredReplay};
use crate::spectator::{SpectatorAction, SpectatorConfig, SpectatorMatch, SpectatorResult};

/// Convert a `GameReplay` (action log) into a `StoredReplay` (state snapshots).
///
/// This replays the game using `GameClient` to capture full state and events
/// after each action, producing the format the Tauri desktop app expects.
///
/// # Arguments
/// * `replay` - The action-based replay to convert
/// * `card_db` - Card database for card lookups
/// * `player1_display_name` - Display name for player 1's deck (e.g., "Architect Fortify")
/// * `player2_display_name` - Display name for player 2's deck (e.g., "Broodmother Pack")
pub fn game_replay_to_stored_replay(
    replay: &GameReplay,
    card_db: Arc<CardDatabase>,
    player1_display_name: &str,
    player2_display_name: &str,
) -> Result<StoredReplay, String> {
    let match_data = game_replay_to_spectator_match(
        replay,
        card_db,
        player1_display_name,
        player2_display_name,
    )?;

    let metadata = ReplayMetadata::new(
        match_data.player1_deck_name.clone(),
        match_data.player2_deck_name.clone(),
        match_data.player1_bot_name.clone(),
        match_data.player2_bot_name.clone(),
        match_data.result.winner,
        match_data.total_turns,
        match_data.actions.len(),
    );

    Ok(StoredReplay {
        metadata,
        match_data,
    })
}

/// Convert a `GameReplay` to a `SpectatorMatch`.
fn game_replay_to_spectator_match(
    replay: &GameReplay,
    card_db: Arc<CardDatabase>,
    player1_display_name: &str,
    player2_display_name: &str,
) -> Result<SpectatorMatch, String> {
    // Create a GameClient and start from the replay's configuration
    let mut client = GameClient::new(card_db.clone());

    let commander1 = replay.player1.commander.unwrap_or(crate::types::CardId(5000));
    let commander2 = replay.player2.commander.unwrap_or(crate::types::CardId(5000));

    client.start_game_raw(
        replay.player1.deck.clone(),
        replay.player2.deck.clone(),
        commander1,
        commander2,
        replay.header.seed,
        replay.header.mode,
    );

    let match_id = format!(
        "mcp-{}-{}",
        replay.header.seed,
        replay.header.timestamp,
    );

    // Capture initial state
    let initial_state = engine_state_to_dto(&client, &card_db, &match_id);

    // Replay all actions, capturing state and events after each
    let mut actions: Vec<SpectatorAction> = Vec::with_capacity(replay.actions.len());

    for replay_action in &replay.actions {
        let current_player = client
            .current_player()
            .ok_or_else(|| "No active player during replay".to_string())?;
        let player_num = current_player.0 + 1;
        let turn = client.turn_number();

        let action = replay_action.action;
        let action_index = action.to_index();
        let action_info = action_to_info(&action, action_index);

        // Apply action and capture events
        let events = client
            .apply_action(action)
            .map_err(|e| format!("Replay action failed: {}", e))?;

        let event_dtos: Vec<GameEventDto> = events.iter().map(game_event_to_dto).collect();
        let state_after = engine_state_to_dto(&client, &card_db, &match_id);

        let thinking_time_ms = replay_action
            .thinking_time_us
            .map(|us| us / 1000)
            .unwrap_or(0);

        actions.push(SpectatorAction {
            turn,
            player: player_num,
            action: action_info,
            state_after,
            events: event_dtos,
            thinking: None,
            insights: None, // MCP replays don't have AI insights
            thinking_time_ms,
        });
    }

    // Extract final result
    let result = client.get_result();
    let final_state = client.get_state();

    let (winner, reason) = match result {
        Some(crate::core::state::GameResult::Win { winner, reason }) => {
            (Some(winner.0 + 1), format!("{:?}", reason))
        }
        Some(crate::core::state::GameResult::Draw) => (None, "TurnLimit".to_string()),
        None => (None, "Unknown".to_string()),
    };

    let (p1_life, p2_life) = final_state
        .map(|s| (s.players[0].life, s.players[1].life))
        .unwrap_or((0, 0));

    let spectator_result = SpectatorResult {
        winner,
        reason,
        player1_final_life: p1_life,
        player2_final_life: p2_life,
    };

    // Build SpectatorConfig
    let config = SpectatorConfig {
        player1_deck_id: replay.player1.deck_name.clone().unwrap_or_default(),
        player1_bot_type: replay.player1.player_type.clone(),
        player2_deck_id: replay.player2.deck_name.clone().unwrap_or_default(),
        player2_bot_type: replay.player2.player_type.clone(),
        seed: Some(replay.header.seed),
        mcts_simulations: 100,
        alphabeta_depth: 4,
    };

    // Build display names
    let player1_bot_name = bot_display_name(&replay.player1.player_type);
    let player2_bot_name = bot_display_name(&replay.player2.player_type);

    Ok(SpectatorMatch {
        id: match_id,
        config,
        initial_state,
        actions,
        result: spectator_result,
        total_turns: client.turn_number(),
        player1_deck_name: player1_display_name.to_string(),
        player2_deck_name: player2_display_name.to_string(),
        player1_bot_name,
        player2_bot_name,
        eval_history: Vec::new(),
        key_moments: Vec::new(),
    })
}

/// Convert GameClient state to GameStateDto with both hands visible (spectator/replay mode).
fn engine_state_to_dto(
    client: &GameClient,
    card_db: &CardDatabase,
    game_id: &str,
) -> GameStateDto {
    let state = match client.get_state() {
        Some(s) => s,
        None => return empty_state_dto(game_id),
    };

    let p1 = &state.players[0];
    let p2 = &state.players[1];

    // Convert both hands (visible in spectator/replay mode)
    let p1_hand: Vec<CardDto> = p1
        .hand
        .iter()
        .filter_map(|ci| card_db.get(ci.card_id).map(CardDto::from_card_def))
        .collect();

    let p2_hand: Vec<CardDto> = p2
        .hand
        .iter()
        .filter_map(|ci| card_db.get(ci.card_id).map(CardDto::from_card_def))
        .collect();

    let p1_creatures = creatures_to_slots(p1, card_db, state.current_turn);
    let p2_creatures = creatures_to_slots(p2, card_db, state.current_turn);
    let p1_supports = supports_to_slots(p1, card_db);
    let p2_supports = supports_to_slots(p2, card_db);

    let result = client.get_result();
    let winner = result.and_then(|r| match r {
        crate::core::state::GameResult::Win { winner, .. } => Some(winner.0),
        crate::core::state::GameResult::Draw => None,
    });
    let game_over_reason = result.map(|r| match r {
        crate::core::state::GameResult::Win { reason, .. } => format!("{:?}", reason),
        crate::core::state::GameResult::Draw => "draw".to_string(),
    });

    GameStateDto {
        id: game_id.to_string(),
        turn: state.current_turn,
        phase: format!("{:?}", state.phase),
        active_player: state.active_player.0,
        player: PlayerStateDto {
            life: p1.life,
            max_life: 30,
            essence: p1.current_essence,
            max_essence: p1.max_essence,
            action_points: p1.action_points,
            deck_count: p1.deck.len(),
            essence_extracted: p1.total_damage_dealt,
            hand: p1_hand,
            creatures: p1_creatures,
            supports: p1_supports,
            commander: None,
        },
        opponent: PlayerStateDto {
            life: p2.life,
            max_life: 30,
            essence: p2.current_essence,
            max_essence: p2.max_essence,
            action_points: p2.action_points,
            deck_count: p2.deck.len(),
            essence_extracted: p2.total_damage_dealt,
            hand: p2_hand,
            creatures: p2_creatures,
            supports: p2_supports,
            commander: None,
        },
        is_game_over: client.is_game_over(),
        winner,
        game_over_reason,
    }
}

/// Convert creatures to slot-indexed array.
fn creatures_to_slots(
    player_state: &crate::core::state::PlayerState,
    card_db: &CardDatabase,
    current_turn: u16,
) -> Vec<Option<CreatureDto>> {
    let mut slots: Vec<Option<CreatureDto>> = vec![None; 5];
    for creature in player_state.creatures.iter() {
        if creature.card_id.0 == 0 {
            slots[creature.slot.0 as usize] = Some(CreatureDto::from_token(creature, current_turn));
        } else if let Some(card) = card_db.get(creature.card_id) {
            slots[creature.slot.0 as usize] =
                Some(CreatureDto::from_creature(creature, card, current_turn));
        }
    }
    slots
}

/// Convert supports to slot-indexed array.
fn supports_to_slots(
    player_state: &crate::core::state::PlayerState,
    card_db: &CardDatabase,
) -> Vec<Option<SupportDto>> {
    let mut slots: Vec<Option<SupportDto>> = vec![None; 2];
    for support in player_state.supports.iter() {
        if let Some(card) = card_db.get(support.card_id) {
            slots[support.slot.0 as usize] = Some(SupportDto::from_support(support, card));
        }
    }
    slots
}

/// Create an empty state DTO (for when no game state is available).
fn empty_state_dto(game_id: &str) -> GameStateDto {
    GameStateDto {
        id: game_id.to_string(),
        turn: 0,
        phase: "not_started".to_string(),
        active_player: 0,
        player: PlayerStateDto {
            life: 0,
            max_life: 30,
            essence: 0,
            max_essence: 0,
            action_points: 0,
            deck_count: 0,
            essence_extracted: 0,
            hand: Vec::new(),
            creatures: vec![None; 5],
            supports: vec![None; 2],
            commander: None,
        },
        opponent: PlayerStateDto {
            life: 0,
            max_life: 30,
            essence: 0,
            max_essence: 0,
            action_points: 0,
            deck_count: 0,
            essence_extracted: 0,
            hand: Vec::new(),
            creatures: vec![None; 5],
            supports: vec![None; 2],
            commander: None,
        },
        is_game_over: false,
        winner: None,
        game_over_reason: None,
    }
}

/// Get display name for a player type string.
fn bot_display_name(player_type: &str) -> String {
    match player_type {
        "human" => "Human".to_string(),
        "random" => "Random Bot".to_string(),
        "greedy" => "Greedy Bot".to_string(),
        "mcts" => "MCTS Bot".to_string(),
        "alphabeta" => "Alpha-Beta Bot".to_string(),
        other => other.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::replay::{PlayerConfig, ReplayBuilder};
    use crate::core::state::GameMode;

    #[test]
    fn test_game_replay_to_stored_replay() {
        let data_dir = crate::data_dir();
        let card_db = Arc::new(CardDatabase::load_with_commanders(
            data_dir.join("cards/core_set"),
            data_dir.join("commanders"),
        ).unwrap());
        let deck_registry = crate::DeckRegistry::load_from_directory(
            data_dir.join("decks"),
        ).unwrap();

        let deck1 = deck_registry.get("architect_fortify").expect("deck not found");
        let deck2 = deck_registry.get("broodmother_pack").expect("deck not found");

        // Play a quick game using random bots to generate a replay
        let seed = 12345u64;
        let mut engine = crate::engine::GameEngine::new(&card_db);
        engine.start_game(deck1, deck2, seed).unwrap();

        let mut builder = ReplayBuilder::new(
            seed,
            GameMode::EssenceWar,
            PlayerConfig {
                name: "Human".into(),
                player_type: "human".into(),
                deck: deck1.cards.iter().map(|&id| crate::types::CardId(id)).collect(),
                deck_name: Some("architect_fortify".into()),
                commander: Some(crate::types::CardId(deck1.commander)),
            },
            PlayerConfig {
                name: "Greedy Bot".into(),
                player_type: "greedy".into(),
                deck: deck2.cards.iter().map(|&id| crate::types::CardId(id)).collect(),
                deck_name: Some("broodmother_pack".into()),
                commander: Some(crate::types::CardId(deck2.commander)),
            },
        );

        // Play a few random moves
        let mut rng = crate::bots::RandomBot::new(42);
        let mut move_count = 0;
        while !engine.is_terminal() && move_count < 200 {
            let legal = engine.get_legal_actions();
            if legal.is_empty() { break; }
            let action = crate::bots::Bot::select_action_with_engine(&mut rng, &engine);
            let turn = engine.state.current_turn;
            builder.record_action(turn, action, None);
            engine.apply_action(action).unwrap();
            move_count += 1;
        }

        let result = engine.state.result;
        let total_turns = engine.state.current_turn;
        let final_life = [engine.state.players[0].life, engine.state.players[1].life];
        let replay = builder.finalize(result, total_turns, final_life);

        // Convert to StoredReplay
        let stored = game_replay_to_stored_replay(
            &replay,
            card_db,
            "Architect Fortify",
            "Broodmother Pack",
        ).unwrap();

        // Verify structure
        assert!(!stored.match_data.id.is_empty());
        assert!(!stored.match_data.actions.is_empty());
        assert_eq!(stored.match_data.actions.len(), replay.actions.len());
        assert_eq!(stored.match_data.player1_deck_name, "Architect Fortify");
        assert_eq!(stored.match_data.player2_deck_name, "Broodmother Pack");
        assert_eq!(stored.match_data.player1_bot_name, "Human");
        assert_eq!(stored.match_data.player2_bot_name, "Greedy Bot");
        assert_eq!(stored.metadata.player1_deck_name, "Architect Fortify");

        // Verify each action has state_after
        for action in &stored.match_data.actions {
            assert!(action.player == 1 || action.player == 2);
            assert!(action.turn > 0);
        }

        // Verify serialization works
        let json = serde_json::to_string_pretty(&stored).unwrap();
        assert!(json.contains("matchData"));
        assert!(json.contains("metadata"));
        let _roundtrip: StoredReplay = serde_json::from_str(&json).unwrap();
    }
}
