//! Unit tests for legal action generation.

use cardgame::actions::Action;
use cardgame::cards::{CardDatabase, CardDefinition, CardType};
use cardgame::effects::{TargetingRule, TokenAbility, TokenData, TokenEffect};
use cardgame::keywords::Keywords;
use cardgame::legal::{legal_action_mask, legal_actions};
use cardgame::state::{CardInstance, Creature, CreatureStatus, GameState};
use cardgame::types::{CardId, CreatureInstanceId, PlayerId, Rarity, Slot};

/// Create a test card database with some basic cards
fn test_card_db() -> CardDatabase {
    let cards = vec![
        // Basic creature: cost 2, 2/3
        CardDefinition {
            id: 1,
            name: "Test Creature".to_string(),
            cost: 2,
            card_type: CardType::Creature {
                attack: 2,
                health: 3,
                keywords: vec![],
                abilities: vec![],
                keywords_bits: cardgame::keywords::Keywords::none(),
            },
            rarity: Rarity::Common,
            tags: vec![],
        },
        // Expensive creature: cost 5, 4/4
        CardDefinition {
            id: 2,
            name: "Expensive Creature".to_string(),
            cost: 5,
            card_type: CardType::Creature {
                attack: 4,
                health: 4,
                keywords: vec![],
                abilities: vec![],
                keywords_bits: cardgame::keywords::Keywords::none(),
            },
            rarity: Rarity::Uncommon,
            tags: vec![],
        },
        // Ranged creature: cost 3, 2/2, Ranged
        CardDefinition {
            id: 3,
            name: "Ranged Creature".to_string(),
            cost: 3,
            card_type: CardType::Creature {
                attack: 2,
                health: 2,
                keywords: vec!["Ranged".to_string()],
                abilities: vec![],
                keywords_bits: cardgame::keywords::Keywords::none(),
            },
            rarity: Rarity::Common,
            tags: vec![],
        },
        // Guard creature: cost 2, 1/4, Guard
        CardDefinition {
            id: 4,
            name: "Guard Creature".to_string(),
            cost: 2,
            card_type: CardType::Creature {
                attack: 1,
                health: 4,
                keywords: vec!["Guard".to_string()],
                abilities: vec![],
                keywords_bits: cardgame::keywords::Keywords::none(),
            },
            rarity: Rarity::Common,
            tags: vec![],
        },
        // Test spell: cost 1
        CardDefinition {
            id: 5,
            name: "Test Spell".to_string(),
            cost: 1,
            card_type: CardType::Spell {
                targeting: TargetingRule::NoTarget,
                effects: vec![],
                conditional_effects: vec![],
            },
            rarity: Rarity::Common,
            tags: vec![],
        },
        // Test support: cost 3
        CardDefinition {
            id: 6,
            name: "Test Support".to_string(),
            cost: 3,
            card_type: CardType::Support {
                durability: 2,
                passive_effects: vec![],
                triggered_effects: vec![],
            },
            rarity: Rarity::Common,
            tags: vec![],
        },
    ];
    CardDatabase::new(cards)
}

/// Create a basic test creature
fn make_creature(
    card_id: u16,
    slot: u8,
    owner: PlayerId,
    turn_played: u16,
    keywords: Keywords,
) -> Creature {
    Creature {
        instance_id: CreatureInstanceId(slot as u32),
        card_id: CardId(card_id),
        owner,
        slot: Slot(slot),
        attack: 2,
        current_health: 3,
        max_health: 3,
        base_attack: 2,
        base_health: 3,
        keywords,
        status: CreatureStatus::default(),
        turn_played,
        frenzy_stacks: 0,
        token_data: None,
    }
}

/// Helper to set up resources for a player in test scenarios.
fn setup_resources(state: &mut GameState, player_idx: usize, ap: u8, essence: u8) {
    state.players[player_idx].action_points = ap;
    state.players[player_idx].max_essence = essence;
    state.players[player_idx].current_essence = essence;
}

