//! Unit tests for the bot introspection module.

use cardgame::actions::Action;
use cardgame::bots::introspection::{
    BotDecision, IntrospectionConfig, MctsNodeStats, MctsTreeSnapshot, PolicyOutput, PolicySource,
};
use cardgame::types::{PlayerId, Slot};

// ── PolicyOutput ─────────────────────────────────────────────────────────────

fn sample_policy() -> PolicyOutput {
    PolicyOutput {
        action_scores: vec![
            (Action::EndTurn, 0.1),
            (
                Action::PlayCard {
                    hand_index: 0,
                    slot: Slot(0),
                },
                0.6,
            ),
            (
                Action::Attack {
                    attacker: Slot(0),
                    defender: Slot(0),
                },
                0.3,
            ),
        ],
        value_estimate: 0.55,
        confidence: 0.8,
        source: PolicySource::Mcts,
    }
}

#[test]
fn test_best_action() {
    let policy = sample_policy();
    let best = policy.best_action().unwrap();
    assert!(matches!(best, Action::PlayCard { hand_index: 0, .. }));
}

#[test]
fn test_best_action_empty() {
    let policy = PolicyOutput {
        action_scores: vec![],
        value_estimate: 0.0,
        confidence: 0.0,
        source: PolicySource::Random,
    };
    assert!(policy.best_action().is_none());
}

#[test]
fn test_top_actions() {
    let policy = sample_policy();
    let top2 = policy.top_actions(2);

    assert_eq!(top2.len(), 2);
    assert!(matches!(top2[0].0, Action::PlayCard { .. })); // Highest score (0.6)
    assert!(matches!(top2[1].0, Action::Attack { .. })); // Second (0.3)
}

#[test]
fn test_top_actions_more_than_available() {
    let policy = sample_policy();
    let top10 = policy.top_actions(10);
    assert_eq!(top10.len(), 3); // Only 3 actions available
}

#[test]
fn test_as_probabilities_sums_to_one() {
    let policy = sample_policy();
    let probs = policy.as_probabilities();

    let sum: f32 = probs.iter().map(|(_, p)| p).sum();
    assert!((sum - 1.0).abs() < 0.001);
}

#[test]
fn test_as_probabilities_preserves_ratio() {
    let policy = sample_policy();
    let probs = policy.as_probabilities();

    // 0.6 / (0.1 + 0.6 + 0.3) = 0.6
    assert!((probs[1].1 - 0.6).abs() < 0.001);
}

#[test]
fn test_as_probabilities_zero_sum() {
    let policy = PolicyOutput {
        action_scores: vec![(Action::EndTurn, 0.0), (Action::EndTurn, 0.0)],
        value_estimate: 0.0,
        confidence: 0.0,
        source: PolicySource::Random,
    };
    let probs = policy.as_probabilities();
    // Should return unchanged when sum <= 0
    assert_eq!(probs[0].1, 0.0);
}

// ── BotDecision constructors ─────────────────────────────────────────────────

#[test]
fn test_simple_decision() {
    let decision = BotDecision::simple(5, PlayerId::PLAYER_ONE, Action::EndTurn, 1000);

    assert_eq!(decision.turn, 5);
    assert_eq!(decision.player, PlayerId::PLAYER_ONE);
    assert!(matches!(decision.action, Action::EndTurn));
    assert!(decision.policy.is_none());
    assert!(decision.mcts_snapshot.is_none());
    assert_eq!(decision.thinking_time_us, 1000);
}

#[test]
fn test_with_policy_decision() {
    let policy = sample_policy();
    let decision =
        BotDecision::with_policy(3, PlayerId::PLAYER_TWO, Action::EndTurn, policy, 500);

    assert_eq!(decision.turn, 3);
    assert!(decision.policy.is_some());
    assert!(decision.mcts_snapshot.is_none());
}

#[test]
fn test_mcts_decision() {
    let policy = sample_policy();
    let snapshot = MctsTreeSnapshot {
        total_simulations: 1000,
        root_visits: 1000,
        children: vec![MctsNodeStats {
            action: Action::EndTurn,
            visits: 500,
            wins: 250.0,
            win_rate: 0.5,
            ucb1_score: 1.2,
            prior: None,
        }],
        selected_action: Action::EndTurn,
        root_value: 0.5,
        max_depth: 8,
    };

    let decision = BotDecision::mcts(
        1,
        PlayerId::PLAYER_ONE,
        Action::EndTurn,
        policy,
        snapshot,
        2000,
    );

    assert!(decision.policy.is_some());
    assert!(decision.mcts_snapshot.is_some());
    let snap = decision.mcts_snapshot.unwrap();
    assert_eq!(snap.total_simulations, 1000);
    assert_eq!(snap.children.len(), 1);
}

// ── IntrospectionConfig presets ──────────────────────────────────────────────

#[test]
fn test_config_full() {
    let config = IntrospectionConfig::full();
    assert!(config.record_mcts_tree);
    assert_eq!(config.max_tree_depth, 10);
    assert!(config.record_all_actions);
}

#[test]
fn test_config_lightweight() {
    let config = IntrospectionConfig::lightweight();
    assert!(!config.record_mcts_tree);
    assert!(!config.record_all_actions);
    assert_eq!(config.top_n_actions, 5);
}

#[test]
fn test_config_disabled() {
    let config = IntrospectionConfig::disabled();
    assert!(!config.record_mcts_tree);
    assert!(!config.record_all_actions);
    assert_eq!(config.top_n_actions, 0);
}

// ── serialization roundtrips ─────────────────────────────────────────────────

#[test]
fn test_policy_output_serialization() {
    let policy = sample_policy();
    let json = serde_json::to_string(&policy).unwrap();
    let restored: PolicyOutput = serde_json::from_str(&json).unwrap();

    assert_eq!(restored.value_estimate, policy.value_estimate);
    assert_eq!(restored.confidence, policy.confidence);
    assert_eq!(restored.source, PolicySource::Mcts);
    assert_eq!(restored.action_scores.len(), 3);
}

#[test]
fn test_bot_decision_serialization() {
    let decision = BotDecision::simple(1, PlayerId::PLAYER_ONE, Action::EndTurn, 100);
    let json = serde_json::to_string(&decision).unwrap();
    let restored: BotDecision = serde_json::from_str(&json).unwrap();

    assert_eq!(restored.turn, 1);
    assert_eq!(restored.thinking_time_us, 100);
}

#[test]
fn test_mcts_tree_snapshot_serialization() {
    let snapshot = MctsTreeSnapshot {
        total_simulations: 500,
        root_visits: 500,
        children: vec![],
        selected_action: Action::EndTurn,
        root_value: 0.45,
        max_depth: 6,
    };

    let json = serde_json::to_string(&snapshot).unwrap();
    let restored: MctsTreeSnapshot = serde_json::from_str(&json).unwrap();

    assert_eq!(restored.total_simulations, 500);
    assert_eq!(restored.max_depth, 6);
}
