"""
Unit tests for Item dataclass and ItemRarity enum.

Tests item creation, rarity, type checking, and serialization.
"""

import pytest
from app.models.items import Item
from app.models.enums import ItemType, ItemRarity, DamageType


class TestItemRarityEnum:
    """Tests for ItemRarity enum."""

    def test_rarity_values(self):
        """Test all rarity values exist and have correct string values."""
        assert ItemRarity.COMMON.value == "common"
        assert ItemRarity.UNCOMMON.value == "uncommon"
        assert ItemRarity.RARE.value == "rare"
        assert ItemRarity.EPIC.value == "epic"
        assert ItemRarity.LEGENDARY.value == "legendary"

    def test_rarity_from_string(self):
        """Test creating rarity from string value."""
        assert ItemRarity("common") == ItemRarity.COMMON
        assert ItemRarity("uncommon") == ItemRarity.UNCOMMON
        assert ItemRarity("rare") == ItemRarity.RARE
        assert ItemRarity("epic") == ItemRarity.EPIC
        assert ItemRarity("legendary") == ItemRarity.LEGENDARY

    def test_rarity_count(self):
        """Test that there are exactly 5 rarity tiers."""
        assert len(ItemRarity) == 5


class TestItemCreation:
    """Tests for creating Item instances."""

    def test_create_basic_item(self):
        """Test creating a basic item with minimal fields."""
        item = Item(
            item_id="test_item",
            name="Test Item",
            item_type=ItemType.QUEST_ITEM,
        )

        assert item.item_id == "test_item"
        assert item.name == "Test Item"
        assert item.item_type == ItemType.QUEST_ITEM
        assert item.rarity == ItemRarity.COMMON  # Default
        assert item.description == ""
        assert item.value == 0
        assert item.is_tradeable == True

    def test_item_default_rarity_is_common(self):
        """Test that items default to COMMON rarity."""
        item = Item(
            item_id="sword_1",
            name="Iron Sword",
            item_type=ItemType.WEAPON,
        )

        assert item.rarity == ItemRarity.COMMON

    def test_create_item_with_rarity(self):
        """Test creating items with different rarity levels."""
        uncommon = Item(
            item_id="sword_2",
            name="Steel Sword",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.UNCOMMON,
        )
        assert uncommon.rarity == ItemRarity.UNCOMMON

        rare = Item(
            item_id="sword_3",
            name="Mithril Sword",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.RARE,
        )
        assert rare.rarity == ItemRarity.RARE

        epic = Item(
            item_id="sword_4",
            name="Dragon Sword",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.EPIC,
        )
        assert epic.rarity == ItemRarity.EPIC

        legendary = Item(
            item_id="sword_5",
            name="Excalibur",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.LEGENDARY,
        )
        assert legendary.rarity == ItemRarity.LEGENDARY

    def test_create_weapon(self):
        """Test creating a weapon with all weapon-specific fields."""
        weapon = Item(
            item_id="fire_sword",
            name="Flame Blade",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.RARE,
            description="A sword wreathed in flames.",
            value=500,
            damage=25,
            damage_type=DamageType.PHYSICAL,
            crit_chance=0.15,
            crit_multiplier=2.5,
            elemental_damage_type=DamageType.FIRE,
            physical_ratio=0.7,
            elemental_ratio=0.3,
        )

        assert weapon.is_weapon() == True
        assert weapon.is_elemental_weapon() == True
        assert weapon.damage == 25
        assert weapon.crit_chance == 0.15
        assert weapon.crit_multiplier == 2.5
        assert weapon.elemental_damage_type == DamageType.FIRE
        assert weapon.physical_ratio == 0.7
        assert weapon.elemental_ratio == 0.3

    def test_create_armor(self):
        """Test creating armor with defense/resistance."""
        armor = Item(
            item_id="plate_armor",
            name="Steel Plate Armor",
            item_type=ItemType.ARMOR,
            rarity=ItemRarity.UNCOMMON,
            description="Heavy steel armor.",
            value=300,
            defense=15,
            resistance=5,
        )

        assert armor.is_armor() == True
        assert armor.defense == 15
        assert armor.resistance == 5

    def test_create_consumable(self):
        """Test creating a consumable item."""
        potion = Item(
            item_id="health_potion",
            name="Health Potion",
            item_type=ItemType.CONSUMABLE,
            rarity=ItemRarity.COMMON,
            description="Restores 50 HP.",
            value=25,
        )

        assert potion.is_consumable() == True
        assert potion.is_tradeable == True


class TestItemTypeMethods:
    """Tests for item type checking methods."""

    def test_is_weapon(self):
        """Test is_weapon() method."""
        weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
        armor = Item(item_id="a", name="A", item_type=ItemType.ARMOR)

        assert weapon.is_weapon() == True
        assert armor.is_weapon() == False

    def test_is_armor(self):
        """Test is_armor() method."""
        weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)
        armor = Item(item_id="a", name="A", item_type=ItemType.ARMOR)

        assert armor.is_armor() == True
        assert weapon.is_armor() == False

    def test_is_consumable(self):
        """Test is_consumable() method."""
        consumable = Item(item_id="c", name="C", item_type=ItemType.CONSUMABLE)
        weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)

        assert consumable.is_consumable() == True
        assert weapon.is_consumable() == False

    def test_is_quest_item(self):
        """Test is_quest_item() method."""
        quest = Item(item_id="q", name="Q", item_type=ItemType.QUEST_ITEM)
        weapon = Item(item_id="w", name="W", item_type=ItemType.WEAPON)

        assert quest.is_quest_item() == True
        assert weapon.is_quest_item() == False