#[test]
fn test_empty_board_only_end_turn() {
    let state = GameState::new();
    let card_db = CardDatabase::empty();

    let actions = legal_actions(&state, &card_db);

    // Only EndTurn should be legal
    assert_eq!(actions.len(), 1);
    assert_eq!(actions[0], Action::EndTurn);
}

#[test]
fn test_hand_with_playable_card() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player 3 AP, enough essence, and a card in hand (cost 2)
    setup_resources(&mut state, 0, 3, 10);
    state.players[0].hand.push(CardInstance::new(CardId(1)));

    let actions = legal_actions(&state, &card_db);

    // Should have PlayCard actions for all 5 empty slots + EndTurn
    assert_eq!(actions.len(), 6);

    // Verify we have PlayCard actions for slots 0-4
    let play_card_count = actions
        .iter()
        .filter(|a| matches!(a, Action::PlayCard { .. }))
        .count();
    assert_eq!(play_card_count, 5);
}

#[test]
fn test_not_enough_ap() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player 1 AP and a card that costs 5
    state.players[0].action_points = 1;
    state.players[0].hand.push(CardInstance::new(CardId(2)));

    let actions = legal_actions(&state, &card_db);

    // Only EndTurn should be legal (can't afford the card)
    assert_eq!(actions.len(), 1);
    assert_eq!(actions[0], Action::EndTurn);
}

#[test]
fn test_creature_attack_adjacent_targeting() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2; // Turn 2 so creatures aren't summoning sick

    // Place a creature at slot 2 (can attack slots 1, 2, 3)
    let creature = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    // Should have attacks on adjacent slots (1, 2, 3) + EndTurn
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert_eq!(attack_count, 3);

    // Verify specific attacks
    assert!(actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(1)
    }));
    assert!(actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(2)
    }));
    assert!(actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(3)
    }));

    // Should NOT be able to attack slot 0 or 4
    assert!(!actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(0)
    }));
    assert!(!actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(4)
    }));
}

#[test]
fn test_ranged_creature_any_target() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;

    // Place a ranged creature at slot 0
    let creature = make_creature(3, 0, PlayerId::PLAYER_ONE, 1, Keywords::none().with_ranged());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    // Ranged should be able to attack all 5 slots
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert_eq!(attack_count, 5);

    // Verify can attack any slot
    for slot in 0..5 {
        assert!(actions.contains(&Action::Attack {
            attacker: Slot(0),
            defender: Slot(slot)
        }));
    }
}

#[test]
fn test_guard_enforcement() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;

    // Place an attacker at slot 2 for player 1
    let attacker = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none());
    state.players[0].creatures.push(attacker);

    // Place a guard creature at slot 1 for player 2
    let guard = make_creature(4, 1, PlayerId::PLAYER_TWO, 1, Keywords::none().with_guard());
    state.players[1].creatures.push(guard);

    // Place a non-guard creature at slot 3 for player 2
    let non_guard = make_creature(1, 3, PlayerId::PLAYER_TWO, 1, Keywords::none());
    state.players[1].creatures.push(non_guard);

    let actions = legal_actions(&state, &card_db);

    // Can only attack the guard creature
    let attacks: Vec<_> = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .collect();
    assert_eq!(attacks.len(), 1);
    assert!(actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(1)
    }));

    // Cannot attack the non-guard or empty slots
    assert!(!actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(3)
    }));
    assert!(!actions.contains(&Action::Attack {
        attacker: Slot(2),
        defender: Slot(2)
    }));
}

