//! Unit tests for the Flanking mechanic.
//!
//! Flanking: when a creature with the FLANKING status bit attacks,
//! and an ally occupies an adjacent slot, deal +1 bonus damage.
//! Suppressed by Silence.

use cardgame::cards::{CardDatabase, CardDefinition, CardType};
use cardgame::combat::resolve_combat;
use cardgame::engine::EffectQueue;
use cardgame::keywords::Keywords;
use cardgame::state::{Creature, CreatureStatus, GameState};
use cardgame::types::{CardId, CreatureInstanceId, PlayerId, Rarity, Slot};

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

fn test_card_db() -> CardDatabase {
    CardDatabase::new(vec![
        // Basic creature (no keywords, no flanking)
        CardDefinition {
            id: 1,
            name: "Basic".to_string(),
            cost: 2,
            card_type: CardType::Creature {
                attack: 3,
                health: 4,
                keywords: vec![],
                abilities: vec![],
                keywords_bits: Keywords::none(),
                level_up: None,
            },
            rarity: Rarity::Common,
            overload_cost: None,
                flanking: false,
            tags: vec![],
        },
        // Flanking creature
        CardDefinition {
            id: 2,
            name: "Flanker".to_string(),
            cost: 3,
            card_type: CardType::Creature {
                attack: 2,
                health: 4,
                keywords: vec![],
                abilities: vec![],
                keywords_bits: Keywords::none(),
                level_up: None,
            },
            rarity: Rarity::Uncommon,
            overload_cost: None,
            flanking: true,
            tags: vec![],
        },
    ])
}

fn next_id() -> CreatureInstanceId {
    use std::sync::atomic::{AtomicU32, Ordering};
    static CTR: AtomicU32 = AtomicU32::new(1);
    CreatureInstanceId(CTR.fetch_add(1, Ordering::Relaxed))
}

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

fn make_flanking_creature(slot: u8, attack: i8, health: i8, player: PlayerId) -> Creature {
    let mut c = make_creature(2, slot, attack, health, player);
    c.status.set_flanking(true);
    c
}

fn make_state_with_creatures(
    attacker: Creature,
    defenders: Vec<Creature>,
    allies: Vec<Creature>,
) -> GameState {
    let mut state = GameState::new();
    state.players[0].life = 30;
    state.players[1].life = 30;
    state.current_turn = 2; // Turn 2: no summoning sickness
    state.players[0].creatures.push(attacker);
    for ally in allies {
        state.players[0].creatures.push(ally);
    }
    for def in defenders {
        state.players[1].creatures.push(def);
    }
    state
}

// =============================================================================
// Tests
// =============================================================================

/// Flanking creature attacking with NO adjacent ally — no bonus.
#[test]
fn flanking_no_bonus_when_alone() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_flanking_creature(2, 2, 4, PlayerId(0));
    let defender = make_creature(1, 2, 3, 3, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    // Attacker has 2 attack, no adjacent ally — deals exactly 2 damage.
    // Defender has 3 health → survives (3 - 2 = 1 health left).
    assert_eq!(result.attacker_damage_dealt, 2, "No flanking bonus: should deal base 2 damage");
    assert!(!result.defender_died, "Defender should survive (3hp - 2 dmg = 1)");
}

/// Flanking creature attacking WITH an adjacent ally — deals +1 bonus damage.
#[test]
fn flanking_bonus_with_adjacent_ally() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_flanking_creature(2, 2, 4, PlayerId(0)); // slot 2
    let ally = make_creature(1, 3, 2, 3, PlayerId(0));           // slot 3 (adjacent to 2)
    let defender = make_creature(1, 2, 3, 3, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    // Attacker has 2 attack + 1 flanking bonus = 3 damage.
    // Defender has 3 health → dies (3 - 3 = 0).
    assert_eq!(result.attacker_damage_dealt, 3, "Flanking bonus: should deal 3 damage (2 + 1)");
    assert!(result.defender_died, "Defender should die (3hp - 3 dmg)");
}

/// +1 per adjacent ally: two adjacent allies give +2 total (middle slot).
#[test]
fn flanking_bonus_stacks_per_adjacent_ally() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_flanking_creature(2, 2, 4, PlayerId(0)); // slot 2, 2 ATK
    let ally_left = make_creature(1, 1, 2, 3, PlayerId(0));      // slot 1
    let ally_right = make_creature(1, 3, 2, 3, PlayerId(0));     // slot 3
    let defender = make_creature(1, 2, 4, 5, PlayerId(1));       // 5 health

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally_left, ally_right]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    // Two adjacent allies → +2 flanking bonus → 2 + 2 = 4 damage.
    assert_eq!(result.attacker_damage_dealt, 4, "Flanking gives +2 with two adjacent allies");
    assert!(!result.defender_died, "Defender (5hp) survives 4 damage");
}

