//! Unit tests for token activated abilities (UseAbility action).
//!
//! Tests verify that token abilities like Volatile Overload, Fungal Rot,
//! and Soul Siphon work correctly.

#![allow(clippy::clone_on_copy)]

use cardgame::actions::{Action, Target};
use cardgame::cards::{CardDatabase, CardType};
use cardgame::engine::GameEngine;
use cardgame::state::{CreatureStatus, GameMode};
use cardgame::types::{CardId, Slot};

/// Helper function to load the full card database with commanders
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 and commanders")
}

/// Helper to set up a game with specific commanders and advance to turn 2
/// so both players have enough essence to play 2-cost creatures.
fn setup_game_with_commanders(
    engine: &mut GameEngine,
    commander1_id: u16,
    commander2_id: u16,
    seed: u64,
) {
    // Use minimal decks with Brass Sentinel (cost 2)
    let deck1: Vec<CardId> = vec![CardId(1000); 30];
    let deck2: Vec<CardId> = vec![CardId(1000); 30];

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(commander1_id),
        CardId(commander2_id),
        seed,
        GameMode::default(),
    ).unwrap();

    // Advance to turn 2 so P1 has 2 essence (enough for 2-cost cards)
    engine.apply_action(Action::EndTurn).expect("P1 should end turn");
    engine.apply_action(Action::EndTurn).expect("P2 should end turn");
}

// =============================================================================
// FUNGAL ROT TESTS (Plague Sovereign's Sporeling token)
// =============================================================================

#[test]
fn test_fungal_rot_debuffs_enemy_creature() {
    // Plague Sovereign (5005): Summons 1/1 Sporeling with Fungal Rot
    // Fungal Rot: Pay 1 essence, sacrifice: Give target enemy creature -1/-1
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    // P1 has Plague Sovereign (spawns Sporelings)
    // P2 has Siege Marshal Vex (+2 attack, no Ward - so debuff can apply)
    setup_game_with_commanders(&mut engine, 5005, 5002, 42);

    // After setup at turn 2, P1 has 2 Sporelings (turn 1 start + turn 2 start)
    let p1_creatures_before = engine.state.players[0].creatures.len();
    assert!(p1_creatures_before >= 1, "P1 should have at least 1 Sporeling");

    // End P1 turn, P2 plays a creature (Brass Sentinel 2/5 Guard -> 4/5 with Vex +2 attack)
    engine.apply_action(Action::EndTurn).expect("P1 end turn");

    // P2 plays a creature
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("P2 plays creature");

    // Check P2's creature initial stats (4/5 with Vex attack buff)
    let p2_creature = engine.state.players[1].get_creature(Slot(0)).expect("P2 should have creature");
    let initial_attack = p2_creature.attack;
    let initial_health = p2_creature.current_health;

    engine.apply_action(Action::EndTurn).expect("P2 end turn");

    // Turn 3: P1 now has 3 essence. Use Fungal Rot on P2's creature.
    // Find the UseAbility action for slot 0 (first Sporeling), targeting enemy slot 0
    let legal = engine.get_legal_actions();

    // UseAbility actions have index >= 75 and < 254
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::EnemySlot(Slot(0))))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility action available for Sporeling targeting enemy");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Fungal Rot should work");

    // Verify: Sporeling died (sacrificed)
    assert!(
        engine.state.players[0].get_creature(Slot(0)).is_none(),
        "Sporeling should have been sacrificed"
    );

    // Verify: P2's creature has -1/-1
    let p2_creature = engine.state.players[1].get_creature(Slot(0)).expect("P2's creature should still exist");
    assert_eq!(
        p2_creature.attack, initial_attack - 1,
        "P2's creature should have -1 attack from Fungal Rot"
    );
    assert_eq!(
        p2_creature.current_health, initial_health - 1,
        "P2's creature should have -1 health from Fungal Rot"
    );
}

