"""
Tests for the Item Generator and Affix System.

Tests cover:
- Affix loading from YAML
- Base item template loading
- Item generation with affixes
- Name generation
- Stat combination
"""

import pytest
from unittest.mock import patch, MagicMock

from app.models.affixes import Affix, BaseItemTemplate
from app.models.enums import AffixType, AffixTier, ItemRarity, ItemType, DamageType
from app.services.affix_loader import AffixLoader, get_affix_loader
from app.services.base_item_loader import BaseItemLoader, get_base_item_loader
from app.services.item_generator import ItemGenerator, get_item_generator


class TestAffixModel:
    """Tests for the Affix dataclass."""

    def test_affix_creation(self):
        """Test creating an Affix instance."""
        affix = Affix(
            affix_id="flaming",
            name="Flaming",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
            description="Fire damage",
            damage_type=DamageType.FIRE,
            elemental_ratio=0.25,
            damage_bonus=3,
        )

        assert affix.affix_id == "flaming"
        assert affix.name == "Flaming"
        assert affix.affix_type == AffixType.PREFIX
        assert affix.tier == AffixTier.MINOR
        assert affix.applies_elemental_damage()

    def test_affix_can_apply_to(self):
        """Test affix eligibility checking."""
        # Weapon-only affix
        weapon_affix = Affix(
            affix_id="sharp",
            name="Sharp",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
            allowed_item_types=["weapon"],
        )

        assert weapon_affix.can_apply_to("weapon", "rare")
        assert not weapon_affix.can_apply_to("armor", "rare")

    def test_affix_legendary_only(self):
        """Test legendary-only affix restriction."""
        legendary_affix = Affix(
            affix_id="vorpal",
            name="Vorpal",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.LEGENDARY,
            required_rarity="legendary",
        )

        assert legendary_affix.is_legendary_only()
        assert legendary_affix.can_apply_to("weapon", "legendary")
        assert not legendary_affix.can_apply_to("weapon", "epic")

    def test_affix_serialization(self):
        """Test affix to_dict and from_dict."""
        affix = Affix(
            affix_id="of_strength",
            name="of Strength",
            affix_type=AffixType.SUFFIX,
            tier=AffixTier.MINOR,
            stat_bonuses={"strength": 2},
        )

        data = affix.to_dict()
        restored = Affix.from_dict(data)

        assert restored.affix_id == affix.affix_id
        assert restored.name == affix.name
        assert restored.stat_bonuses == affix.stat_bonuses


class TestBaseItemTemplate:
    """Tests for the BaseItemTemplate dataclass."""

    def test_template_creation(self):
        """Test creating a BaseItemTemplate instance."""
        template = BaseItemTemplate(
            template_id="dagger",
            name="Dagger",
            item_type="weapon",
            base_damage=6,
            base_value=15,
            crit_chance=0.08,
            required_level=1,
        )

        assert template.template_id == "dagger"
        assert template.base_damage == 6
        assert template.crit_chance == 0.08

    def test_template_rarity_eligibility(self):
        """Test template rarity checking."""
        template = BaseItemTemplate(
            template_id="plate_armor",
            name="Plate Armor",
            item_type="armor",
            min_rarity="rare",
        )

        assert template.can_generate_at_rarity("rare")
        assert template.can_generate_at_rarity("epic")
        assert template.can_generate_at_rarity("legendary")
        assert not template.can_generate_at_rarity("common")
        assert not template.can_generate_at_rarity("uncommon")

    def test_template_level_eligibility(self):
        """Test template level checking."""
        template = BaseItemTemplate(
            template_id="greatsword",
            name="Greatsword",
            item_type="weapon",
            required_level=5,
        )

        assert template.can_drop_for_level(5)
        assert template.can_drop_for_level(10)
        assert not template.can_drop_for_level(4)


class TestAffixLoader:
    """Tests for the AffixLoader service."""

    def test_loader_initialization(self):
        """Test AffixLoader initializes correctly."""
        loader = get_affix_loader()
        assert loader is not None

    def test_load_prefixes(self):
        """Test loading prefixes from YAML."""
        loader = get_affix_loader()
        loader.load_all()

        prefixes = loader.get_all_prefixes()
        assert len(prefixes) > 0

        # Check for known prefix
        flaming = loader.get_affix("flaming")
        assert flaming is not None
        assert flaming.affix_type == AffixType.PREFIX
        assert flaming.name == "Flaming"

    def test_load_suffixes(self):
        """Test loading suffixes from YAML."""
        loader = get_affix_loader()
        loader.load_all()

        suffixes = loader.get_all_suffixes()
        assert len(suffixes) > 0

        # Check for known suffix
        of_strength = loader.get_affix("of_strength")
        assert of_strength is not None
        assert of_strength.affix_type == AffixType.SUFFIX
        assert of_strength.name == "of Strength"

    def test_get_eligible_prefixes(self):
        """Test filtering eligible prefixes."""
        loader = get_affix_loader()

        # Get weapon prefixes for rare items
        eligible = loader.get_eligible_prefixes("weapon", "rare")
        assert len(eligible) > 0

        # All should be applicable to weapons
        for prefix in eligible:
            assert prefix.can_apply_to("weapon", "rare")

    def test_get_random_prefix(self):
        """Test random prefix selection."""
        loader = get_affix_loader()

        prefix = loader.get_random_prefix("weapon", "rare")
        assert prefix is not None
        assert prefix.affix_type == AffixType.PREFIX


