//! Unit tests for the Level-Up creature mechanic.
//!
//! Level-Up: a creature accumulates progress on kills (OnKill) or damage dealt
//! (OnDealDamage). When progress reaches the threshold, it permanently gains
//! attack/health bonuses and optional keywords.

use cardgame::cards::{CardDatabase, CardDefinition, CardType, LevelUpDefinition, LevelUpTrigger};
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 make_level_up_creature_def(
    id: u16,
    attack: u8,
    health: u8,
    trigger: LevelUpTrigger,
    threshold: u8,
    attack_bonus: i8,
    health_bonus: i8,
    keywords_gained_names: &[&str],
) -> CardDefinition {
    let keywords_gained: Vec<String> = keywords_gained_names.iter().map(|s| s.to_string()).collect();
    let keywords_gained_bits = Keywords::from_names(keywords_gained_names);
    CardDefinition {
        id,
        name: format!("LevelUp Creature {}", id),
        cost: 4,
        overload_cost: None,
        card_type: CardType::Creature {
            attack,
            health,
            keywords: vec![],
            keywords_bits: Keywords::none(),
            abilities: vec![],
            level_up: Some(LevelUpDefinition {
                trigger,
                threshold,
                attack_bonus,
                health_bonus,
                keywords_gained,
                keywords_gained_bits,
            }),
        },
        rarity: Rarity::Rare,
        flanking: false,
        tags: vec![],
    }
}

fn make_normal_creature_def(id: u16, attack: u8, health: u8) -> CardDefinition {
    CardDefinition {
        id,
        name: format!("Normal Creature {}", 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, // Last turn — no summoning sickness
        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;
    state
}

// =============================================================================
// Card Definition Tests
// =============================================================================

#[test]
fn level_up_def_accessible_via_helper() {
    let def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 3, 2, 2, &[]);
    assert!(def.level_up().is_some());
    let lu = def.level_up().unwrap();
    assert_eq!(lu.trigger, LevelUpTrigger::OnKill);
    assert_eq!(lu.threshold, 3);
    assert_eq!(lu.attack_bonus, 2);
    assert_eq!(lu.health_bonus, 2);
}

#[test]
fn normal_creature_has_no_level_up() {
    let def = make_normal_creature_def(1, 3, 3);
    assert!(def.level_up().is_none());
}

#[test]
fn level_up_keywords_gained_bits_computed_from_names() {
    let def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 3, 2, 2, &["Rush"]);
    let lu = def.level_up().unwrap();
    assert!(lu.keywords_gained_bits.has_rush(), "keywords_gained_bits should include Rush");
}

// =============================================================================
// Creature Field Defaults
// =============================================================================

#[test]
fn new_creature_has_zero_level_and_progress() {
    let c = make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 3, 4, Keywords::none());
    assert_eq!(c.level, 0);
    assert_eq!(c.progress, 0);
}

// =============================================================================
// YAML Loading
// =============================================================================

#[test]
fn level_up_loads_from_yaml() {
    let yaml = r#"
name: "Level-Up Test Set"
cards:
  - id: 1
    name: "Argentum Juggernaut"
    cost: 4
    card_type: creature
    attack: 3
    health: 4
    rarity: Rare
    level_up:
      trigger: OnKill
      threshold: 3
      attack_bonus: 3
      health_bonus: 2
      keywords_gained:
        - Rush
  - id: 2
    name: "Symbiote Broodmother"
    cost: 5
    card_type: creature
    attack: 2
    health: 5
    rarity: Rare
    level_up:
      trigger: OnDealDamage
      threshold: 6
      attack_bonus: 2
      health_bonus: 3
      keywords_gained:
        - Lifesteal
  - id: 3
    name: "Normal Creature"
    cost: 2
    card_type: creature
    attack: 2
    health: 3
    rarity: Common
"#;
    let db = CardDatabase::load_from_yaml(yaml).expect("Failed to parse YAML");

    let juggernaut = db.get(CardId(1)).expect("Juggernaut not found");
    let lu = juggernaut.level_up().expect("should have level_up");
    assert_eq!(lu.trigger, LevelUpTrigger::OnKill);
    assert_eq!(lu.threshold, 3);
    assert_eq!(lu.attack_bonus, 3);
    assert_eq!(lu.health_bonus, 2);
    assert!(lu.keywords_gained_bits.has_rush());

    let broodmother = db.get(CardId(2)).expect("Broodmother not found");
    let lu2 = broodmother.level_up().expect("should have level_up");
    assert_eq!(lu2.trigger, LevelUpTrigger::OnDealDamage);
    assert_eq!(lu2.threshold, 6);
    assert!(lu2.keywords_gained_bits.has_lifesteal());

    let normal = db.get(CardId(3)).expect("Normal not found");
    assert!(normal.level_up().is_none());
}

