"""
Unit tests for 15-Tier Item Affix Matrix (T1 to T15) and Exponential Crafting Currency & Gold Sinks.
Verifies iLvl gating, tier range rolling, gold fee scaling, and high-tier rarity.
"""

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


class TestItemCrafting15Tiers(unittest.TestCase):
    def setUp(self) -> None:
        self.engine = ItemCraftingEngine()

    def test_catalog_contains_15_tiers_for_core_affixes(self) -> None:
        """Catalog must define T1 through T15 for core stat families."""
        phys_affixes = [a for a in CANONICAL_AFFIX_DEFINITIONS if a.mod_id.startswith("pref_phys_pct_t")]
        self.assertEqual(len(phys_affixes), 15, "Physical damage % must have exactly 15 tiers (T1 to T15)")

        tiers = {a.tier for a in phys_affixes}
        self.assertEqual(tiers, set(range(1, 16)), "Tiers must span 1 to 15 inclusive")

    def test_tier_1_requires_high_item_level_and_has_highest_stats(self) -> None:
        """T1 requires high iLvl (85+) and has superior min/max stats compared to T15."""
        t1 = next(a for a in CANONICAL_AFFIX_DEFINITIONS if a.mod_id == "pref_phys_pct_t1")
        t15 = next(a for a in CANONICAL_AFFIX_DEFINITIONS if a.mod_id == "pref_phys_pct_t15")

        self.assertGreaterEqual(t1.required_ilvl, 85)
        self.assertEqual(t15.required_ilvl, 1)

        # T1 stats must be much higher than T15
        self.assertGreater(t1.min_val, t15.max_val)
        self.assertGreater(t1.max_val, t15.max_val)

        # T1 must be significantly rarer (lower roll weight) than T15
        self.assertLess(t1.weight, t15.weight)

    def test_low_ilvl_item_cannot_roll_t1_affix(self) -> None:
        """An item with iLvl 15 can only roll low tiers (e.g. T14-T15), never T1."""
        available = get_available_affixes(ItemSlot.WEAPON_1H, AffixType.PREFIX, item_level=15)
        available_tiers = {a.tier for a in available if a.mod_id.startswith("pref_phys_pct_t")}

        self.assertNotIn(1, available_tiers, "iLvl 15 weapon must not roll T1")
        self.assertNotIn(2, available_tiers, "iLvl 15 weapon must not roll T2")
        self.assertTrue(all(t >= 14 for t in available_tiers), "Only lowest tiers eligible for iLvl 15")

    def test_high_ilvl_item_rolls_value_strictly_within_chosen_tier_range(self) -> None:
        """When an affix rolls, the value must fall strictly within [min_val, max_val] of that tier."""
        item = CraftingItem(
            item_uuid="weapon_god_base",
            base_name="Huyết Ảnh Tà Đao",
            slot=ItemSlot.WEAPON_1H,
            item_level=88,
            rarity=ItemRarity.NORMAL,
        )

        res = self.engine.apply_primal_currency(
            item=item,
            currency=PrimalCurrencyType.HAC_SO_THACH,
            player_gold=50_000,
        )
        self.assertTrue(res.success)
        self.assertGreater(len(res.item.prefixes) + len(res.item.suffixes), 0)

        for mod in res.item.prefixes + res.item.suffixes:
            matching_def = next((a for a in CANONICAL_AFFIX_DEFINITIONS if a.mod_name == mod.mod_name), None)
            if matching_def:
                self.assertGreaterEqual(mod.value, matching_def.min_val)
                self.assertLessEqual(mod.value, matching_def.max_val)

    def test_crafting_gold_fee_scales_exponentially_with_item_level(self) -> None:
        """Crafting fee scales with item level to increase gold sink on endgame gear."""
        fee_low = calculate_crafting_gold_fee(PrimalCurrencyType.HUYET_HON_THACH, item_level=20)
        fee_high = calculate_crafting_gold_fee(PrimalCurrencyType.HUYET_HON_THACH, item_level=86)

        self.assertGreater(fee_high, fee_low * 3, "High iLvl crafting gold fee must be substantially higher")

    def test_crafting_rejected_if_player_has_insufficient_gold(self) -> None:
        """Crafting requires gold fee; fails if player lacks required gold."""
        item = CraftingItem(
            item_uuid="expensive_base",
            base_name="Cổ Kiếm Thần Ma",
            slot=ItemSlot.WEAPON_1H,
            item_level=85,
            rarity=ItemRarity.RARE,
        )

        # Player only has 5 gold (fee is > 2,000 gold)
        res = self.engine.apply_primal_currency(
            item=item,
            currency=PrimalCurrencyType.HUYET_HON_THACH,
            player_gold=5,
        )
        self.assertFalse(res.success)
        self.assertIn("INSUFFICIENT_GOLD_FEE", res.message)

    def test_divine_rerolls_within_tier_range(self) -> None:
        """Tinh Hoa Huyết Thạch (Divine) rerolls values strictly within the existing tier boundaries."""
        item = CraftingItem(
            item_uuid="test_divine_item",
            base_name="Cốt Khôi",
            slot=ItemSlot.HELMET,
            item_level=85,
            rarity=ItemRarity.RARE,
        )
        # Seed with a T1 life mod with minimum roll
        t1_life_def = next(a for a in CANONICAL_AFFIX_DEFINITIONS if a.mod_id == "pref_life_t1")
        item.prefixes.append(
            AffixMod(
                mod_id=t1_life_def.mod_id,
                mod_name=t1_life_def.mod_name,
                mod_type=AffixType.PREFIX,
                tier=1,
                value=t1_life_def.min_val,
                tier_min=t1_life_def.min_val,
                tier_max=t1_life_def.max_val,
            )
        )

        res = self.engine.apply_primal_currency(
            item=item,
            currency=PrimalCurrencyType.TINH_HOA_HUYET_THACH,
            player_gold=50_000,
        )
        self.assertTrue(res.success)
        divined_mod = res.item.prefixes[0]
        self.assertEqual(divined_mod.tier, 1, "Divine must preserve Tier")
        self.assertGreaterEqual(divined_mod.value, t1_life_def.min_val)
        self.assertLessEqual(divined_mod.value, t1_life_def.max_val)


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