class TestBaseItemLoader:
    """Tests for the BaseItemLoader service."""

    def test_loader_initialization(self):
        """Test BaseItemLoader initializes correctly."""
        loader = get_base_item_loader()
        assert loader is not None

    def test_load_weapons(self):
        """Test loading weapon templates from YAML."""
        loader = get_base_item_loader()
        loader.load_all()

        weapons = loader.get_all_weapons()
        assert len(weapons) > 0

        # Check for known weapon
        dagger = loader.get_template("dagger")
        assert dagger is not None
        assert dagger.item_type == "weapon"
        assert dagger.base_damage > 0

    def test_load_armor(self):
        """Test loading armor templates from YAML."""
        loader = get_base_item_loader()
        loader.load_all()

        armor = loader.get_all_armor()
        assert len(armor) > 0

        # Check for known armor
        chainmail = loader.get_template("chainmail")
        assert chainmail is not None
        assert chainmail.item_type == "armor"
        assert chainmail.base_defense > 0

    def test_get_eligible_templates(self):
        """Test filtering eligible templates."""
        loader = get_base_item_loader()

        # Get weapons for level 1, common rarity
        eligible = loader.get_eligible_templates("weapon", "common", 1)
        assert len(eligible) > 0

        # All should be eligible
        for template in eligible:
            assert template.can_drop_for_level(1)
            assert template.can_generate_at_rarity("common")

    def test_get_random_template(self):
        """Test random template selection."""
        loader = get_base_item_loader()

        template = loader.get_random_template("weapon", "common", 1)
        assert template is not None
        assert template.item_type == "weapon"


class TestItemGenerator:
    """Tests for the ItemGenerator service."""

    def test_generator_initialization(self):
        """Test ItemGenerator initializes correctly."""
        generator = get_item_generator()
        assert generator is not None

    def test_generate_common_item(self):
        """Test generating a common item (no affixes)."""
        generator = get_item_generator()

        item = generator.generate_item("weapon", ItemRarity.COMMON, 1)
        assert item is not None
        assert item.rarity == ItemRarity.COMMON
        assert item.is_generated
        assert len(item.applied_affixes) == 0
        # Common items have no generated name
        assert item.generated_name == item.name

    def test_generate_rare_item(self):
        """Test generating a rare item (1 affix)."""
        generator = get_item_generator()

        item = generator.generate_item("weapon", ItemRarity.RARE, 1)
        assert item is not None
        assert item.rarity == ItemRarity.RARE
        assert item.is_generated
        assert len(item.applied_affixes) == 1
        assert item.generated_name != item.name

    def test_generate_epic_item(self):
        """Test generating an epic item (2 affixes)."""
        generator = get_item_generator()

        item = generator.generate_item("weapon", ItemRarity.EPIC, 1)
        assert item is not None
        assert item.rarity == ItemRarity.EPIC
        assert item.is_generated
        assert len(item.applied_affixes) == 2

    def test_generate_legendary_item(self):
        """Test generating a legendary item (3 affixes)."""
        generator = get_item_generator()

        item = generator.generate_item("weapon", ItemRarity.LEGENDARY, 5)
        assert item is not None
        assert item.rarity == ItemRarity.LEGENDARY
        assert item.is_generated
        assert len(item.applied_affixes) == 3

    def test_generated_name_format(self):
        """Test that generated names follow the expected format."""
        generator = get_item_generator()

        # Generate multiple items and check name patterns
        for _ in range(10):
            item = generator.generate_item("weapon", ItemRarity.EPIC, 1)
            if item:
                name = item.get_display_name()
                # EPIC should have both prefix and suffix (typically)
                # Name should contain the base item name
                assert item.name in name or item.base_template_id in name.lower()

    def test_stat_combination(self):
        """Test that affix stats are properly combined."""
        generator = get_item_generator()

        # Generate items and verify stat bonuses are present
        for _ in range(5):
            item = generator.generate_item("weapon", ItemRarity.RARE, 1)
            if item and item.applied_affixes:
                # Item should have some stat modifications
                # Either stat_bonuses, damage_bonus, or elemental properties
                has_stats = (
                    bool(item.stat_bonuses) or
                    item.damage > 0 or
                    item.elemental_ratio > 0
                )
                assert has_stats

    def test_generate_armor(self):
        """Test generating armor items."""
        generator = get_item_generator()

        item = generator.generate_item("armor", ItemRarity.RARE, 1)
        assert item is not None
        assert item.item_type == ItemType.ARMOR
        assert item.defense > 0 or item.resistance > 0

    def test_generate_loot_drop(self):
        """Test random loot drop generation."""
        generator = get_item_generator()

        # Generate multiple drops to test randomness
        rarities_seen = set()
        for _ in range(50):
            item = generator.generate_loot_drop(5, luck_stat=8)
            if item:
                rarities_seen.add(item.rarity)

        # Should see at least common and uncommon
        assert ItemRarity.COMMON in rarities_seen or ItemRarity.UNCOMMON in rarities_seen

    def test_luck_affects_rarity(self):
        """Test that higher luck increases rare drops."""
        generator = get_item_generator()

        # This is a statistical test - higher luck should trend toward better rarity
        low_luck_rares = 0
        high_luck_rares = 0

        for _ in range(100):
            low_luck_item = generator.generate_loot_drop(5, luck_stat=1)
            high_luck_item = generator.generate_loot_drop(5, luck_stat=20)

            if low_luck_item and low_luck_item.rarity in [ItemRarity.RARE, ItemRarity.EPIC, ItemRarity.LEGENDARY]:
                low_luck_rares += 1
            if high_luck_item and high_luck_item.rarity in [ItemRarity.RARE, ItemRarity.EPIC, ItemRarity.LEGENDARY]:
                high_luck_rares += 1

        # High luck should generally produce more rare+ items
        # (This may occasionally fail due to randomness, but should pass most of the time)
        # We're just checking the trend, not a strict guarantee
        # logger.info(f"Low luck rares: {low_luck_rares}, High luck rares: {high_luck_rares}")