#[test]
fn test_fungal_rot_can_kill_creature_at_1_health() {
    // Verify that debuff can kill a creature if it brings health to 0
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    // P1 has Plague Sovereign (spawns Sporelings)
    // P2 has a passive commander

    // Use Eager Sellsword (4031) - 1 cost, 2/1 Rush for P2
    let deck1: Vec<CardId> = vec![CardId(1000); 30];
    let deck2: Vec<CardId> = vec![CardId(4031); 30]; // 2/1 creatures

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5005), // Plague Sovereign
        CardId(5002), // Siege Marshal Vex (+2 attack, no health buff)
        42,
        GameMode::default(),
    ).unwrap();

    // End P1 turn
    engine.apply_action(Action::EndTurn).expect("P1 end turn");

    // P2 plays Eager Sellsword (2/1 -> 4/1 with Vex +2 attack)
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("P2 plays creature");

    engine.apply_action(Action::EndTurn).expect("P2 end turn");

    // Turn 3: P1 uses Fungal Rot on P2's 1-health creature
    let legal = engine.get_legal_actions();
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::EnemySlot(Slot(0))))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility action");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Fungal Rot should work");

    // Verify: P2's creature died from -1 health debuff (1 - 1 = 0)
    assert!(
        engine.state.players[1].get_creature(Slot(0)).is_none(),
        "P2's creature should have died from Fungal Rot debuff (1 health - 1 = 0)"
    );
}

#[test]
fn test_fungal_rot_requires_essence() {
    // Verify that Fungal Rot requires 1 essence
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    setup_game_with_commanders(&mut engine, 5005, 5001, 42);

    // Spend all essence first
    let player = &mut engine.state.players[0];
    player.current_essence = 0;

    // End P1 turn, P2 plays a creature
    engine.apply_action(Action::EndTurn).expect("P1 end turn");
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("P2 plays creature");
    engine.apply_action(Action::EndTurn).expect("P2 end turn");

    // Turn 3: P1 has 3 essence, but let's manually set to 0
    engine.state.players[0].current_essence = 0;

    // Check that no UseAbility actions are available (no essence)
    let legal = engine.get_legal_actions();
    let has_use_ability = legal.iter().any(|a| matches!(a, Action::UseAbility { .. }));

    assert!(
        !has_use_ability,
        "Should not have UseAbility actions when essence is 0"
    );
}

// =============================================================================
// SOUL SIPHON TESTS (Shadow Weaver's Shade token)
// =============================================================================

#[test]
fn test_soul_siphon_damages_and_heals() {
    // Shadow Weaver (5010): Summons 2/1 Shade with Stealth and Soul Siphon
    // Soul Siphon: Pay 1 essence, sacrifice: Deal 1 damage to enemy commander, heal your commander for 2
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    setup_game_with_commanders(&mut engine, 5010, 5001, 42);

    // Record initial life totals
    let p2_life_before = engine.state.players[1].life;

    // Damage P1 a bit so heal has room to work
    engine.state.players[0].life = 25;
    let p1_life_before_ability = engine.state.players[0].life;

    // After setup at turn 2, P1 has Shades
    let shade_count = engine.state.players[0].creatures.len();
    assert!(shade_count >= 1, "P1 should have at least 1 Shade");

    // Verify Shade has Stealth
    let shade = engine.state.players[0].get_creature(Slot(0)).expect("Shade should exist");
    assert!(shade.keywords.has_stealth(), "Shade should have Stealth");

    // Use Soul Siphon (NoTarget ability - targets enemy commander automatically)
    let legal = engine.get_legal_actions();

    // Find UseAbility with NoTarget
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::NoTarget))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility action for Soul Siphon (NoTarget)");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Soul Siphon should work");

    // Verify: Shade died (sacrificed)
    assert!(
        engine.state.players[0].get_creature(Slot(0)).is_none(),
        "Shade should have been sacrificed"
    );

    // Verify: P2 took 1 damage
    assert_eq!(
        engine.state.players[1].life,
        p2_life_before - 1,
        "P2 should have taken 1 damage from Soul Siphon"
    );

    // Verify: P1 healed for 2
    assert_eq!(
        engine.state.players[0].life,
        p1_life_before_ability + 2,
        "P1 should have healed for 2 from Soul Siphon"
    );
}