#[test]
fn test_guard_enforcement_ranged() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;

    // Place a ranged attacker at slot 0 for player 1
    let attacker = make_creature(3, 0, PlayerId::PLAYER_ONE, 1, Keywords::none().with_ranged());
    state.players[0].creatures.push(attacker);

    // Place a guard creature at slot 4 for player 2
    let guard = make_creature(4, 4, PlayerId::PLAYER_TWO, 1, Keywords::none().with_guard());
    state.players[1].creatures.push(guard);

    // Place a non-guard creature at slot 1 for player 2
    let non_guard = make_creature(1, 1, PlayerId::PLAYER_TWO, 1, Keywords::none());
    state.players[1].creatures.push(non_guard);

    let actions = legal_actions(&state, &card_db);

    // Ranged can still only attack guard when guards are present
    let attacks: Vec<_> = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .collect();
    assert_eq!(attacks.len(), 1);
    assert!(actions.contains(&Action::Attack {
        attacker: Slot(0),
        defender: Slot(4)
    }));
}

#[test]
fn test_summoning_sickness() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 1;

    // Place a creature played this turn (has summoning sickness)
    let creature = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    // No attacks should be possible (summoning sickness)
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert_eq!(attack_count, 0);
}

#[test]
fn test_rush_ignores_summoning_sickness() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 1;

    // Place a Rush creature played this turn
    let creature = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none().with_rush());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    // Rush creature CAN attack on the turn it's played
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert!(attack_count > 0);
}

#[test]
fn test_legal_action_mask() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player 3 AP, essence, and a card in hand
    setup_resources(&mut state, 0, 3, 10);
    state.players[0].hand.push(CardInstance::new(CardId(1)));

    let mask = legal_action_mask(&state, &card_db);

    // EndTurn (index 421) should be legal
    assert!(mask[421]);

    // PlayCard for hand_index=0, slots 0-4 should be legal
    // Index = hand_idx * 12 + slot
    for (slot, &is_legal) in mask.iter().enumerate().take(5) {
        assert!(is_legal, "PlayCard(0, {slot}) should be legal");
    }

    // Other PlayCard indices should be false
    assert!(!mask[12]); // hand_index=1, slot=0 (no card at index 1)
}

#[test]
fn test_spell_play_action() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player AP, essence, and a spell in hand
    setup_resources(&mut state, 0, 5, 10);
    state.players[0].hand.push(CardInstance::new(CardId(5))); // Spell

    let actions = legal_actions(&state, &card_db);

    // Should have PlayCard for the spell + EndTurn
    let play_count = actions
        .iter()
        .filter(|a| matches!(a, Action::PlayCard { .. }))
        .count();
    assert_eq!(play_count, 1); // Spells only get one PlayCard action

    assert!(actions.contains(&Action::PlayCard {
        hand_index: 0,
        slot: Slot(0)
    }));
}

#[test]
fn test_support_play_action() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player AP, essence, and a support in hand
    setup_resources(&mut state, 0, 5, 10);
    state.players[0].hand.push(CardInstance::new(CardId(6))); // Support

    let actions = legal_actions(&state, &card_db);

    // Should have PlayCard for 2 support slots + EndTurn
    let play_count = actions
        .iter()
        .filter(|a| matches!(a, Action::PlayCard { .. }))
        .count();
    assert_eq!(play_count, 2); // Two support slots

    assert!(actions.contains(&Action::PlayCard {
        hand_index: 0,
        slot: Slot(0)
    }));
    assert!(actions.contains(&Action::PlayCard {
        hand_index: 0,
        slot: Slot(1)
    }));
}

#[test]
fn test_occupied_creature_slot() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player AP, essence, and a creature card in hand
    setup_resources(&mut state, 0, 5, 10);
    state.players[0].hand.push(CardInstance::new(CardId(1))); // Creature

    // Occupy slots 0 and 2
    let creature1 = make_creature(1, 0, PlayerId::PLAYER_ONE, 0, Keywords::none());
    let creature2 = make_creature(1, 2, PlayerId::PLAYER_ONE, 0, Keywords::none());
    state.players[0].creatures.push(creature1);
    state.players[0].creatures.push(creature2);

    let actions = legal_actions(&state, &card_db);

    // Should only be able to play to empty slots 1, 3, 4
    let play_actions: Vec<_> = actions
        .iter()
        .filter_map(|a| match a {
            Action::PlayCard { hand_index, slot } => Some((*hand_index, slot.0)),
            _ => None,
        })
        .collect();

    assert_eq!(play_actions.len(), 3);
    assert!(play_actions.contains(&(0, 1)));
    assert!(play_actions.contains(&(0, 3)));
    assert!(play_actions.contains(&(0, 4)));
}