/// Flanking is suppressed when the attacker is Silenced.
#[test]
fn flanking_suppressed_by_silence() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let mut attacker = make_flanking_creature(2, 2, 4, PlayerId(0));
    attacker.status.set_silenced(true); // Silence suppresses Flanking

    let ally = make_creature(1, 3, 2, 3, PlayerId(0)); // adjacent ally present
    let defender = make_creature(1, 2, 3, 3, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    // Silenced: no flanking bonus, deals only base 2 damage.
    assert_eq!(result.attacker_damage_dealt, 2, "Silenced flanker should deal base damage only");
    assert!(!result.defender_died, "Defender (3hp) survives 2 damage when flanking suppressed");
}

/// Exhausted adjacent ally still counts for flanking (presence, not readiness).
#[test]
fn flanking_counts_exhausted_ally() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_flanking_creature(2, 2, 4, PlayerId(0));
    let mut ally = make_creature(1, 3, 2, 3, PlayerId(0));
    ally.status.set_exhausted(true); // Ally is exhausted but still present

    let defender = make_creature(1, 2, 3, 3, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    // Exhausted ally still grants flanking bonus.
    assert_eq!(result.attacker_damage_dealt, 3, "Exhausted ally still enables flanking bonus");
    assert!(result.defender_died, "Defender (3hp) dies to 3 damage");
}

/// Non-adjacent ally does NOT grant flanking bonus.
#[test]
fn flanking_no_bonus_for_non_adjacent_ally() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_flanking_creature(0, 2, 4, PlayerId(0)); // slot 0
    let ally = make_creature(1, 4, 2, 3, PlayerId(0));           // slot 4 (not adjacent to 0)
    let defender = make_creature(1, 0, 3, 3, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(0),
        Slot(0),
        None,
    );

    // Slot 0 is adjacent to [0, 1] only. Slot 4 is not adjacent.
    assert_eq!(result.attacker_damage_dealt, 2, "Non-adjacent ally does not grant flanking bonus");
}

/// A non-flanking creature gets no bonus even with adjacent allies.
#[test]
fn no_flanking_bonus_for_non_flanking_creature() {
    let db = test_card_db();
    let mut queue = EffectQueue::new();

    let attacker = make_creature(1, 2, 3, 4, PlayerId(0)); // no flanking bit
    let ally = make_creature(1, 3, 2, 3, PlayerId(0));
    let defender = make_creature(1, 2, 5, 5, PlayerId(1));

    let mut state = make_state_with_creatures(attacker, vec![defender], vec![ally]);

    let result = resolve_combat(
        &mut state,
        &db,
        &mut queue,
        PlayerId(0),
        Slot(2),
        Slot(2),
        None,
    );

    assert_eq!(result.attacker_damage_dealt, 3, "No flanking flag: base damage only");
}

/// CreatureStatus bit checks work correctly in isolation.
#[test]
fn creature_status_flanking_bit() {
    let mut status = CreatureStatus::default();
    assert!(!status.has_flanking(), "Default status has no flanking");

    status.set_flanking(true);
    assert!(status.has_flanking(), "Flanking set to true");
    assert!(!status.is_exhausted(), "Flanking does not affect exhausted");
    assert!(!status.is_silenced(), "Flanking does not affect silenced");

    status.set_flanking(false);
    assert!(!status.has_flanking(), "Flanking cleared");
}

/// YAML-loaded flanking card has the flanking bit set when played.
#[test]
fn flanking_card_loaded_from_real_db_has_flanking() {
    use cardgame::embedded_data::load_embedded_cards;

    let db = load_embedded_cards().expect("should load embedded card db");

    // Phalanx Vanguard (id 1078) — Argentum flanking card
    if let Some(card) = db.get(CardId(1078)) {
        assert!(card.has_flanking(), "Phalanx Vanguard should have flanking=true");
    }

    // Packrunner (id 2077) — Symbiote flanking card
    if let Some(card) = db.get(CardId(2077)) {
        assert!(card.has_flanking(), "Packrunner should have flanking=true");
    }

    // Basic Brass Sentinel (id 1000) — no flanking
    if let Some(card) = db.get(CardId(1000)) {
        assert!(!card.has_flanking(), "Brass Sentinel should NOT have flanking");
    }
}
