//! Parity tests: death triggers must fire identically for combat kills vs spell kills.
//!
//! The engine has two independent death-processing paths:
//!   1. Combat path:  `resolve_combat` → `process_creature_death` (src/core/combat/death.rs)
//!   2. Effect path:  `EffectQueue::process_all` → `process_deaths` (src/core/engine/triggers.rs)
//!
//! Any keyword or trigger implemented in only one path is a latent bug.
//! These tests explicitly verify that every trigger fires (or does NOT fire) the same
//! way in both paths, and serve as a regression harness against future path divergence.
//!
//! Covered triggers:
//! - Volatile   – 2 damage to all enemy creatures on death
//! - OnDeath    – creature's own death effect
//! - OnAllyDeath – surviving ally reacts to a friendly death

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
// ============================================================================

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![],
    }
}

/// Card with OnDeath: GainEssence 2 (observable, avoids deck dependency)
fn make_on_death_gain_essence_card(id: u16, attack: u8, health: u8) -> CardDefinition {
    CardDefinition {
        id,
        name: format!("DeathEssence {id}"),
        cost: 2,
        overload_cost: None,
        card_type: CardType::Creature {
            attack,
            health,
            keywords: vec![],
            keywords_bits: Keywords::none(),
            abilities: vec![AbilityDefinition {
                trigger: Trigger::OnDeath,
                targeting: TargetingRule::NoTarget,
                essence_cost: 0,
                effects: vec![EffectDefinition::GainEssence { amount: 2 }],
                conditional_effects: vec![],
            }],
            level_up: None,
        },
        rarity: Rarity::Uncommon,
        flanking: false,
        tags: vec![],
    }
}

/// Card with OnAllyDeath: GainEssence 1 (observer fires when an ally dies)
fn make_on_ally_death_card(id: u16, attack: u8, health: u8) -> CardDefinition {
    CardDefinition {
        id,
        name: format!("AllyMourner {id}"),
        cost: 2,
        overload_cost: None,
        card_type: CardType::Creature {
            attack,
            health,
            keywords: vec![],
            keywords_bits: Keywords::none(),
            abilities: vec![AbilityDefinition {
                trigger: Trigger::OnAllyDeath,
                targeting: TargetingRule::NoTarget,
                essence_cost: 0,
                effects: vec![EffectDefinition::GainEssence { amount: 1 }],
                conditional_effects: vec![],
            }],
            level_up: None,
        },
        rarity: Rarity::Uncommon,
        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 both players starting essence so GainEssence effects are observable
    state.players[0].max_essence = 5;
    state.players[0].current_essence = 2;
    state.players[1].max_essence = 5;
    state.players[1].current_essence = 2;
    state
}

// ============================================================================
// VOLATILE PARITY
// ============================================================================

/// Volatile fires on COMBAT death — baseline for parity comparison.
#[test]
fn volatile_parity_fires_on_combat_death() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 3, 3), // attacker: kills the volatile
        make_vanilla_card(2, 2, 2), // volatile creature (no card ability needed, keyword in instance)
        make_vanilla_card(3, 1, 3), // P1 bystander: will absorb Volatile damage
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // P1: big attacker 3/3
    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 3, Keywords::none()));
    // P1: bystander 1/3
    state.players[0].creatures.push(make_creature(3, 3, PlayerId::PLAYER_ONE, 1, 1, 3, Keywords::none()));
    // P2: Volatile 2/2
    state.players[1].creatures.push(make_creature(
        2, 2, PlayerId::PLAYER_TWO, 0, 2, 2, Keywords::none().with_volatile(),
    ));

    // P1 attacks P2's Volatile — both die (3 damage to attacker, attacker deals 3, kills 2/2)
    // Attacker: 3 - 2 = 1 health (survives). Volatile creature dies.
    resolve_combat(&mut state, &db, &mut queue, PlayerId::PLAYER_ONE, Slot(0), Slot(0), None);
    queue.process_all(&mut state, &db);

    // P1 bystander should have taken 2 Volatile damage (3 - 2 = 1)
    let bystander = state.players[0].get_creature(Slot(1)).expect("bystander survives");
    assert_eq!(bystander.current_health, 1, "Volatile COMBAT path: bystander takes 2 damage");
}

/// Volatile fires on SPELL death — must match combat path behavior.
#[test]
fn volatile_parity_fires_on_spell_death() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(2, 2, 2),
        make_vanilla_card(3, 1, 3),
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // P1: Volatile 2/2
    state.players[0].creatures.push(make_creature(
        1, 2, PlayerId::PLAYER_ONE, 0, 2, 2, Keywords::none().with_volatile(),
    ));
    // P2: bystander 1/3
    state.players[1].creatures.push(make_creature(2, 3, PlayerId::PLAYER_TWO, 0, 1, 3, Keywords::none()));

    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_ONE, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    // P2 bystander should have taken 2 Volatile damage (3 - 2 = 1)
    let bystander = state.players[1].get_creature(Slot(0)).expect("bystander survives");
    assert_eq!(bystander.current_health, 1, "Volatile SPELL path: bystander takes 2 damage");
}