#[test]
fn test_soul_siphon_heal_caps_at_30() {
    // Verify heal doesn't exceed max life
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    setup_game_with_commanders(&mut engine, 5010, 5001, 42);

    // Set P1 to 29 life (heal 2 would go to 31, but should cap at 30)
    engine.state.players[0].life = 29;

    // Use Soul Siphon
    let legal = engine.get_legal_actions();
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::NoTarget))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility action");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Soul Siphon should work");

    // Verify: P1 healed but capped at 30
    assert_eq!(
        engine.state.players[0].life,
        30,
        "P1's life should be capped at 30 (was 29, healed 2, cap at 30)"
    );
}

#[test]
fn test_shade_has_stealth() {
    // Verify Shade token has Stealth keyword
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    setup_game_with_commanders(&mut engine, 5010, 5001, 42);

    // Check that Shade has Stealth
    let shade = engine.state.players[0].get_creature(Slot(0)).expect("Shade should exist");
    assert!(
        shade.keywords.has_stealth(),
        "Shade should have Stealth keyword"
    );
}

// =============================================================================
// VOLATILE OVERLOAD TESTS (High Artificer's Brass Cog token)
// =============================================================================

#[test]
fn test_volatile_overload_damages_enemy_creature() {
    // High Artificer (5000): Summons 3/2 Brass Cog with Volatile Overload
    // Volatile Overload: Pay 1 essence, sacrifice: Deal 2 damage to any enemy
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    // Use Siege Marshal Vex (5002) - no Ward, so damage can apply
    setup_game_with_commanders(&mut engine, 5000, 5002, 42);

    // End P1 turn, P2 plays a creature
    engine.apply_action(Action::EndTurn).expect("P1 end turn");
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("P2 plays creature");

    // Get P2's creature health (Brass Sentinel 2/5 -> 4/5 with Vex +2 attack)
    let p2_creature = engine.state.players[1].get_creature(Slot(0)).expect("P2 should have creature");
    let initial_health = p2_creature.current_health;

    engine.apply_action(Action::EndTurn).expect("P2 end turn");

    // Turn 3: Use Volatile Overload targeting P2's creature
    let legal = engine.get_legal_actions();
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::EnemySlot(Slot(0))))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility for Volatile Overload");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Volatile Overload should work");

    // Verify: Brass Cog died
    assert!(
        engine.state.players[0].get_creature(Slot(0)).is_none(),
        "Brass Cog should have been sacrificed"
    );

    // Verify: P2's creature took 2 damage
    let p2_creature = engine.state.players[1].get_creature(Slot(0)).expect("P2's creature should still exist");
    assert_eq!(
        p2_creature.current_health,
        initial_health - 2,
        "P2's creature should have taken 2 damage from Volatile Overload"
    );
}

#[test]
fn test_volatile_overload_damages_enemy_commander() {
    // Verify Volatile Overload can target enemy commander (NoTarget = commander)
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    setup_game_with_commanders(&mut engine, 5000, 5001, 42);

    let p2_life_before = engine.state.players[1].life;

    // Use Volatile Overload with NoTarget (damages enemy commander)
    let legal = engine.get_legal_actions();
    let use_ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, target, .. }
            if *slot == Slot(0) && matches!(target, Target::NoTarget))
    });

    assert!(use_ability_action.is_some(), "Should have UseAbility for Volatile Overload (NoTarget)");

    engine.apply_action(use_ability_action.unwrap().clone()).expect("Volatile Overload should work");

    // Verify: P2 took 2 damage
    assert_eq!(
        engine.state.players[1].life,
        p2_life_before - 2,
        "P2 commander should have taken 2 damage from Volatile Overload"
    );
}

// =============================================================================
// CARD-BASED ACTIVATED ABILITY TESTS
// =============================================================================

use cardgame::cards::{CommanderDefinition, CommanderPassiveAbility, CommanderPassiveEffect, Faction};
use cardgame::types::Rarity;