#[test]
fn test_exhausted_creature_cannot_attack() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;

    // Place an exhausted creature
    let mut creature = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none());
    creature.status.set_exhausted(true);
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    // No attacks should be possible (exhausted)
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert_eq!(attack_count, 0);
}

#[test]
fn test_terminal_state_no_actions() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Mark game as ended
    state.result = Some(cardgame::state::GameResult::Draw);

    let actions = legal_actions(&state, &card_db);

    // No actions should be legal when game is over
    assert_eq!(actions.len(), 0);
}

#[test]
fn test_multiple_cards_in_hand() {
    let card_db = test_card_db();
    let mut state = GameState::new();

    // Give player 5 AP, 10 essence, and two cards in hand
    setup_resources(&mut state, 0, 5, 10);
    state.players[0].hand.push(CardInstance::new(CardId(1))); // cost 2 creature
    state.players[0].hand.push(CardInstance::new(CardId(2))); // cost 5 creature

    let actions = legal_actions(&state, &card_db);

    // Both cards can be played (5 AP available, 10 essence covers both costs)
    // Card 0: 5 slots, Card 1: 5 slots = 10 PlayCard + EndTurn
    let play_count = actions
        .iter()
        .filter(|a| matches!(a, Action::PlayCard { .. }))
        .count();
    assert_eq!(play_count, 10);
}

#[test]
fn test_attack_empty_slot_face_damage() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;

    // Place a creature that can attack
    let creature = make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none());
    state.players[0].creatures.push(creature);

    // No enemy creatures on board

    let actions = legal_actions(&state, &card_db);

    // Should be able to attack empty slots (for face damage)
    let attack_count = actions
        .iter()
        .filter(|a| matches!(a, Action::Attack { .. }))
        .count();
    assert_eq!(attack_count, 3); // Adjacent slots 1, 2, 3
}

// =============================================================================
// COMMANDER'S INSIGHT TESTS
// =============================================================================

#[test]
fn test_commander_insight_not_available_early_game() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 9; // Before turn 10

    // Set up conditions that would otherwise allow insight
    state.players[0].current_essence = 10;
    state.players[0].hand.clear(); // 0 cards - starving
    state.players[0].life = 10;
    state.players[1].life = 30; // Behind on life

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available before turn 10");
}

#[test]
fn test_commander_insight_not_available_with_full_hand() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    // Add 2 cards to hand (more than allowed 1)
    state.players[0].hand.push(CardInstance::new(CardId(1)));
    state.players[0].hand.push(CardInstance::new(CardId(1)));
    state.players[0].current_essence = 10;
    state.players[0].life = 10;
    state.players[1].life = 30;

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available with more than 1 card in hand");
}

#[test]
fn test_commander_insight_not_available_when_winning() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear(); // 0 cards - starving
    state.players[0].current_essence = 10;

    // Winning on both metrics
    state.players[0].life = 30;
    state.players[1].life = 20;

    // More creatures than opponent
    let creature = make_creature(1, 0, PlayerId::PLAYER_ONE, 5, Keywords::none());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available when winning");
}

#[test]
fn test_commander_insight_not_available_when_tied() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear();
    state.players[0].current_essence = 10;

    // Tied on both metrics
    state.players[0].life = 20;
    state.players[1].life = 20;

    let creature1 = make_creature(1, 0, PlayerId::PLAYER_ONE, 5, Keywords::none());
    let creature2 = make_creature(1, 0, PlayerId::PLAYER_TWO, 5, Keywords::none());
    state.players[0].creatures.push(creature1);
    state.players[1].creatures.push(creature2);

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available when tied");
}

