"""
Integration tests for combat system.

Tests the complete combat flow including damage calculation, effects, and turn order.
"""

import pytest
from app.models.stats import Stats
from app.models.items import Item
from app.models.effects import Effect
from app.models.abilities import Ability
from app.models.combat import Combatant, CombatEncounter
from app.models.character import Character
from app.models.skills import PlayerClass
from app.models.enums import (
    ItemType,
    DamageType,
    EffectType,
    StatType,
    AbilityType,
    CombatStatus,
)


@pytest.fixture
def warrior_combatant():
    """Create a warrior combatant for testing."""
    stats = Stats(strength=15, dexterity=10, constitution=14, intelligence=8, wisdom=10, charisma=11)

    return Combatant(
        combatant_id="warrior_1",
        name="Test Warrior",
        is_player=True,
        current_hp=stats.hit_points,
        max_hp=stats.hit_points,
        current_mp=stats.mana_points,
        max_mp=stats.mana_points,
        stats=stats,
        abilities=["basic_attack", "power_strike"],
    )


@pytest.fixture
def goblin_combatant():
    """Create a goblin enemy for testing."""
    stats = Stats(strength=8, dexterity=12, constitution=10, intelligence=6, wisdom=8, charisma=6)

    return Combatant(
        combatant_id="goblin_1",
        name="Goblin",
        is_player=False,
        current_hp=stats.hit_points,
        max_hp=stats.hit_points,
        current_mp=stats.mana_points,
        max_mp=stats.mana_points,
        stats=stats,
        abilities=["basic_attack"],
    )


def test_combatant_creation(warrior_combatant):
    """Test creating a Combatant."""
    assert warrior_combatant.combatant_id == "warrior_1"
    assert warrior_combatant.name == "Test Warrior"
    assert warrior_combatant.is_player == True
    assert warrior_combatant.is_alive() == True
    assert warrior_combatant.is_stunned() == False


def test_combatant_take_damage(warrior_combatant):
    """Test taking damage."""
    initial_hp = warrior_combatant.current_hp

    damage_dealt = warrior_combatant.take_damage(10)

    assert damage_dealt == 10
    assert warrior_combatant.current_hp == initial_hp - 10


def test_combatant_take_damage_with_shield(warrior_combatant):
    """Test taking damage with shield absorption."""
    # Add a shield effect
    shield = Effect(
        effect_id="shield_1",
        name="Shield",
        effect_type=EffectType.SHIELD,
        duration=3,
        power=15,
    )
    warrior_combatant.add_effect(shield)

    initial_hp = warrior_combatant.current_hp

    # Deal 10 damage - should be fully absorbed by shield
    damage_dealt = warrior_combatant.take_damage(10)

    assert damage_dealt == 0  # No HP damage
    assert warrior_combatant.current_hp == initial_hp


def test_combatant_death(warrior_combatant):
    """Test combatant death."""
    assert warrior_combatant.is_alive() == True

    # Deal massive damage
    warrior_combatant.take_damage(1000)

    assert warrior_combatant.is_alive() == False
    assert warrior_combatant.is_dead() == True


def test_combatant_healing(warrior_combatant):
    """Test healing."""
    # Take some damage first
    warrior_combatant.take_damage(20)
    damaged_hp = warrior_combatant.current_hp

    # Heal
    healed = warrior_combatant.heal(10)

    assert healed == 10
    assert warrior_combatant.current_hp == damaged_hp + 10


def test_combatant_healing_capped_at_max(warrior_combatant):
    """Test that healing cannot exceed max HP."""
    max_hp = warrior_combatant.max_hp

    # Try to heal beyond max
    healed = warrior_combatant.heal(1000)

    assert warrior_combatant.current_hp == max_hp


def test_combatant_stun_effect(warrior_combatant):
    """Test stun effect prevents actions."""
    assert warrior_combatant.is_stunned() == False

    # Add stun effect
    stun = Effect(
        effect_id="stun_1",
        name="Stunned",
        effect_type=EffectType.STUN,
        duration=1,
        power=0,
    )
    warrior_combatant.add_effect(stun)

    assert warrior_combatant.is_stunned() == True


def test_combatant_tick_effects(warrior_combatant):
    """Test that ticking effects deals damage/healing."""
    # Add a DOT effect
    poison = Effect(
        effect_id="poison_1",
        name="Poison",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
    )
    warrior_combatant.add_effect(poison)

    initial_hp = warrior_combatant.current_hp

    # Tick effects
    results = warrior_combatant.tick_effects()

    # Should have taken 5 poison damage
    assert len(results) == 1
    assert results[0]["effect_type"] == "dot"
    assert results[0]["value"] == 5
    assert warrior_combatant.current_hp == initial_hp - 5