/// Helper to create a card database with a test creature that has an activated ability
fn create_test_db_with_activated_ability() -> CardDatabase {
    let yaml = r#"
name: test_set
cards:
  - id: 9001
    name: "Test Buffer"
    cost: 2
    card_type: creature
    attack: 2
    health: 4
    abilities:
      - trigger: Activated
        essence_cost: 2
        targeting: NoTarget
        effects:
          - type: buff_stats
            attack: 1
            health: 1
  - id: 9002
    name: "Test Damager"
    cost: 3
    card_type: creature
    attack: 3
    health: 3
    abilities:
      - trigger: Activated
        essence_cost: 1
        targeting: TargetEnemyCreature
        effects:
          - type: damage
            amount: 2
  - id: 9003
    name: "Basic Creature"
    cost: 1
    card_type: creature
    attack: 1
    health: 2
"#;
    let card_db = CardDatabase::load_from_yaml(yaml).expect("Failed to load test card YAML");

    // Add a simple test commander
    let commanders = vec![
        CommanderDefinition {
            id: 9999,
            name: "Test Commander".to_string(),
            faction: Faction::Neutral,
            rarity: Rarity::Common,
            passive_ability: Some(CommanderPassiveAbility {
                description: "Test passive".to_string(),
                effect: CommanderPassiveEffect::BuffStats { attack: 0, health: 0 },
            }),
            triggered_ability: None,
            flavor: None,
        },
    ];
    card_db.with_commanders(commanders)
}

/// Helper to create a minimal game with test cards
fn setup_test_game_with_activated_abilities(
    engine: &mut GameEngine,
    seed: u64,
) {
    // Create decks with our test cards
    let deck1: Vec<CardId> = vec![CardId(9001), CardId(9002), CardId(9003)]
        .into_iter()
        .cycle()
        .take(30)
        .collect();
    let deck2: Vec<CardId> = vec![CardId(9003); 30];

    // Use test commander
    engine.start_game_raw(
        deck1,
        deck2,
        CardId(9999), // Test commander
        CardId(9999), // Test commander
        seed,
        GameMode::default(),
    ).unwrap();
}