#[test]
fn test_commander_insight_not_available_without_essence() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear();
    state.players[0].current_essence = 3; // Less than required 4
    state.players[0].life = 10;
    state.players[1].life = 30;

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available without enough essence");
}

#[test]
fn test_commander_insight_not_available_when_already_used() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear();
    state.players[0].current_essence = 10;
    state.players[0].life = 10;
    state.players[1].life = 30;
    state.players[0].used_commander_insight = true; // Already used

    let actions = legal_actions(&state, &card_db);

    assert!(!actions.contains(&Action::CommanderInsight),
        "Commander's Insight should not be available if already used this turn");
}

#[test]
fn test_commander_insight_available_behind_on_life() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear();
    state.players[0].current_essence = 10;
    state.players[0].life = 10;
    state.players[1].life = 30; // Behind on life

    // Tied or ahead on creatures
    let creature = make_creature(1, 0, PlayerId::PLAYER_ONE, 5, Keywords::none());
    state.players[0].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    assert!(actions.contains(&Action::CommanderInsight),
        "Commander's Insight should be available when behind on life");
}

#[test]
fn test_commander_insight_available_behind_on_creatures() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    state.players[0].hand.clear();
    state.players[0].current_essence = 10;
    state.players[0].life = 30;
    state.players[1].life = 20; // Ahead on life

    // Behind on creatures (opponent has more)
    let creature = make_creature(1, 0, PlayerId::PLAYER_TWO, 5, Keywords::none());
    state.players[1].creatures.push(creature);

    let actions = legal_actions(&state, &card_db);

    assert!(actions.contains(&Action::CommanderInsight),
        "Commander's Insight should be available when behind on creatures");
}

#[test]
fn test_commander_insight_available_with_one_card() {
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 10;

    // Exactly 1 card in hand is still eligible
    state.players[0].hand.push(CardInstance::new(CardId(1)));
    state.players[0].current_essence = 10;
    state.players[0].life = 10;
    state.players[1].life = 30;

    let actions = legal_actions(&state, &card_db);

    assert!(actions.contains(&Action::CommanderInsight),
        "Commander's Insight should be available with exactly 1 card in hand");
}

// ========================================
// Token Ability Tests
// ========================================

#[test]
fn test_token_ability_generates_use_ability_action() {
    let card_db = CardDatabase::empty();
    let mut state = GameState::new();

    // Give player resources
    setup_resources(&mut state, 0, 3, 5);

    // Create a token creature with an activated ability
    let token_abilities = vec![TokenAbility {
        name: "Volatile Overload".to_string(),
        essence_cost: 1,
        targeting: TargetingRule::TargetAny,
        effects: vec![
            TokenEffect::DestroySelf,
            TokenEffect::Damage { amount: 2 },
        ],
    }];

    let token = Creature {
        instance_id: CreatureInstanceId(0),
        card_id: CardId(0), // Token marker
        owner: PlayerId::PLAYER_ONE,
        slot: Slot(0),
        attack: 2,
        current_health: 2,
        max_health: 2,
        base_attack: 2,
        base_health: 2,
        keywords: Keywords::none(),
        status: CreatureStatus::default(),
        turn_played: 1, // Not summoning sick
        frenzy_stacks: 0,
        token_data: Some(Box::new(TokenData {
            name: "Brass Cog".to_string(),
            abilities: token_abilities,
        })),
    };
    state.players[0].creatures.push(token);

    let actions = legal_actions(&state, &card_db);

    // Should have UseAbility action with TargetAny -> targets face (NoTarget)
    // No enemy creatures, so only face target available
    let use_ability_count = actions
        .iter()
        .filter(|a| matches!(a, Action::UseAbility { .. }))
        .count();

    assert!(
        use_ability_count > 0,
        "Should have UseAbility actions for token ability. Got actions: {:?}",
        actions
    );
}