class TestItemSerialization:
    """Tests for Item serialization and deserialization."""

    def test_to_dict_includes_rarity(self):
        """Test that to_dict() includes rarity as string."""
        item = Item(
            item_id="test",
            name="Test",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.EPIC,
            description="Test item",
        )

        data = item.to_dict()

        assert data["rarity"] == "epic"
        assert data["item_type"] == "weapon"

    def test_from_dict_parses_rarity(self):
        """Test that from_dict() parses rarity correctly."""
        data = {
            "item_id": "test",
            "name": "Test",
            "item_type": "weapon",
            "rarity": "legendary",
            "description": "Test item",
        }

        item = Item.from_dict(data)

        assert item.rarity == ItemRarity.LEGENDARY
        assert item.item_type == ItemType.WEAPON

    def test_from_dict_defaults_to_common_rarity(self):
        """Test that from_dict() defaults to COMMON if rarity missing."""
        data = {
            "item_id": "test",
            "name": "Test",
            "item_type": "weapon",
            "description": "Test item",
            # No rarity field
        }

        item = Item.from_dict(data)

        assert item.rarity == ItemRarity.COMMON

    def test_round_trip_serialization(self):
        """Test serialization and deserialization preserve all data."""
        original = Item(
            item_id="flame_sword",
            name="Flame Blade",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.RARE,
            description="A fiery blade.",
            value=500,
            damage=25,
            damage_type=DamageType.PHYSICAL,
            crit_chance=0.12,
            crit_multiplier=2.5,
            elemental_damage_type=DamageType.FIRE,
            physical_ratio=0.7,
            elemental_ratio=0.3,
            defense=0,
            resistance=0,
            required_level=5,
            stat_bonuses={"strength": 3},
        )

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

        assert restored.item_id == original.item_id
        assert restored.name == original.name
        assert restored.item_type == original.item_type
        assert restored.rarity == original.rarity
        assert restored.description == original.description
        assert restored.value == original.value
        assert restored.damage == original.damage
        assert restored.damage_type == original.damage_type
        assert restored.crit_chance == original.crit_chance
        assert restored.crit_multiplier == original.crit_multiplier
        assert restored.elemental_damage_type == original.elemental_damage_type
        assert restored.physical_ratio == original.physical_ratio
        assert restored.elemental_ratio == original.elemental_ratio
        assert restored.required_level == original.required_level
        assert restored.stat_bonuses == original.stat_bonuses

    def test_round_trip_all_rarities(self):
        """Test round-trip serialization for all rarity levels."""
        for rarity in ItemRarity:
            original = Item(
                item_id=f"item_{rarity.value}",
                name=f"{rarity.value.title()} Item",
                item_type=ItemType.CONSUMABLE,
                rarity=rarity,
            )

            data = original.to_dict()
            restored = Item.from_dict(data)

            assert restored.rarity == rarity


class TestItemEquippability:
    """Tests for item equip requirements."""

    def test_can_equip_level_requirement(self):
        """Test level requirement checking."""
        item = Item(
            item_id="high_level_sword",
            name="Epic Sword",
            item_type=ItemType.WEAPON,
            required_level=10,
        )

        assert item.can_equip(character_level=5) == False
        assert item.can_equip(character_level=10) == True
        assert item.can_equip(character_level=15) == True

    def test_can_equip_class_requirement(self):
        """Test class requirement checking."""
        item = Item(
            item_id="mage_staff",
            name="Mage Staff",
            item_type=ItemType.WEAPON,
            required_class="mage",
        )

        assert item.can_equip(character_level=1, character_class="warrior") == False
        assert item.can_equip(character_level=1, character_class="mage") == True


class TestItemStatBonuses:
    """Tests for item stat bonus methods."""

    def test_get_total_stat_bonus(self):
        """Test getting stat bonuses from items."""
        item = Item(
            item_id="ring_of_power",
            name="Ring of Power",
            item_type=ItemType.ARMOR,
            stat_bonuses={"strength": 5, "constitution": 3},
        )

        assert item.get_total_stat_bonus("strength") == 5
        assert item.get_total_stat_bonus("constitution") == 3
        assert item.get_total_stat_bonus("dexterity") == 0  # Not in bonuses


class TestItemRepr:
    """Tests for item string representation."""

    def test_weapon_repr(self):
        """Test weapon __repr__ output."""
        weapon = Item(
            item_id="sword",
            name="Iron Sword",
            item_type=ItemType.WEAPON,
            damage=10,
            value=50,
        )

        repr_str = repr(weapon)
        assert "Iron Sword" in repr_str
        assert "weapon" in repr_str

    def test_armor_repr(self):
        """Test armor __repr__ output."""
        armor = Item(
            item_id="armor",
            name="Leather Armor",
            item_type=ItemType.ARMOR,
            defense=5,
            value=30,
        )

        repr_str = repr(armor)
        assert "Leather Armor" in repr_str
        assert "armor" in repr_str

    def test_consumable_repr(self):
        """Test consumable __repr__ output."""
        potion = Item(
            item_id="potion",
            name="Health Potion",
            item_type=ItemType.CONSUMABLE,
            value=10,
        )

        repr_str = repr(potion)
        assert "Health Potion" in repr_str
        assert "consumable" in repr_str