#[test]
fn test_card_based_activated_ability_legal_actions() {
    // Test that card-based activated abilities appear in legal actions
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    // Give enough essence
    engine.state.players[0].current_essence = 10;

    // Find the Test Buffer (9001) in hand and play it
    let hand_idx = engine.state.players[0].hand.iter()
        .position(|c| c.card_id == CardId(9001))
        .expect("Test Buffer should be in hand");

    let action = Action::PlayCard { hand_index: hand_idx as u8, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Buffer");

    // Verify creature is on board with correct card
    let creature = engine.state.players[0].get_creature(Slot(0));
    assert!(creature.is_some(), "Test Buffer should be on board");
    let creature = creature.unwrap();
    assert_eq!(creature.card_id, CardId(9001), "Should be Test Buffer");

    // Un-exhaust the creature (normally exhausted from summoning sickness)
    if let Some(creature) = engine.state.players[0].get_creature_mut(Slot(0)) {
        creature.status.set_exhausted(false);
    }

    // Check legal actions for UseAbility
    let legal = engine.get_legal_actions();
    let has_ability_action = legal.iter().any(|a| matches!(a, Action::UseAbility { .. }));

    assert!(
        has_ability_action,
        "Should have UseAbility action for Test Buffer's activated ability"
    );
}

#[test]
fn test_card_based_activated_ability_requires_essence() {
    // Test that activated abilities check essence cost
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    // Give enough essence to play the creature
    engine.state.players[0].current_essence = 10;

    // Find and play Test Buffer (9001)
    let hand_idx = engine.state.players[0].hand.iter()
        .position(|c| c.card_id == CardId(9001))
        .expect("Test Buffer should be in hand");
    let action = Action::PlayCard { hand_index: hand_idx as u8, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Buffer");

    // Un-exhaust so ability would normally be available
    if let Some(creature) = engine.state.players[0].get_creature_mut(Slot(0)) {
        creature.status.set_exhausted(false);
    }

    // Set essence to 1 - below the 2 cost for the ability
    engine.state.players[0].current_essence = 1;

    // Check legal actions - should NOT have UseAbility since we need 2 essence
    let legal = engine.get_legal_actions();
    let has_ability_action = legal.iter().any(|a| matches!(a, Action::UseAbility { slot, .. } if *slot == Slot(0)));

    assert!(
        !has_ability_action,
        "Should NOT have UseAbility when essence is insufficient"
    );
}

#[test]
fn test_card_based_activated_ability_exhausts_creature() {
    // Test that using an activated ability exhausts the creature
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    // Give enough essence
    engine.state.players[0].current_essence = 10;

    // Find and play Test Buffer (9001)
    let hand_idx = engine.state.players[0].hand.iter()
        .position(|c| c.card_id == CardId(9001))
        .expect("Test Buffer should be in hand");
    let action = Action::PlayCard { hand_index: hand_idx as u8, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Buffer");

    // Un-exhaust the creature so it can use ability
    if let Some(creature) = engine.state.players[0].get_creature_mut(Slot(0)) {
        creature.status.set_exhausted(false);
    }

    // Check that ability action is available
    let legal = engine.get_legal_actions();
    let ability_action = legal.iter().find(|a| matches!(a, Action::UseAbility { slot, .. } if *slot == Slot(0)));
    assert!(ability_action.is_some(), "Should have ability action before use");

    // Use the ability
    engine.apply_action(ability_action.unwrap().clone()).expect("Should use ability");

    // Verify creature is now exhausted
    let creature = engine.state.players[0].get_creature(Slot(0)).expect("Creature should exist");
    assert!(
        creature.status.is_exhausted(),
        "Creature should be exhausted after using activated ability"
    );
}

#[test]
fn test_card_based_activated_ability_deducts_essence() {
    // Test that using an activated ability deducts essence
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    // Give enough essence
    engine.state.players[0].current_essence = 10;

    // Find and play Test Buffer (9001)
    let hand_idx = engine.state.players[0].hand.iter()
        .position(|c| c.card_id == CardId(9001))
        .expect("Test Buffer should be in hand");
    let action = Action::PlayCard { hand_index: hand_idx as u8, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Buffer");

    // Set essence to known amount after play
    engine.state.players[0].current_essence = 5;
    let essence_before = engine.state.players[0].current_essence;

    // Make creature ready
    if let Some(creature) = engine.state.players[0].get_creature_mut(Slot(0)) {
        creature.status.set_exhausted(false);
    }

    // Use the ability (costs 2 essence)
    let legal = engine.get_legal_actions();
    let ability_action = legal.iter().find(|a| matches!(a, Action::UseAbility { slot, .. } if *slot == Slot(0)));
    assert!(ability_action.is_some(), "Should have ability action");
    engine.apply_action(ability_action.unwrap().clone()).expect("Should use ability");

    // Verify essence was deducted
    let essence_after = engine.state.players[0].current_essence;
    assert_eq!(
        essence_after,
        essence_before - 2,
        "Essence should be deducted by ability cost (2)"
    );
}

#[test]
fn test_card_based_activated_ability_cannot_use_when_exhausted() {
    // Test that exhausted creatures cannot use abilities
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    engine.state.players[0].current_essence = 10;

    // Play Test Healer
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Healer");

    // Creature starts exhausted (summoning sickness), so should not have ability
    let legal = engine.get_legal_actions();
    let has_ability_for_slot_0 = legal.iter().any(|a| {
        matches!(a, Action::UseAbility { slot, .. } if *slot == Slot(0))
    });

    assert!(
        !has_ability_for_slot_0,
        "Exhausted creature should NOT have UseAbility action"
    );
}

#[test]
fn test_card_based_activated_ability_silenced_creature_cannot_use() {
    // Test that silenced creatures cannot use abilities
    let card_db = create_test_db_with_activated_ability();
    let mut engine = GameEngine::new(&card_db);

    setup_test_game_with_activated_abilities(&mut engine, 42);

    engine.state.players[0].current_essence = 10;

    // Play Test Healer
    let action = Action::PlayCard { hand_index: 0, slot: Slot(0) };
    engine.apply_action(action).expect("Should play Test Healer");

    // Make creature ready but silenced
    if let Some(creature) = engine.state.players[0].get_creature_mut(Slot(0)) {
        creature.status.set_exhausted(false);
        creature.status.set_silenced(true);
    }

    // Check that no ability actions are available for this creature
    let legal = engine.get_legal_actions();
    let has_ability_for_slot_0 = legal.iter().any(|a| {
        matches!(a, Action::UseAbility { slot, .. } if *slot == Slot(0))
    });

    assert!(
        !has_ability_for_slot_0,
        "Silenced creature should NOT have UseAbility action"
    );
}

// =============================================================================
// CARD-BASED ABILITY TARGET RESOLUTION TESTS
// =============================================================================

#[test]
fn test_hemomancer_activated_ability_damages_enemy_and_heals_self() {
    // Hemomancer (3004): 4/4 Lifesteal
    // Activated ability (2 essence): TargetEnemyPlayer -> Deal 2 damage, Heal 2
    // This tests that card-based abilities with TargetEnemyPlayer resolve correctly
    // (regression test for Target::NoTarget -> EffectTarget::None bug)
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    // Use any commanders, fill decks with Hemomancer (3004, cost 4)
    let hemomancer_id = CardId(3004);
    let deck1: Vec<CardId> = vec![hemomancer_id; 30];
    let deck2: Vec<CardId> = vec![CardId(1000); 30]; // Brass Sentinel filler

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5001), // any commander
        CardId(5002), // any commander
        42,
        GameMode::default(),
    ).unwrap();

    // Advance to turn 4 so P1 has 4 essence (enough for Hemomancer cost 4)
    for _ in 0..6 {
        engine.apply_action(Action::EndTurn).expect("end turn");
    }
    // Now it's turn 4, P1's turn, 4 essence

    // Play Hemomancer to slot 0
    engine.apply_action(Action::PlayCard {
        hand_index: 0,
        slot: Slot(0),
    }).expect("play Hemomancer");

    // End turn to clear summoning sickness, then P2 ends, back to P1
    engine.apply_action(Action::EndTurn).expect("P1 end turn");
    engine.apply_action(Action::EndTurn).expect("P2 end turn");
    // Now turn 5, P1 has 5 essence, Hemomancer is ready

    // Damage P1 first so we can verify heal works (and doesn't exceed max)
    engine.state.players[0].life = 25;

    let p1_life_before = engine.state.players[0].life;
    let p2_life_before = engine.state.players[1].life;
    let p1_extraction_before = engine.state.players[0].total_damage_dealt;

    // Find and use Hemomancer's activated ability (UseAbility slot:0, ability:0, target:NoTarget)
    let legal = engine.get_legal_actions();
    let ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, ability_index, target }
            if *slot == Slot(0) && *ability_index == 0 && matches!(target, Target::NoTarget))
    });

    assert!(ability_action.is_some(), "Hemomancer should have UseAbility action with NoTarget");
    engine.apply_action(ability_action.unwrap().clone()).expect("Hemomancer ability should work");

    // Verify: P2 took 2 damage
    assert_eq!(
        engine.state.players[1].life,
        p2_life_before - 2,
        "Enemy should take 2 damage from Hemomancer ability"
    );

    // Verify: P1 healed 2
    assert_eq!(
        engine.state.players[0].life,
        p1_life_before + 2,
        "Caster should heal 2 from Hemomancer ability"
    );

    // Verify: Extraction counter increased
    assert_eq!(
        engine.state.players[0].total_damage_dealt,
        p1_extraction_before + 2,
        "Face damage should count toward extraction"
    );
}