#[test]
fn level_up_sample_cards_in_real_db() {
    use cardgame::embedded_data::load_embedded_cards;
    let db = load_embedded_cards().expect("should load embedded card db");

    // Argentum Juggernaut (id 1080)
    if let Some(card) = db.get(CardId(1080)) {
        let lu = card.level_up().expect("Juggernaut should have level_up");
        assert_eq!(lu.trigger, LevelUpTrigger::OnKill);
        assert_eq!(lu.threshold, 3);
        assert!(lu.keywords_gained_bits.has_rush());
    }

    // Symbiote Broodmother (id 2079)
    let broodmother = db.get(CardId(2079)).expect("Card 2079 should exist in DB");
    let lu = broodmother.level_up().expect("Broodmother should have level_up");
    assert_eq!(lu.trigger, LevelUpTrigger::OnDealDamage);
    assert_eq!(lu.threshold, 6);
    assert!(lu.keywords_gained_bits.has_lifesteal());
}

// =============================================================================
// OnKill Progress
// =============================================================================

/// Attacker (3/4, OnKill threshold 3) kills a 1/1 — progress should be 1.
#[test]
fn on_kill_increments_progress_on_kill() {
    let attacker_def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 3, 2, 2, &[]);
    let defender_def = make_normal_creature_def(2, 1, 1);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    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()));

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 0, "not leveled yet");
    assert_eq!(attacker.progress, 1, "progress should be 1 after one kill");
}

/// Attacker that dies in the same combat should NOT gain progress.
#[test]
fn on_kill_no_progress_if_attacker_dies() {
    // Attacker 1/4 vs defender 5/1 — both die
    let attacker_def = make_level_up_creature_def(1, 1, 4, LevelUpTrigger::OnKill, 3, 2, 2, &[]);
    let defender_def = make_normal_creature_def(2, 5, 1);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    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, 5, 1, Keywords::none()));

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

    // Attacker should be dead (removed from board) — no progress possible
    assert!(state.players[0].get_creature(Slot(0)).is_none(), "attacker should be dead");
}

/// Attacker reaches threshold exactly on a kill → should level up immediately.
#[test]
fn on_kill_levels_up_at_threshold() {
    // Threshold 1 — levels up on first kill
    let attacker_def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 1, 2, 2, &[]);
    let defender_def = make_normal_creature_def(2, 1, 1);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    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()));

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 1, "should be leveled up");
    // 3 base + 2 bonus = 5 attack
    assert_eq!(attacker.attack, 5, "attack should be 3+2=5 after level-up");
    // Attacker took 1 damage from the 1/1 defender, leaving it at 3 health, then +2 = 5 current_health
    // max_health = 4 + 2 = 6 (bonus is always added to max)
    assert_eq!(attacker.current_health, 5, "health should be (4-1)+2=5 after combat damage + level-up");
    assert_eq!(attacker.max_health, 6, "max_health should be 4+2=6");
}

