"""
Unit tests for FreeExile Endgame Crafting, Affix Slot Matrix & Trade-off Downsides.
Validates PoE2 itemization, iLvl tier gating, pinnacle boss materials,
and quest-gated Primal Boneforge trade-off affixes.
"""

import unittest
from server.world.item_crafting_types import (
    ItemSlot,
    ItemRarity,
    AffixType,
    PrimalCurrencyType,
    DownsidePenaltyType,
    CraftingItem,
    AffixMod,
)
from server.world.item_crafting_engine import ItemCraftingEngine
from server.world.item_affix_catalog import (
    get_available_affixes,
    CANONICAL_TRADEOFF_AFFIXES,
)


class TestEndgameCraftingAndAffixes(unittest.TestCase):
    def setUp(self):
        self.engine = ItemCraftingEngine(rng_seed=42)

    def test_slot_exclusivity_rules(self):
        """Validates that affixes only roll on allowed item slots."""
        # Boots can roll Movement Speed
        boot_suffixes = get_available_affixes(ItemSlot.BOOTS, AffixType.SUFFIX, item_level=90)
        boot_mod_ids = [m.mod_id for m in boot_suffixes]
        self.assertTrue(any("move_spd" in mid for mid in boot_mod_ids))

        # Body Armour CANNOT roll Movement Speed
        chest_suffixes = get_available_affixes(ItemSlot.BODY_ARMOUR, AffixType.SUFFIX, item_level=90)
        chest_mod_ids = [m.mod_id for m in chest_suffixes]
        self.assertFalse(any("move_spd" in mid for mid in chest_mod_ids))

        # Weapons CANNOT roll Flat Armor
        weapon_prefixes = get_available_affixes(ItemSlot.WEAPON_1H, AffixType.PREFIX, item_level=90)
        weapon_mod_ids = [m.mod_id for m in weapon_prefixes]
        self.assertFalse(any("armor_flat" in mid for mid in weapon_mod_ids))
        self.assertTrue(any("phys_pct" in mid for mid in weapon_mod_ids))

        # Amulet can roll +Skills, Boots cannot
        amulet_prefixes = get_available_affixes(ItemSlot.AMULET, AffixType.PREFIX, item_level=90)
        amulet_mod_ids = [m.mod_id for m in amulet_prefixes]
        self.assertTrue(any("all_skills" in mid for mid in amulet_mod_ids))

    def test_ilvl_tier_gating(self):
        """Low iLvl items cannot roll high-tier (T1) affixes."""
        # Low iLvl item (iLvl 20)
        low_pool = get_available_affixes(ItemSlot.WEAPON_1H, AffixType.PREFIX, item_level=20)
        for affix in low_pool:
            self.assertGreaterEqual(affix.tier, 4, f"Affix {affix.mod_id} tier {affix.tier} rolled on iLvl 20!")

        # High iLvl item (iLvl 86+) can roll T1
        high_pool = get_available_affixes(ItemSlot.WEAPON_1H, AffixType.PREFIX, item_level=86)
        has_t1 = any(affix.tier == 1 for affix in high_pool)
        self.assertTrue(has_t1, "iLvl 86 item should have access to T1 affixes.")

    def test_primal_currencies_progression(self):
        """Tests Transmutation -> Augmentation -> Regal crafting progression."""
        item = CraftingItem(
            item_uuid="primal_blade_01",
            base_name="U Minh Cốt Đao",
            item_level=85,
            slot=ItemSlot.WEAPON_1H,
            rarity=ItemRarity.NORMAL,
        )

        # 1. Transmute Normal -> Magic
        ok, msg, item = self.engine.apply_currency(item, PrimalCurrencyType.HAC_SO_THACH)
        self.assertTrue(ok, msg)
        self.assertEqual(item.rarity, ItemRarity.MAGIC)
        self.assertTrue(1 <= item.total_affixes_count <= 2)

        # 2. Augment Magic if it rolled 1 mod
        if item.total_affixes_count == 1:
            ok, msg, item = self.engine.apply_currency(item, PrimalCurrencyType.UAN_MA_THACH)
            self.assertTrue(ok, msg)
            self.assertEqual(item.total_affixes_count, 2)

        # 3. Regal Magic -> Rare
        ok, msg, item = self.engine.apply_currency(item, PrimalCurrencyType.CO_COT_AN)
        self.assertTrue(ok, msg)
        self.assertEqual(item.rarity, ItemRarity.RARE)
        self.assertEqual(item.total_affixes_count, 3)

    def test_pinnacle_flame_demon_essence_guaranteed_t1(self):
        """Flame Demon Core Essence guarantees T1 Fire Damage on Weapons."""
        sword = CraftingItem(
            item_uuid="boss_sword_01",
            base_name="Liệt Hỏa Ma Kiếm",
            item_level=85,
            slot=ItemSlot.WEAPON_1H,
            rarity=ItemRarity.RARE,
        )
        ok, msg, crafted = self.engine.apply_pinnacle_material(sword, PrimalCurrencyType.TINH_HUYET_VIEM_QUY)
        self.assertTrue(ok, msg)
        has_t1_fire = any(p.mod_name.startswith("Liệt Diễm Thiêu Hồn") and p.tier == 1 for p in crafted.prefixes)
        self.assertTrue(has_t1_fire, "Weapon must have T1 Fire damage guaranteed.")

    def test_pinnacle_black_dragon_marrow_fractures_affix(self):
        """Black Dragon Marrow locks 1 affix permanently as Fractured."""
        item = CraftingItem(
            item_uuid="fracture_target",
            base_name="Thiên Ma Cốt Giáp",
            item_level=88,
            slot=ItemSlot.BODY_ARMOUR,
            rarity=ItemRarity.RARE,
        )
        item.prefixes.append(AffixMod("pref_life_t1", "Bất Diệt Huyết Mạch (T1)", AffixType.PREFIX, 1, 145))
        item.suffixes.append(AffixMod("suff_fire_res_t1", "Kháng Hỏa Chân Thể % (T1)", AffixType.SUFFIX, 1, 48))

        ok, msg, item = self.engine.apply_pinnacle_material(item, PrimalCurrencyType.COT_TUY_HAC_LONG)
        self.assertTrue(ok, msg)

        fractured_mods = [m for m in item.prefixes + item.suffixes if m.is_fractured]
        self.assertEqual(len(fractured_mods), 1)

        # Chaos reroll MUST preserve the fractured mod!
        fractured_name = fractured_mods[0].mod_name
        ok, msg, rerolled = self.engine.apply_currency(item, PrimalCurrencyType.HUYET_HON_THACH)
        self.assertTrue(ok, msg)
        all_mod_names = [m.mod_name for m in rerolled.prefixes + rerolled.suffixes]
        self.assertIn(fractured_name, all_mod_names)

    def test_quest_gated_primal_boneforge_tradeoff_crafting(self):
        """
        Primal Boneforge Altar requires quest completion and attaches severe downsides
        to avoid overpowered exploits.
        """
        armor = CraftingItem(
            item_uuid="god_armor_01",
            base_name="Vô Địch Cốt Thần Giáp",
            item_level=90,
            slot=ItemSlot.BODY_ARMOUR,
            rarity=ItemRarity.RARE,
        )

        # 1. Without completed quest -> REJECTED
        uncompleted_quests = {"quest_norm_01_thuc_tinh_tang_kiem"}
        ok, msg, _ = self.engine.apply_boneforge_tradeoff_craft(
            armor, "curse_titan_carapace", uncompleted_quests
        )
        self.assertFalse(ok)
        self.assertIn("PREREQUISITE_QUEST_NOT_COMPLETED", msg)

        # 2. With Epic Quest completed -> SUCCESS
        completed_quests = {"quest_epic_01_phong_ma_co_tran"}
        ok, msg, forged_armor = self.engine.apply_boneforge_tradeoff_craft(
            armor, "curse_titan_carapace", completed_quests
        )
        self.assertTrue(ok, msg)

        # Verify mod details and attached downside penalty
        applied_tradeoff = next((p for p in forged_armor.prefixes if p.is_downside), None)
        self.assertIsNotNone(applied_tradeoff)
        self.assertEqual(applied_tradeoff.downside_type, DownsidePenaltyType.CANNOT_EVADE_OR_DASH)
        self.assertIn("cấm hoàn toàn Thân Pháp", applied_tradeoff.downside_desc)

    def test_feral_corruption_permanent_annihilation_sink(self):
        """Validates Feral Corruption (Vaal / Primal Orb) including 25% annihilation."""
        destroyed_count = 0
        corrupted_count = 0
        iterations = 100

        for i in range(iterations):
            item = CraftingItem(
                item_uuid=f"corrupt_test_{i}",
                base_name="Tế Phẩm Đao",
                item_level=85,
                slot=ItemSlot.WEAPON_1H,
                rarity=ItemRarity.RARE,
            )
            item.prefixes.append(AffixMod("pref_phys_t1", "Phys Dmg", AffixType.PREFIX, 1, 100))

            ok, msg, res_item = self.engine.apply_currency(item, PrimalCurrencyType.TA_HUYET_DI_BIEN_THACH)
            self.assertTrue(ok)
            self.assertTrue(res_item.is_corrupted)

            if res_item.is_destroyed:
                destroyed_count += 1
                self.assertEqual(msg, "PERMANENT_ANNIHILATION")
                self.assertEqual(len(res_item.prefixes), 0)
            else:
                corrupted_count += 1

            # Corrupted item cannot be modified further
            fail, fail_msg, _ = self.engine.apply_currency(res_item, PrimalCurrencyType.HUYET_HON_THACH)
            self.assertFalse(fail)

        # Over 100 trials, both annihilation and survival must occur
        self.assertGreater(destroyed_count, 10, f"Expected ~25% annihilation, got {destroyed_count}")
        self.assertGreater(corrupted_count, 40, f"Expected ~75% survival, got {corrupted_count}")

    def test_quality_currencies(self):
        """Tests Whetstone and Armour Scrap quality enhancement up to 20%."""
        sword = CraftingItem("sword_q", "Thanh Phong Kiếm", 80, ItemSlot.WEAPON_1H, ItemRarity.NORMAL)
        # Normal weapon +5% quality per whetstone
        ok, msg, sword = self.engine.apply_currency(sword, PrimalCurrencyType.HAC_THACH_MAI_NHAN)
        self.assertTrue(ok)
        self.assertEqual(sword.quality, 5)

        # Whetstone on Body Armour must fail
        chest = CraftingItem("chest_q", "Hắc Bì Giáp", 80, ItemSlot.BODY_ARMOUR, ItemRarity.NORMAL)
        fail, msg, _ = self.engine.apply_currency(chest, PrimalCurrencyType.HAC_THACH_MAI_NHAN)
        self.assertFalse(fail)
        self.assertIn("INCOMPATIBLE_SLOT", msg)

        # Armour scrap on Body Armour succeeds (+5% on Normal)
        ok, msg, chest = self.engine.apply_currency(chest, PrimalCurrencyType.COT_GIAP_THAO_THIET)
        self.assertTrue(ok)
        self.assertEqual(chest.quality, 5)

    def test_alteration_and_scour_cycle(self):
        """Tests Scour resetting Rare to Normal and Alteration rerolling Magic."""
        rare_item = CraftingItem("rare_reforge", "Bá Vương Kiếm", 85, ItemSlot.WEAPON_1H, ItemRarity.RARE, quality=20)
        rare_item.prefixes.append(AffixMod("pref_bad", "Bad Mod", AffixType.PREFIX, 5, 10))
        rare_item.suffixes.append(AffixMod("suff_frac", "Fractured God Mod", AffixType.SUFFIX, 1, 50, is_fractured=True))

        # Scour resets to Normal, preserves fractured mod and quality!
        ok, msg, scoured = self.engine.apply_currency(rare_item, PrimalCurrencyType.TRO_XUONG_TAY_UE)
        self.assertTrue(ok)
        self.assertEqual(scoured.rarity, ItemRarity.NORMAL)
        self.assertEqual(scoured.quality, 20)
        self.assertEqual(len(scoured.prefixes), 0)
        self.assertEqual(len(scoured.suffixes), 1)
        self.assertTrue(scoured.suffixes[0].is_fractured)

        # Transmute -> Magic
        ok, msg, magic_item = self.engine.apply_currency(scoured, PrimalCurrencyType.HAC_SO_THACH)
        self.assertTrue(ok)
        self.assertEqual(magic_item.rarity, ItemRarity.MAGIC)

        # Alteration rerolls Magic item
        ok, msg, altered_item = self.engine.apply_currency(magic_item, PrimalCurrencyType.BAO_MA_THACH)
        self.assertTrue(ok)
        self.assertEqual(altered_item.rarity, ItemRarity.MAGIC)
        self.assertTrue(any(s.is_fractured for s in altered_item.suffixes))


if __name__ == "__main__":
    unittest.main()