#[test]
fn test_token_ability_requires_essence() {
    let card_db = CardDatabase::empty();
    let mut state = GameState::new();

    // Give player AP but NO essence
    state.players[0].action_points = 3;
    state.players[0].max_essence = 0;
    state.players[0].current_essence = 0;

    // Create a token with ability that costs 1 essence
    let token_abilities = vec![TokenAbility {
        name: "Volatile Overload".to_string(),
        essence_cost: 1,
        targeting: TargetingRule::NoTarget,
        effects: vec![TokenEffect::DestroySelf],
    }];

    let token = Creature {
        instance_id: CreatureInstanceId(0),
        card_id: CardId(0),
        owner: PlayerId::PLAYER_ONE,
        slot: Slot(0),
        attack: 2,
        current_health: 2,
        max_health: 2,
        base_attack: 2,
        base_health: 2,
        keywords: Keywords::none(),
        status: CreatureStatus::default(),
        turn_played: 1,
        frenzy_stacks: 0,
        token_data: Some(Box::new(TokenData {
            name: "Brass Cog".to_string(),
            abilities: token_abilities,
        })),
    };
    state.players[0].creatures.push(token);

    let actions = legal_actions(&state, &card_db);

    // Should NOT have UseAbility action (not enough essence)
    let use_ability_count = actions
        .iter()
        .filter(|a| matches!(a, Action::UseAbility { .. }))
        .count();

    assert_eq!(
        use_ability_count, 0,
        "Should NOT have UseAbility actions without enough essence"
    );
}

#[test]
fn test_silenced_token_cannot_use_ability() {
    let card_db = CardDatabase::empty();
    let mut state = GameState::new();

    setup_resources(&mut state, 0, 3, 5);

    // Create a SILENCED token with ability
    let token_abilities = vec![TokenAbility {
        name: "Volatile Overload".to_string(),
        essence_cost: 1,
        targeting: TargetingRule::NoTarget,
        effects: vec![TokenEffect::DestroySelf],
    }];

    let mut token = Creature {
        instance_id: CreatureInstanceId(0),
        card_id: CardId(0),
        owner: PlayerId::PLAYER_ONE,
        slot: Slot(0),
        attack: 2,
        current_health: 2,
        max_health: 2,
        base_attack: 2,
        base_health: 2,
        keywords: Keywords::none(),
        status: CreatureStatus::default(),
        turn_played: 1,
        frenzy_stacks: 0,
        token_data: Some(Box::new(TokenData {
            name: "Brass Cog".to_string(),
            abilities: token_abilities,
        })),
    };
    token.status.set_silenced(true);
    state.players[0].creatures.push(token);

    let actions = legal_actions(&state, &card_db);

    // Should NOT have UseAbility action (silenced)
    let use_ability_count = actions
        .iter()
        .filter(|a| matches!(a, Action::UseAbility { .. }))
        .count();

    assert_eq!(
        use_ability_count, 0,
        "Silenced token should NOT be able to use abilities"
    );
}