/// Level-up with keyword gain — attacker should gain the Rush keyword.
#[test]
fn on_kill_level_up_grants_keywords() {
    let attacker_def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 1, 0, 0, &["Rush"]);
    let defender_def = make_normal_creature_def(2, 1, 1);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    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()));

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 1);
    assert!(attacker.keywords.has_rush(), "leveled creature should have Rush");
}

/// A creature at level 1 should NOT accumulate further progress.
#[test]
fn already_leveled_creature_does_not_progress_further() {
    let attacker_def = make_level_up_creature_def(1, 3, 4, LevelUpTrigger::OnKill, 3, 2, 2, &[]);
    let defender_def = make_normal_creature_def(2, 1, 1);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    let mut state = test_state();
    let mut queue = EffectQueue::new();

    // Attacker already at level 1 with progress 3
    let mut attacker = make_creature(1, 1, PlayerId::PLAYER_ONE, 0, 5, 6, Keywords::none());
    attacker.level = 1;
    attacker.progress = 3;
    state.players[0].creatures.push(attacker);
    state.players[1].creatures.push(make_creature(2, 2, PlayerId::PLAYER_TWO, 0, 1, 1, Keywords::none()));

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 1, "should still be level 1");
    assert_eq!(attacker.progress, 3, "progress should not change when already leveled");
}

// =============================================================================
// OnDealDamage Progress
// =============================================================================

/// Attacker with OnDealDamage (threshold 6) deals 3 damage — progress should be 3.
#[test]
fn on_deal_damage_increments_progress_by_damage_amount() {
    // 3 attack hits a 4-health creature — deals 3 damage, does not kill
    let attacker_def = make_level_up_creature_def(1, 3, 6, LevelUpTrigger::OnDealDamage, 6, 2, 3, &["Lifesteal"]);
    let defender_def = make_normal_creature_def(2, 1, 4); // 1/4 — survives 3 damage
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    let mut state = test_state();
    let mut queue = EffectQueue::new();

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

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 0, "not leveled yet (3 progress < threshold 6)");
    assert_eq!(attacker.progress, 3, "should have 3 progress from 3 damage dealt");
}

/// Attacker's damage reaches or exceeds threshold → levels up.
#[test]
fn on_deal_damage_levels_up_at_threshold() {
    // 6 attack vs a 10-health creature — deals 6 damage (exactly threshold)
    let attacker_def = make_level_up_creature_def(1, 6, 6, LevelUpTrigger::OnDealDamage, 6, 2, 3, &[]);
    let defender_def = make_normal_creature_def(2, 1, 10); // 1/10 — survives 6 damage
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    let mut state = test_state();
    let mut queue = EffectQueue::new();

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

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

    let attacker = state.players[0].get_creature(Slot(0)).expect("attacker should survive");
    assert_eq!(attacker.level, 1, "should be leveled up");
    assert_eq!(attacker.attack, 8, "attack should be 6+2=8");
    // Attacker (6/6) took 1 damage from the 1/10 defender: 6-1=5 health, then +3=8 current
    // max_health = 6+3=9
    assert_eq!(attacker.current_health, 8, "health should be (6-1)+3=8 after combat damage + level-up");
    assert_eq!(attacker.max_health, 9, "max_health should be 6+3=9");
}

/// Attacker that dies while dealing damage should NOT gain progress.
#[test]
fn on_deal_damage_no_progress_if_attacker_dies() {
    // 3/1 attacker kills 1/3 defender but dies from counter-attack
    let attacker_def = make_level_up_creature_def(1, 3, 1, LevelUpTrigger::OnDealDamage, 6, 2, 3, &[]);
    let defender_def = make_normal_creature_def(2, 1, 3);
    let db = CardDatabase::new(vec![attacker_def, defender_def]);

    let mut state = test_state();
    let mut queue = EffectQueue::new();

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

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

    // Attacker dies (1 health vs 1 counter-attack... wait: 1/3 deals 1 damage, attacker has 1 health — attacker dies)
    // Since both die, attacker is gone
    assert!(state.players[0].get_creature(Slot(0)).is_none(), "attacker should be dead");
}