class TestNameGeneration:
    """Tests specifically for item name generation."""

    def test_prefix_only_name(self):
        """Test name with only a prefix."""
        generator = get_item_generator()

        # Create mock affixes
        prefix = Affix(
            affix_id="flaming",
            name="Flaming",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
        )

        name = generator._build_name("Dagger", [prefix], [])
        assert name == "Flaming Dagger"

    def test_suffix_only_name(self):
        """Test name with only a suffix."""
        generator = get_item_generator()

        suffix = Affix(
            affix_id="of_strength",
            name="of Strength",
            affix_type=AffixType.SUFFIX,
            tier=AffixTier.MINOR,
        )

        name = generator._build_name("Dagger", [], [suffix])
        assert name == "Dagger of Strength"

    def test_full_name(self):
        """Test name with prefix and suffix."""
        generator = get_item_generator()

        prefix = Affix(
            affix_id="flaming",
            name="Flaming",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
        )
        suffix = Affix(
            affix_id="of_strength",
            name="of Strength",
            affix_type=AffixType.SUFFIX,
            tier=AffixTier.MINOR,
        )

        name = generator._build_name("Dagger", [prefix], [suffix])
        assert name == "Flaming Dagger of Strength"

    def test_multiple_prefixes(self):
        """Test name with multiple prefixes."""
        generator = get_item_generator()

        prefix1 = Affix(
            affix_id="flaming",
            name="Flaming",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
        )
        prefix2 = Affix(
            affix_id="sharp",
            name="Sharp",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
        )

        name = generator._build_name("Dagger", [prefix1, prefix2], [])
        assert name == "Flaming Sharp Dagger"


class TestStatCombination:
    """Tests for combining affix stats."""

    def test_combine_stat_bonuses(self):
        """Test combining stat bonuses from multiple affixes."""
        generator = get_item_generator()

        affix1 = Affix(
            affix_id="test1",
            name="Test1",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
            stat_bonuses={"strength": 2, "constitution": 1},
        )
        affix2 = Affix(
            affix_id="test2",
            name="Test2",
            affix_type=AffixType.SUFFIX,
            tier=AffixTier.MINOR,
            stat_bonuses={"strength": 3, "dexterity": 2},
        )

        combined = generator._combine_affix_stats([affix1, affix2])

        assert combined["stat_bonuses"]["strength"] == 5
        assert combined["stat_bonuses"]["constitution"] == 1
        assert combined["stat_bonuses"]["dexterity"] == 2

    def test_combine_damage_bonuses(self):
        """Test combining damage bonuses."""
        generator = get_item_generator()

        affix1 = Affix(
            affix_id="sharp",
            name="Sharp",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
            damage_bonus=3,
        )
        affix2 = Affix(
            affix_id="keen",
            name="Keen",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MAJOR,
            damage_bonus=5,
        )

        combined = generator._combine_affix_stats([affix1, affix2])

        assert combined["damage_bonus"] == 8

    def test_combine_crit_bonuses(self):
        """Test combining crit chance and multiplier bonuses."""
        generator = get_item_generator()

        affix1 = Affix(
            affix_id="sharp",
            name="Sharp",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MINOR,
            crit_chance_bonus=0.02,
        )
        affix2 = Affix(
            affix_id="keen",
            name="Keen",
            affix_type=AffixType.PREFIX,
            tier=AffixTier.MAJOR,
            crit_chance_bonus=0.04,
            crit_multiplier_bonus=0.5,
        )

        combined = generator._combine_affix_stats([affix1, affix2])

        assert combined["crit_chance_bonus"] == pytest.approx(0.06)
        assert combined["crit_multiplier_bonus"] == pytest.approx(0.5)