def test_combatant_effect_expiration(warrior_combatant):
    """Test that expired effects are removed."""
    # Add effect with 1 turn duration
    dot = Effect(
        effect_id="burn_1",
        name="Burning",
        effect_type=EffectType.DOT,
        duration=1,
        power=5,
    )
    warrior_combatant.add_effect(dot)

    assert len(warrior_combatant.active_effects) == 1

    # Tick - effect should expire
    results = warrior_combatant.tick_effects()

    assert results[0]["expired"] == True
    assert len(warrior_combatant.active_effects) == 0  # Removed


def test_ability_mana_cost(warrior_combatant):
    """Test ability mana cost and usage."""
    ability = Ability(
        ability_id="fireball",
        name="Fireball",
        description="Fiery explosion",
        ability_type=AbilityType.SPELL,
        base_power=30,
        damage_type=DamageType.FIRE,
        mana_cost=15,
    )

    initial_mp = warrior_combatant.current_mp

    # Check if can use
    assert warrior_combatant.can_use_ability("fireball", ability) == False  # Not in ability list
    warrior_combatant.abilities.append("fireball")
    assert warrior_combatant.can_use_ability("fireball", ability) == True

    # Use ability
    warrior_combatant.use_ability_cost(ability, "fireball")

    assert warrior_combatant.current_mp == initial_mp - 15


def test_ability_cooldown(warrior_combatant):
    """Test ability cooldowns."""
    ability = Ability(
        ability_id="power_strike",
        name="Power Strike",
        description="Powerful attack",
        ability_type=AbilityType.SKILL,
        base_power=20,
        cooldown=3,
    )

    warrior_combatant.abilities.append("power_strike")

    # Can use initially
    assert warrior_combatant.can_use_ability("power_strike", ability) == True

    # Use ability
    warrior_combatant.use_ability_cost(ability, "power_strike")

    # Now on cooldown
    assert "power_strike" in warrior_combatant.cooldowns
    assert warrior_combatant.cooldowns["power_strike"] == 3
    assert warrior_combatant.can_use_ability("power_strike", ability) == False

    # Tick cooldown
    warrior_combatant.tick_cooldowns()
    assert warrior_combatant.cooldowns["power_strike"] == 2

    # Tick more
    warrior_combatant.tick_cooldowns()
    warrior_combatant.tick_cooldowns()

    # Should be available again
    assert "power_strike" not in warrior_combatant.cooldowns
    assert warrior_combatant.can_use_ability("power_strike", ability) == True


def test_combat_encounter_initialization(warrior_combatant, goblin_combatant):
    """Test initializing a combat encounter."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant, goblin_combatant],
    )

    encounter.initialize_combat()

    # Should have turn order
    assert len(encounter.turn_order) == 2
    assert encounter.round_number == 1
    assert encounter.status == CombatStatus.ACTIVE

    # Both combatants should have initiative
    assert warrior_combatant.initiative > 0
    assert goblin_combatant.initiative > 0


def test_combat_turn_advancement(warrior_combatant, goblin_combatant):
    """Test advancing turns in combat."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant, goblin_combatant],
    )

    encounter.initialize_combat()

    # Get first combatant
    first = encounter.get_current_combatant()
    assert first is not None

    # Advance turn
    encounter.advance_turn()

    # Should be second combatant now
    second = encounter.get_current_combatant()
    assert second is not None
    assert second.combatant_id != first.combatant_id

    # Advance again - should cycle back to first and increment round
    encounter.advance_turn()

    assert encounter.round_number == 2
    third = encounter.get_current_combatant()
    assert third.combatant_id == first.combatant_id


def test_combat_victory_condition(warrior_combatant, goblin_combatant):
    """Test victory condition detection."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant, goblin_combatant],
    )

    encounter.initialize_combat()

    # Kill the goblin
    goblin_combatant.current_hp = 0

    # Check end condition
    status = encounter.check_end_condition()

    assert status == CombatStatus.VICTORY
    assert encounter.status == CombatStatus.VICTORY


def test_combat_defeat_condition(warrior_combatant, goblin_combatant):
    """Test defeat condition detection."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant, goblin_combatant],
    )

    encounter.initialize_combat()

    # Kill the warrior
    warrior_combatant.current_hp = 0

    # Check end condition
    status = encounter.check_end_condition()

    assert status == CombatStatus.DEFEAT
    assert encounter.status == CombatStatus.DEFEAT


