//! Unit tests for the OnKill trigger.
//!
//! OnKill fires on the ATTACKER when it kills an enemy creature in combat.
//! It is purely a combat trigger and does NOT fire for:
//!   - Spell/ability kills
//!   - Face attacks (attacker targeting enemy player)
//!   - The defender (even if the defender kills the attacker)
//!
//! The trigger calls the attacker's card definition ability with `trigger == Trigger::OnKill`.
//! Silenced creatures do not trigger.

use cardgame::cards::{
    AbilityDefinition, CardDatabase, CardDefinition, CardType, EffectDefinition,
};
use cardgame::combat::resolve_combat;
use cardgame::effects::{Effect, EffectSource, EffectTarget, TargetingRule, Trigger};
use cardgame::engine::EffectQueue;
use cardgame::keywords::Keywords;
use cardgame::state::{Creature, CreatureStatus, GameState};
use cardgame::types::{CardId, CreatureInstanceId, PlayerId, Rarity, Slot};

// ============================================================================
// Helpers
// ============================================================================

/// A card with OnKill: GainEssence {amount} for its owner.
fn make_on_kill_card(id: u16, attack: u8, health: u8, essence_gain: u8) -> CardDefinition {
    CardDefinition {
        id,
        name: format!("KillReward {id}"),
        cost: 3,
        overload_cost: None,
        card_type: CardType::Creature {
            attack,
            health,
            keywords: vec![],
            keywords_bits: Keywords::none(),
            abilities: vec![AbilityDefinition {
                trigger: Trigger::OnKill,
                targeting: TargetingRule::NoTarget,
                essence_cost: 0,
                effects: vec![EffectDefinition::GainEssence { amount: essence_gain }],
                conditional_effects: vec![],
            }],
            level_up: None,
        },
        rarity: Rarity::Uncommon,
        flanking: false,
        tags: vec![],
    }
}

fn make_vanilla_card(id: u16, attack: u8, health: u8) -> CardDefinition {
    CardDefinition {
        id,
        name: format!("Vanilla {id}"),
        cost: 2,
        overload_cost: None,
        card_type: CardType::Creature {
            attack,
            health,
            keywords: vec![],
            keywords_bits: Keywords::none(),
            abilities: vec![],
            level_up: None,
        },
        rarity: Rarity::Common,
        flanking: false,
        tags: vec![],
    }
}

fn make_creature(
    instance_id: u32,
    card_id: u16,
    owner: PlayerId,
    slot: u8,
    attack: i8,
    health: i8,
    keywords: Keywords,
) -> Creature {
    Creature {
        instance_id: CreatureInstanceId(instance_id),
        card_id: CardId(card_id),
        owner,
        slot: Slot(slot),
        attack,
        current_health: health,
        max_health: health,
        base_attack: attack as u8,
        base_health: health as u8,
        keywords,
        status: CreatureStatus::default(),
        turn_played: 1,
        frenzy_stacks: 0,
        temp_attack_bonus: 0,
        level: 0,
        progress: 0,
        token_data: None,
    }
}

fn test_state() -> GameState {
    let mut state = GameState::new();
    state.players[0].life = 30;
    state.players[1].life = 30;
    state.current_turn = 2;
    // Give players observable starting essence
    state.players[0].max_essence = 10;
    state.players[0].current_essence = 3;
    state.players[1].max_essence = 10;
    state.players[1].current_essence = 3;
    state
}

// ============================================================================
// Fires on kill (attacker survives)
// ============================================================================

/// Attacker with OnKill kills defender and survives → gains essence.
#[test]
fn on_kill_fires_when_attacker_kills_and_survives() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 4, 2), // attacker: OnKill GainEssence 2
        make_vanilla_card(2, 1, 1),    // defender fodder
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 4, Keywords::none()));
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_none(), "defender should be dead");
    assert!(state.players[0].get_creature(Slot(0)).is_some(), "attacker should survive");
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnKill should trigger GainEssence 2"
    );
}

/// Defender does NOT get OnKill even when it kills the attacker.
#[test]
fn on_kill_does_not_fire_for_defender() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 1, 1),    // attacker: weak, will die
        make_on_kill_card(2, 5, 5, 2), // defender with OnKill (must NOT fire)
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

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

    let p2_essence_before = state.players[1].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[0].get_creature(Slot(0)).is_none(), "attacker should be dead");
    assert_eq!(
        state.players[1].current_essence, p2_essence_before,
        "OnKill must NOT fire for the defending creature"
    );
}

