"""
Unit tests for Effect dataclass.

Tests all effect types, tick() method, stacking, and serialization.
"""

import pytest
from app.models.effects import Effect
from app.models.enums import EffectType, StatType


def test_effect_creation():
    """Test creating an Effect instance."""
    effect = Effect(
        effect_id="burn_1",
        name="Burning",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
    )

    assert effect.effect_id == "burn_1"
    assert effect.name == "Burning"
    assert effect.effect_type == EffectType.DOT
    assert effect.duration == 3
    assert effect.power == 5
    assert effect.stacks == 1
    assert effect.max_stacks == 5


def test_dot_effect_tick():
    """Test DOT (damage over time) effect ticking."""
    effect = Effect(
        effect_id="poison_1",
        name="Poisoned",
        effect_type=EffectType.DOT,
        duration=3,
        power=10,
        stacks=2,
    )

    result = effect.tick()

    assert result["effect_type"] == "dot"
    assert result["value"] == 20  # 10 power × 2 stacks
    assert result["expired"] == False
    assert effect.duration == 2  # Reduced by 1


def test_hot_effect_tick():
    """Test HOT (heal over time) effect ticking."""
    effect = Effect(
        effect_id="regen_1",
        name="Regeneration",
        effect_type=EffectType.HOT,
        duration=5,
        power=8,
        stacks=1,
    )

    result = effect.tick()

    assert result["effect_type"] == "hot"
    assert result["value"] == 8  # 8 power × 1 stack
    assert result["expired"] == False
    assert effect.duration == 4


def test_stun_effect_tick():
    """Test STUN effect ticking."""
    effect = Effect(
        effect_id="stun_1",
        name="Stunned",
        effect_type=EffectType.STUN,
        duration=1,
        power=0,
    )

    result = effect.tick()

    assert result["effect_type"] == "stun"
    assert result.get("stunned") == True
    assert effect.duration == 0


def test_shield_effect_tick():
    """Test SHIELD effect ticking."""
    effect = Effect(
        effect_id="shield_1",
        name="Shield",
        effect_type=EffectType.SHIELD,
        duration=3,
        power=50,
    )

    result = effect.tick()

    assert result["effect_type"] == "shield"
    assert result["shield_remaining"] == 50
    assert effect.duration == 2


def test_buff_effect_tick():
    """Test BUFF effect ticking."""
    effect = Effect(
        effect_id="str_buff_1",
        name="Strength Boost",
        effect_type=EffectType.BUFF,
        duration=4,
        power=5,
        stat_affected=StatType.STRENGTH,
        stacks=2,
    )

    result = effect.tick()

    assert result["effect_type"] == "buff"
    assert result["stat_affected"] == "strength"
    assert result["stat_modifier"] == 10  # 5 power × 2 stacks
    assert effect.duration == 3


def test_debuff_effect_tick():
    """Test DEBUFF effect ticking."""
    effect = Effect(
        effect_id="weak_1",
        name="Weakened",
        effect_type=EffectType.DEBUFF,
        duration=2,
        power=3,
        stat_affected=StatType.STRENGTH,
    )

    result = effect.tick()

    assert result["effect_type"] == "debuff"
    assert result["stat_affected"] == "strength"
    assert result["stat_modifier"] == 3
    assert effect.duration == 1


def test_effect_expiration():
    """Test that effect expires when duration reaches 0."""
    effect = Effect(
        effect_id="burn_1",
        name="Burning",
        effect_type=EffectType.DOT,
        duration=1,
        power=5,
    )

    result = effect.tick()

    assert result["expired"] == True
    assert effect.duration == 0


def test_effect_stacking():
    """Test apply_stack() increases stacks up to max."""
    effect = Effect(
        effect_id="poison_1",
        name="Poison",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
        max_stacks=5,
    )

    assert effect.stacks == 1

    effect.apply_stack()
    assert effect.stacks == 2

    effect.apply_stack()
    assert effect.stacks == 3

    # Apply 3 more to reach max
    effect.apply_stack()
    effect.apply_stack()
    effect.apply_stack()
    assert effect.stacks == 5  # Capped at max_stacks

    # Try to apply one more - should still be 5
    effect.apply_stack()
    assert effect.stacks == 5