#[test]
fn test_token_ability_action_indices() {
    let card_db = CardDatabase::empty();
    let mut state = GameState::new();

    // Give player resources
    setup_resources(&mut state, 0, 3, 5);

    // Create a token creature with an activated ability
    let token_abilities = vec![TokenAbility {
        name: "Volatile Overload".to_string(),
        essence_cost: 1,
        targeting: TargetingRule::TargetAny,
        effects: vec![
            TokenEffect::DestroySelf,
            TokenEffect::Damage { amount: 2 },
        ],
    }];

    let token = Creature {
        instance_id: CreatureInstanceId(0),
        card_id: CardId(0), // Token marker
        owner: PlayerId::PLAYER_ONE,
        slot: Slot(0),
        attack: 2,
        current_health: 2,
        max_health: 2,
        base_attack: 2,
        base_health: 2,
        keywords: Keywords::none(),
        status: CreatureStatus::default(),
        turn_played: 1,
        frenzy_stacks: 0,
        token_data: Some(Box::new(TokenData {
            name: "Brass Cog".to_string(),
            abilities: token_abilities,
        })),
    };
    state.players[0].creatures.push(token);

    // Add an enemy creature to have a target
    let enemy = Creature {
        instance_id: CreatureInstanceId(1),
        card_id: CardId(1),
        owner: PlayerId::PLAYER_TWO,
        slot: Slot(2),
        attack: 2,
        current_health: 2,
        max_health: 2,
        base_attack: 2,
        base_health: 2,
        keywords: Keywords::none(),
        status: CreatureStatus::default(),
        turn_played: 1,
        frenzy_stacks: 0,
        token_data: None,
    };
    state.players[1].creatures.push(enemy);

    let actions = legal_actions(&state, &card_db);

    // Find UseAbility actions
    let use_ability_actions: Vec<_> = actions
        .iter()
        .filter(|a| matches!(a, Action::UseAbility { .. }))
        .collect();

    println!("UseAbility actions found: {:?}", use_ability_actions);

    // TargetAny should create:
    // 1. Action targeting enemy creature (slot 2)
    // 2. Action targeting face (NoTarget)
    assert_eq!(
        use_ability_actions.len(), 2,
        "Should have 2 UseAbility actions (1 enemy creature + 1 face). Got: {:?}",
        use_ability_actions
    );

    // Verify action indices are in the UseAbility range (145-419)
    for action in &use_ability_actions {
        let index = action.to_index();
        assert!(
            (145..=419).contains(&index),
            "UseAbility action should have index 145-419, got {}",
            index
        );
    }
}

// --- Stealth + Lane Blocking Tests ---

#[test]
fn test_stealth_creature_does_not_block_face_attacks() {
    // A non-ranged attacker adjacent to a Stealth creature should still be able
    // to attack that slot (face attack), but NOT target the Stealth creature.
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;
    setup_resources(&mut state, 0, 3, 10);

    // P1 attacker at slot 2 (played last turn so can attack)
    state.players[0].creatures.push(make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none()));

    // P2 has a Stealth creature at slot 2 (same lane)
    state.players[1].creatures.push(make_creature(1, 2, PlayerId::PLAYER_TWO, 1, Keywords::none().with_stealth()));

    let actions = legal_actions(&state, &card_db);
    let attack_actions: Vec<_> = actions.iter().filter(|a| matches!(a, Action::Attack { .. })).collect();

    // Should have attack actions — the Stealth creature doesn't block face attacks
    assert!(!attack_actions.is_empty(), "Stealth creature should not block face attacks through its lane");

    // The attack should target slot 2 (face attack through the Stealth creature's lane)
    let attacks_slot_2: Vec<_> = attack_actions.iter()
        .filter(|a| matches!(a, Action::Attack { defender, .. } if *defender == Slot(2)))
        .collect();
    assert!(!attacks_slot_2.is_empty(), "Should be able to attack slot 2 as a face attack");
}

#[test]
fn test_all_stealth_creatures_still_allows_face_attacks() {
    // Even if ALL 5 opponent lanes have Stealth creatures, face attacks are still possible
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;
    setup_resources(&mut state, 0, 3, 10);

    // P1 attacker at slot 2
    state.players[0].creatures.push(make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none()));

    // P2 has Stealth creatures in all 5 slots
    for slot in 0..5u8 {
        state.players[1].creatures.push(make_creature(1, slot, PlayerId::PLAYER_TWO, 1, Keywords::none().with_stealth()));
    }

    let actions = legal_actions(&state, &card_db);
    let attack_actions: Vec<_> = actions.iter().filter(|a| matches!(a, Action::Attack { .. })).collect();

    // Non-ranged at slot 2 can attack adjacent slots (1, 2, 3) as face attacks
    assert!(
        !attack_actions.is_empty(),
        "All-Stealth board should NOT prevent face attacks. Got no attack actions!"
    );
}