/// OnKill fires even in a mutual kill (attacker also dies).
#[test]
fn on_kill_fires_in_mutual_kill() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 3, 2), // attacker with OnKill
        make_vanilla_card(2, 3, 3),    // defender: same stats, mutual kill
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 3, Keywords::none()));
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 3, 3, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    // Both should be dead
    assert!(state.players[0].get_creature(Slot(0)).is_none(), "attacker dies in mutual kill");
    assert!(state.players[1].get_creature(Slot(0)).is_none(), "defender dies in mutual kill");
    // OnKill should still fire (the kill happened before attacker is removed)
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnKill fires even when attacker also dies"
    );
}

/// Silenced attacker does NOT get OnKill.
#[test]
fn on_kill_does_not_fire_when_attacker_is_silenced() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 4, 2),
        make_vanilla_card(2, 1, 1),
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    let mut attacker = make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 4, Keywords::none());
    attacker.status.set_silenced(true);
    state.players[0].creatures.push(attacker);
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_none(), "defender dead");
    assert_eq!(
        state.players[0].current_essence, p1_essence_before,
        "silenced attacker must not trigger OnKill"
    );
}

/// OnKill does NOT fire when the attacker does not kill (defender survives).
#[test]
fn on_kill_does_not_fire_when_defender_survives() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 1, 4, 2), // weak attacker, won't kill
        make_vanilla_card(2, 1, 5),    // tanky defender
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 1, 4, Keywords::none()));
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 5, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_some(), "defender survived");
    assert_eq!(
        state.players[0].current_essence, p1_essence_before,
        "OnKill must not fire when defender survived"
    );
}

// ============================================================================
// OnKill does NOT fire for spell kills
// ============================================================================

/// OnKill does NOT fire when a creature is killed by a spell/ability.
/// OnKill is purely a combat trigger — it requires the creature to be the attacker.
#[test]
fn on_kill_does_not_fire_on_spell_kill() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 4, 2), // P1 creature with OnKill
        make_vanilla_card(2, 1, 1),    // P2 target that will be spell-killed
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // P1 creature sits on the board (not attacking)
    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 4, Keywords::none()));
    // P2 target
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    // Kill P2 creature via effect queue (spell path), NOT via combat
    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_TWO, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_none(), "P2 creature dead from spell");
    assert_eq!(
        state.players[0].current_essence, p1_essence_before,
        "OnKill must NOT fire for spell kills"
    );
}

// ============================================================================
// OnKill with Rush and Lethal
// ============================================================================

/// OnKill fires when attacker has Rush (killing on the same turn it was played).
#[test]
fn on_kill_fires_with_rush_attacker() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 3, 2), // OnKill attacker
        make_vanilla_card(2, 1, 1),
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // Rush creature (can attack immediately — turn_played = current_turn)
    let mut attacker = make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 3, Keywords::none().with_rush());
    attacker.turn_played = state.current_turn; // same turn = summoning sickness normally
    state.players[0].creatures.push(attacker);
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnKill fires with Rush attacker"
    );
}

/// OnKill fires when Lethal attacker kills an otherwise-surviving creature.
#[test]
fn on_kill_fires_with_lethal_attacker() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 1, 4, 2), // 1-attack Lethal attacker
        make_vanilla_card(2, 1, 5),    // 1/5 defender — normally survives 1 damage
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // Lethal: any damage is lethal
    state.players[0].creatures.push(make_creature(
        1, 1, PlayerId::PLAYER_ONE, 0, 1, 4, Keywords::none().with_lethal(),
    ));
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 5, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_none(), "Lethal kills the 1/5 defender");
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnKill fires when Lethal attacker kills"
    );
}

/// OnKill fires when attacker kills via Ranged (ranged attacks don't take retaliation).
#[test]
fn on_kill_fires_with_ranged_attacker() {
    let db = CardDatabase::new(vec![
        make_on_kill_card(1, 3, 2, 2), // Ranged attacker
        make_vanilla_card(2, 5, 2),    // 5/2 defender — would kill attacker in melee
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // Ranged: attacker deals damage but takes no retaliation from creature attacks
    state.players[0].creatures.push(make_creature(
        1, 1, PlayerId::PLAYER_ONE, 0, 3, 2, Keywords::none().with_ranged(),
    ));
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 5, 2, Keywords::none()));

    let p1_essence_before = state.players[0].current_essence;

    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    assert!(state.players[1].get_creature(Slot(0)).is_none(), "Ranged kills the 5/2 defender");
    assert!(state.players[0].get_creature(Slot(0)).is_some(), "Ranged attacker takes no retaliation");
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnKill fires with Ranged attacker"
    );
}