#[test]
fn test_player_heal_capped_at_max_life() {
    // Verify that healing a player via ability doesn't exceed max life (30)
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    let hemomancer_id = CardId(3004);
    let deck1: Vec<CardId> = vec![hemomancer_id; 30];
    let deck2: Vec<CardId> = vec![CardId(1000); 30];

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5001),
        CardId(5002),
        42,
        GameMode::default(),
    ).unwrap();

    // Advance to turn 5 so P1 has enough essence
    for _ in 0..8 {
        engine.apply_action(Action::EndTurn).expect("end turn");
    }

    // Play Hemomancer
    engine.apply_action(Action::PlayCard {
        hand_index: 0,
        slot: Slot(0),
    }).expect("play Hemomancer");

    // Clear summoning sickness
    engine.apply_action(Action::EndTurn).expect("P1 end turn");
    engine.apply_action(Action::EndTurn).expect("P2 end turn");

    // Set P1 life to max (30) - heal should not exceed
    engine.state.players[0].life = 30;

    let legal = engine.get_legal_actions();
    let ability_action = legal.iter().find(|a| {
        matches!(a, Action::UseAbility { slot, ability_index, .. }
            if *slot == Slot(0) && *ability_index == 0)
    });

    assert!(ability_action.is_some(), "Should have ability action");
    engine.apply_action(ability_action.unwrap().clone()).expect("ability should work");

    assert_eq!(
        engine.state.players[0].life, 30,
        "Life should be capped at 30 after healing at full health"
    );
}