// ============================================================================
// ONDEATH TRIGGER PARITY
// ============================================================================

/// OnDeath GainEssence fires on COMBAT death — baseline.
#[test]
fn on_death_parity_fires_on_combat_death() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 3, 3),               // killer
        make_on_death_gain_essence_card(2, 1, 1), // victim with OnDeath
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // P2's creature (OnDeath: GainEssence 2 for P2)
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));
    // P1's attacker kills it
    state.players[0].creatures.push(make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 3, 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[1].get_creature(Slot(0)).is_none(), "victim should be dead");
    assert_eq!(
        state.players[1].current_essence,
        p2_essence_before + 2,
        "OnDeath COMBAT path: P2 should gain 2 essence"
    );
}

/// OnDeath GainEssence fires on SPELL death — must match combat path behavior.
#[test]
fn on_death_parity_fires_on_spell_death() {
    let db = CardDatabase::new(vec![
        make_on_death_gain_essence_card(1, 1, 1), // victim with OnDeath
    ]);
    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()));
    let p1_essence_before = state.players[0].current_essence;

    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_ONE, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    assert!(state.players[0].get_creature(Slot(0)).is_none(), "victim should be dead");
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 2,
        "OnDeath SPELL path: P1 should gain 2 essence"
    );
}

/// OnDeath does NOT fire when creature is silenced (combat).
#[test]
fn on_death_parity_silenced_no_fire_combat() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 3, 3),
        make_on_death_gain_essence_card(2, 1, 1),
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

    let mut victim = make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none());
    victim.status.set_silenced(true);

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

    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_eq!(
        state.players[1].current_essence, p2_essence_before,
        "silenced OnDeath COMBAT: no essence gain"
    );
}

/// OnDeath does NOT fire when creature is silenced (spell).
#[test]
fn on_death_parity_silenced_no_fire_spell() {
    let db = CardDatabase::new(vec![
        make_on_death_gain_essence_card(1, 1, 1),
    ]);
    let mut state = test_state();
    let mut queue = EffectQueue::new();

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

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

    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_ONE, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    assert_eq!(
        state.players[0].current_essence, p1_essence_before,
        "silenced OnDeath SPELL: no essence gain"
    );
}

// ============================================================================
// ONALLYDEATH TRIGGER PARITY
// ============================================================================

/// OnAllyDeath GainEssence fires when an ally dies in COMBAT.
#[test]
fn on_ally_death_parity_fires_on_combat_death() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 3, 3),             // P1 attacker
        make_vanilla_card(2, 1, 1),             // P2 fodder that will die
        make_on_ally_death_card(3, 1, 3),       // P2 observer with OnAllyDeath
    ]);
    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()));
    // P2 fodder
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));
    // P2 observer
    state.players[1].creatures.push(make_creature(3, 3, PlayerId::PLAYER_TWO, 1, 1, 3, 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[1].get_creature(Slot(0)).is_none(), "fodder should be dead");
    assert_eq!(
        state.players[1].current_essence,
        p2_essence_before + 1,
        "OnAllyDeath COMBAT path: P2 observer gains 1 essence"
    );
}

/// OnAllyDeath GainEssence fires when an ally dies via SPELL.
#[test]
fn on_ally_death_parity_fires_on_spell_death() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 1, 1),             // P1 fodder that will be spell-killed
        make_on_ally_death_card(2, 1, 3),       // P1 observer with OnAllyDeath
    ]);
    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[0].creatures.push(make_creature(2, 2, PlayerId::PLAYER_ONE, 1, 1, 3, Keywords::none()));

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

    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_ONE, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    assert!(state.players[0].get_creature(Slot(0)).is_none(), "fodder dead");
    assert_eq!(
        state.players[0].current_essence,
        p1_essence_before + 1,
        "OnAllyDeath SPELL path: P1 observer gains 1 essence"
    );
}

/// OnAllyDeath does NOT fire for silenced observer (spell path).
#[test]
fn on_ally_death_parity_silenced_observer_no_fire_spell() {
    let db = CardDatabase::new(vec![
        make_vanilla_card(1, 1, 1),
        make_on_ally_death_card(2, 1, 3),
    ]);
    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()));
    let mut observer = make_creature(2, 2, PlayerId::PLAYER_ONE, 1, 1, 3, Keywords::none());
    observer.status.set_silenced(true);
    state.players[0].creatures.push(observer);

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

    queue.push(
        Effect::Damage {
            target: EffectTarget::Creature { owner: PlayerId::PLAYER_ONE, slot: Slot(0) },
            amount: 10,
            filter: None,
        },
        EffectSource::System,
    );
    queue.process_all(&mut state, &db);

    assert_eq!(
        state.players[0].current_essence, p1_essence_before,
        "silenced OnAllyDeath observer should not trigger"
    );
}