#[test]
fn test_stealth_creature_cannot_be_directly_targeted() {
    // Verify Stealth creatures can't be targeted for creature combat
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;
    setup_resources(&mut state, 0, 3, 10);

    // P1 attacker at slot 2
    state.players[0].creatures.push(make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none()));

    // P2 has a normal creature at slot 1 and a Stealth creature at slot 3
    state.players[1].creatures.push(make_creature(1, 1, PlayerId::PLAYER_TWO, 1, Keywords::none()));
    state.players[1].creatures.push(make_creature(1, 3, PlayerId::PLAYER_TWO, 1, Keywords::none().with_stealth()));

    let actions = legal_actions(&state, &card_db);
    let attack_actions: Vec<_> = actions.iter().filter(|a| matches!(a, Action::Attack { .. })).collect();

    // Should be able to attack slot 1 (normal creature) and slot 3 (face attack through Stealth)
    // Both slots should be valid attack targets
    assert!(attack_actions.len() >= 2, "Should have at least 2 attack options");

    // Slot 1 should have a creature attack, slot 3 should be a face attack
    let attacks_slot_1 = attack_actions.iter().any(|a| matches!(a, Action::Attack { defender, .. } if *defender == Slot(1)));
    let attacks_slot_3 = attack_actions.iter().any(|a| matches!(a, Action::Attack { defender, .. } if *defender == Slot(3)));
    assert!(attacks_slot_1, "Should be able to attack normal creature at slot 1");
    assert!(attacks_slot_3, "Should be able to attack through Stealth creature at slot 3 (face attack)");
}

#[test]
fn test_ranged_stealth_interaction() {
    // Ranged attackers also can't target Stealth creatures but can face-attack their slots
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;
    setup_resources(&mut state, 0, 3, 10);

    // P1 ranged attacker at slot 0
    state.players[0].creatures.push(make_creature(3, 0, PlayerId::PLAYER_ONE, 1, Keywords::none().with_ranged()));

    // P2 has a Stealth creature at slot 4 (far away, only reachable by ranged)
    state.players[1].creatures.push(make_creature(1, 4, PlayerId::PLAYER_TWO, 1, Keywords::none().with_stealth()));

    let actions = legal_actions(&state, &card_db);
    let attack_actions: Vec<_> = actions.iter().filter(|a| matches!(a, Action::Attack { .. })).collect();

    // Ranged can attack all 5 slots as face attacks (slot 4 has Stealth, treated as empty)
    assert_eq!(attack_actions.len(), 5, "Ranged should be able to face-attack all 5 slots");
}

#[test]
fn test_guard_overrides_stealth_lane_bypass() {
    // If non-stealth Guards exist, must target them (can't bypass to face-attack Stealth lanes)
    let card_db = test_card_db();
    let mut state = GameState::new();
    state.current_turn = 2;
    setup_resources(&mut state, 0, 3, 10);

    // P1 attacker at slot 2
    state.players[0].creatures.push(make_creature(1, 2, PlayerId::PLAYER_ONE, 1, Keywords::none()));

    // P2 has a Guard at slot 1 and a Stealth creature at slot 3
    state.players[1].creatures.push(make_creature(4, 1, PlayerId::PLAYER_TWO, 1, Keywords::none().with_guard()));
    state.players[1].creatures.push(make_creature(1, 3, PlayerId::PLAYER_TWO, 1, Keywords::none().with_stealth()));

    let actions = legal_actions(&state, &card_db);
    let attack_actions: Vec<_> = actions.iter().filter(|a| matches!(a, Action::Attack { .. })).collect();

    // Must target the Guard — can't face-attack through Stealth lane when Guards present
    assert_eq!(attack_actions.len(), 1, "Must attack the Guard creature");
    assert!(matches!(attack_actions[0], Action::Attack { defender, .. } if *defender == Slot(1)));
}