// =============================================================================
// ON-PLAY SINGLE TARGET TESTS
// =============================================================================

#[test]
fn test_on_play_target_enemy_creature_hits_single_target_same_lane() {
    // Void Devourer (3021): OnPlay TargetEnemyCreature -> Deal 4 damage
    // Should hit ONE creature (same lane priority), NOT all enemy creatures.
    // Regression test for AoE bug where TargetEnemyCreature resolved to AllEnemyCreatures.
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    // Fill decks: P1 gets Void Devourer (3021, cost 5), P2 gets Brass Sentinels (1000, cost 2)
    let void_devourer_id = CardId(3021);
    let brass_sentinel_id = CardId(1000);
    let deck1: Vec<CardId> = vec![void_devourer_id; 30];
    let deck2: Vec<CardId> = vec![brass_sentinel_id; 30];

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5001),
        CardId(5002),
        42,
        GameMode::default(),
    ).unwrap();

    // Directly place P2 creatures on the board for a controlled test
    use cardgame::state::Creature;
    let brass_def = card_db.get(brass_sentinel_id).unwrap();
    let (atk, hp) = match &brass_def.card_type {
        CardType::Creature { attack, health, .. } => (*attack, *health),
        _ => panic!("Expected creature"),
    };
    for &slot_idx in &[0u8, 2, 4] {
        let creature = Creature {
            instance_id: engine.state.next_creature_instance_id(),
            card_id: brass_sentinel_id,
            owner: cardgame::PlayerId::PLAYER_TWO,
            slot: Slot(slot_idx),
            attack: atk as i8,
            current_health: hp as i8,
            max_health: hp as i8,
            base_attack: atk,
            base_health: hp,
            keywords: brass_def.keywords(),
            status: CreatureStatus::default(),
            turn_played: 0,
            frenzy_stacks: 0,
            token_data: None,
        };
        engine.state.players[1].creatures.push(creature);
    }

    // Verify P2 has 3 creatures
    let p2_creature_count = engine.state.players[1].creatures.len();
    assert_eq!(p2_creature_count, 3, "P2 should have 3 creatures");

    // Advance to turn where P1 has 5+ essence (turn 5 = 5 essence)
    for _ in 0..8 {
        engine.apply_action(Action::EndTurn).expect("end turn");
    }
    // Now P1's turn with 5 essence

    // Play Void Devourer to slot 2 (same lane as P2's creature in slot 2)
    engine.apply_action(Action::PlayCard { hand_index: 0, slot: Slot(2) }).expect("P1 play Void Devourer");

    // Count surviving P2 creatures
    let p2_survivors = engine.state.players[1].creatures.len();

    // With single-target: only 1 creature should be hit (slot 2, same lane)
    // Brass Sentinel has 5 HP, Void Devourer OnPlay deals 4 damage → survives at 1 HP
    // So all 3 creatures should survive, but one should be damaged
    assert_eq!(
        p2_survivors, 3,
        "OnPlay should hit only 1 creature (single target), all 3 should survive with 5HP vs 4dmg"
    );

    // The creature in slot 2 (same lane) should have taken 4 damage
    let slot2_creature = engine.state.players[1].get_creature(Slot(2))
        .expect("P2 should still have creature in slot 2");
    assert_eq!(
        slot2_creature.current_health, 1,
        "Creature in same lane should have taken 4 damage (5 -> 1)"
    );

    // Creatures in other slots should be undamaged
    let slot0_creature = engine.state.players[1].get_creature(Slot(0))
        .expect("P2 should still have creature in slot 0");
    assert_eq!(
        slot0_creature.current_health, slot0_creature.max_health,
        "Creature in different lane should be undamaged"
    );

    let slot4_creature = engine.state.players[1].get_creature(Slot(4))
        .expect("P2 should still have creature in slot 4");
    assert_eq!(
        slot4_creature.current_health, slot4_creature.max_health,
        "Creature in different lane should be undamaged"
    );
}