def test_shield_damage_absorption():
    """Test reduce_shield() method."""
    effect = Effect(
        effect_id="shield_1",
        name="Shield",
        effect_type=EffectType.SHIELD,
        duration=3,
        power=50,
        stacks=1,
    )

    # Shield absorbs 20 damage
    remaining = effect.reduce_shield(20)
    assert remaining == 0  # All damage absorbed
    assert effect.power == 30  # Shield reduced by 20

    # Shield absorbs 20 more
    remaining = effect.reduce_shield(20)
    assert remaining == 0
    assert effect.power == 10

    # Shield takes 15 damage, breaks completely
    remaining = effect.reduce_shield(15)
    assert remaining == 5  # 5 damage passes through
    assert effect.power == 0
    assert effect.duration == 0  # Effect expires


def test_shield_with_stacks():
    """Test shield absorption with multiple stacks."""
    effect = Effect(
        effect_id="shield_1",
        name="Shield",
        effect_type=EffectType.SHIELD,
        duration=3,
        power=20,
        stacks=3,
    )

    # Total shield = 20 × 3 = 60
    # Apply 50 damage
    remaining = effect.reduce_shield(50)
    assert remaining == 0  # All absorbed
    # Shield reduced: 50 / 3 stacks = 16.67 per stack
    assert effect.power < 20  # Power reduced


def test_effect_serialization():
    """Test to_dict() serialization."""
    effect = Effect(
        effect_id="burn_1",
        name="Burning",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
        stacks=2,
        max_stacks=5,
        source="fireball",
    )

    data = effect.to_dict()

    assert data["effect_id"] == "burn_1"
    assert data["name"] == "Burning"
    assert data["effect_type"] == "dot"
    assert data["duration"] == 3
    assert data["power"] == 5
    assert data["stacks"] == 2
    assert data["max_stacks"] == 5
    assert data["source"] == "fireball"


def test_effect_deserialization():
    """Test from_dict() deserialization."""
    data = {
        "effect_id": "regen_1",
        "name": "Regeneration",
        "effect_type": "hot",
        "duration": 5,
        "power": 10,
        "stat_affected": None,
        "stacks": 1,
        "max_stacks": 5,
        "source": "potion",
    }

    effect = Effect.from_dict(data)

    assert effect.effect_id == "regen_1"
    assert effect.name == "Regeneration"
    assert effect.effect_type == EffectType.HOT
    assert effect.duration == 5
    assert effect.power == 10
    assert effect.stacks == 1


def test_effect_with_stat_deserialization():
    """Test deserializing effect with stat_affected."""
    data = {
        "effect_id": "buff_1",
        "name": "Strength Boost",
        "effect_type": "buff",
        "duration": 3,
        "power": 5,
        "stat_affected": "strength",
        "stacks": 1,
        "max_stacks": 5,
        "source": "spell",
    }

    effect = Effect.from_dict(data)

    assert effect.stat_affected == StatType.STRENGTH
    assert effect.effect_type == EffectType.BUFF


def test_effect_round_trip_serialization():
    """Test that serialization and deserialization preserve data."""
    original = Effect(
        effect_id="test_effect",
        name="Test Effect",
        effect_type=EffectType.DEBUFF,
        duration=10,
        power=15,
        stat_affected=StatType.CONSTITUTION,
        stacks=3,
        max_stacks=5,
        source="enemy_spell",
    )

    # Serialize then deserialize
    data = original.to_dict()
    restored = Effect.from_dict(data)

    assert restored.effect_id == original.effect_id
    assert restored.name == original.name
    assert restored.effect_type == original.effect_type
    assert restored.duration == original.duration
    assert restored.power == original.power
    assert restored.stat_affected == original.stat_affected
    assert restored.stacks == original.stacks
    assert restored.max_stacks == original.max_stacks
    assert restored.source == original.source


def test_effect_repr():
    """Test string representation."""
    effect = Effect(
        effect_id="poison_1",
        name="Poison",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
        stacks=2,
    )

    repr_str = repr(effect)

    assert "Poison" in repr_str
    assert "dot" in repr_str


def test_non_shield_reduce_shield():
    """Test that reduce_shield() on non-SHIELD effects returns full damage."""
    effect = Effect(
        effect_id="burn_1",
        name="Burning",
        effect_type=EffectType.DOT,
        duration=3,
        power=5,
    )

    remaining = effect.reduce_shield(50)
    assert remaining == 50  # All damage passes through for non-shield effects