def test_combat_start_turn_processing(warrior_combatant):
    """Test start_turn() processes effects and cooldowns."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant],
    )

    # Initialize combat to set turn order
    encounter.initialize_combat()

    # Add a DOT effect
    poison = Effect(
        effect_id="poison_1",
        name="Poison",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
    )
    warrior_combatant.add_effect(poison)

    # Add a cooldown
    warrior_combatant.cooldowns["power_strike"] = 2

    initial_hp = warrior_combatant.current_hp

    # Start turn
    results = encounter.start_turn()

    # Effects should have ticked
    assert len(results) == 1
    assert warrior_combatant.current_hp == initial_hp - 5

    # Cooldown should have decreased
    assert warrior_combatant.cooldowns["power_strike"] == 1


def test_combat_logging(warrior_combatant, goblin_combatant):
    """Test combat log entries."""
    encounter = CombatEncounter(
        encounter_id="combat_001",
        combatants=[warrior_combatant, goblin_combatant],
    )

    encounter.log_action("attack", "warrior_1", "Warrior attacks Goblin for 10 damage")

    assert len(encounter.combat_log) == 1
    assert encounter.combat_log[0]["action_type"] == "attack"
    assert encounter.combat_log[0]["combatant_id"] == "warrior_1"
    assert "Warrior attacks Goblin" in encounter.combat_log[0]["message"]


def test_ability_damage_calculation():
    """Test ability power calculation with stat scaling."""
    stats = Stats(strength=20, intelligence=16)

    # Physical ability scaling with strength
    physical = Ability(
        ability_id="cleave",
        name="Cleave",
        description="Powerful strike",
        ability_type=AbilityType.SKILL,
        base_power=15,
        scaling_stat=StatType.STRENGTH,
        scaling_factor=0.5,
    )

    power = physical.calculate_power(stats)
    # 15 (base) + (20 strength × 0.5) = 15 + 10 = 25
    assert power == 25

    # Magical ability scaling with intelligence
    magical = Ability(
        ability_id="fireball",
        name="Fireball",
        description="Fire spell",
        ability_type=AbilityType.SPELL,
        base_power=20,
        scaling_stat=StatType.INTELLIGENCE,
        scaling_factor=0.5,
    )

    power = magical.calculate_power(stats)
    # 20 (base) + (16 intelligence × 0.5) = 20 + 8 = 28
    assert power == 28


def test_full_combat_simulation():
    """Integration test: Full combat simulation with all systems."""
    # Create warrior
    warrior_stats = Stats(strength=15, constitution=14)
    warrior = Combatant(
        combatant_id="hero",
        name="Hero",
        is_player=True,
        current_hp=warrior_stats.hit_points,
        max_hp=warrior_stats.hit_points,
        current_mp=warrior_stats.mana_points,
        max_mp=warrior_stats.mana_points,
        stats=warrior_stats,
    )

    # Create goblin
    goblin_stats = Stats(strength=8, constitution=10)
    goblin = Combatant(
        combatant_id="goblin",
        name="Goblin",
        is_player=False,
        current_hp=goblin_stats.hit_points,
        max_hp=goblin_stats.hit_points,
        current_mp=goblin_stats.mana_points,
        max_mp=goblin_stats.mana_points,
        stats=goblin_stats,
    )

    # Create encounter
    encounter = CombatEncounter(
        encounter_id="test_combat",
        combatants=[warrior, goblin],
    )

    encounter.initialize_combat()

    # Verify setup
    assert encounter.status == CombatStatus.ACTIVE
    assert len(encounter.turn_order) == 2
    assert warrior.is_alive() and goblin.is_alive()

    # Simulate turns until combat ends
    max_turns = 50  # Increased to ensure combat completes
    turn_count = 0

    while encounter.status == CombatStatus.ACTIVE and turn_count < max_turns:
        # Get current combatant
        current = encounter.get_current_combatant()

        # Start turn (tick effects)
        encounter.start_turn()

        if current and current.is_alive() and not current.is_stunned():
            # Simple AI: deal damage to opponent
            if current.combatant_id == "hero":
                target = goblin
            else:
                target = warrior

            # Calculate simple attack damage: strength / 2 - target defense
            damage = max(1, (current.stats.strength // 2) - target.stats.defense)
            target.take_damage(damage)

            encounter.log_action(
                "attack",
                current.combatant_id,
                f"{current.name} attacks {target.name} for {damage} damage",
            )

        # Check for combat end
        encounter.check_end_condition()

        # Advance turn
        encounter.advance_turn()
        turn_count += 1

    # Combat should have ended
    assert encounter.status in [CombatStatus.VICTORY, CombatStatus.DEFEAT]
    assert len(encounter.combat_log) > 0