#[test]
fn test_on_play_target_enemy_creature_picks_nearest_when_same_lane_empty() {
    // When no creature in the same lane, OnPlay should target the nearest enemy creature.
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    let void_devourer_id = CardId(3021);
    let brass_sentinel_id = CardId(1000);
    let deck1: Vec<CardId> = vec![void_devourer_id; 30];
    let deck2: Vec<CardId> = vec![brass_sentinel_id; 30];

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5001),
        CardId(5002),
        42,
        GameMode::default(),
    ).unwrap();

    // Place P2 creatures in slots 0 and 4 only (not slot 2)
    let brass_def = card_db.get(brass_sentinel_id).unwrap();
    let (atk, hp) = match &brass_def.card_type {
        CardType::Creature { attack, health, .. } => (*attack, *health),
        _ => panic!("Expected creature"),
    };
    for &slot_idx in &[0u8, 4] {
        let creature = cardgame::state::Creature {
            instance_id: engine.state.next_creature_instance_id(),
            card_id: brass_sentinel_id,
            owner: cardgame::PlayerId::PLAYER_TWO,
            slot: Slot(slot_idx),
            attack: atk as i8,
            current_health: hp as i8,
            max_health: hp as i8,
            base_attack: atk,
            base_health: hp,
            keywords: brass_def.keywords(),
            status: CreatureStatus::default(),
            turn_played: 0,
            frenzy_stacks: 0,
            token_data: None,
        };
        engine.state.players[1].creatures.push(creature);
    }

    // Skip turns for essence (need 5 for Void Devourer)
    for _ in 0..8 {
        engine.apply_action(Action::EndTurn).expect("end turn");
    }

    let p2_slot0_hp_before = engine.state.players[1].get_creature(Slot(0))
        .expect("slot 0").current_health;
    let p2_slot4_hp_before = engine.state.players[1].get_creature(Slot(4))
        .expect("slot 4").current_health;

    // Play Void Devourer to slot 2 — no enemy in slot 2, nearest are slot 0 (dist 2) and slot 4 (dist 2)
    engine.apply_action(Action::PlayCard { hand_index: 0, slot: Slot(2) }).expect("P1 play Void Devourer");

    // Exactly one creature should have taken damage
    let p2_slot0_hp_after = engine.state.players[1].get_creature(Slot(0))
        .map(|c| c.current_health).unwrap_or(0);
    let p2_slot4_hp_after = engine.state.players[1].get_creature(Slot(4))
        .map(|c| c.current_health).unwrap_or(0);

    let slot0_damaged = p2_slot0_hp_after < p2_slot0_hp_before;
    let slot4_damaged = p2_slot4_hp_after < p2_slot4_hp_before;

    assert!(
        slot0_damaged ^ slot4_damaged,
        "Exactly one creature should be damaged (single target), got slot0_damaged={}, slot4_damaged={}",
        slot0_damaged, slot4_damaged
    );
}

#[test]
fn test_on_play_fizzles_with_no_valid_target() {
    // OnPlay TargetEnemyCreature should fizzle (do nothing) if no enemy creatures exist.
    let card_db = load_test_db();
    let mut engine = GameEngine::new(&card_db);

    let void_devourer_id = CardId(3021);
    let brass_sentinel_id = CardId(1000);
    let deck1: Vec<CardId> = vec![void_devourer_id; 30];
    let deck2: Vec<CardId> = vec![brass_sentinel_id; 30];

    engine.start_game_raw(
        deck1,
        deck2,
        CardId(5001),
        CardId(5002),
        42,
        GameMode::default(),
    ).unwrap();

    // Skip turns until P1 has 5 essence, P2 plays no creatures
    for _ in 0..8 {
        engine.apply_action(Action::EndTurn).expect("end turn");
    }

    // Verify P2 has no creatures
    assert!(engine.state.players[1].creatures.is_empty(), "P2 should have no creatures");

    let p2_life_before = engine.state.players[1].life;

    // Play Void Devourer — OnPlay should fizzle (no target)
    engine.apply_action(Action::PlayCard { hand_index: 0, slot: Slot(0) }).expect("P1 play Void Devourer");

    // P2 life should be unchanged (OnPlay targets creatures, not face)
    assert_eq!(
        engine.state.players[1].life, p2_life_before,
        "OnPlay should fizzle with no valid target, not deal damage to face"
    );
}
