"""
Unit tests for Chân Võ Đan & Thần Biến Phù (Martial Matrix Engine) in FreeExile.
Verifies:
1. Socketing active martial skills & linking augmenting sigils.
2. Tag compatibility enforcement (cannot link incompatible sigils).
3. PoE2 Trade-Off mechanics (Upside always accompanied by Downside).
4. Character Loadout & Attributes scaling (Cương Thể, Thân Pháp, Thần Niệm).
5. Song Binh Tự Động Hoán Đổi (Dual Weapon Auto-Swap in 0ms).
6. Recoil damage, resource conversion (Blood Magic), and intrinsic skill restrictions.
"""

import unittest
import sys
import os

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

from world.martial_matrix import (
    MartialMatrix,
    ChanVoDan,
    DiBienTaAn,
    FiveElements,
    MartialSect,
    SkillTag,
    WeaponCategory,
    WeaponItem,
    CharacterLoadout,
    SigilAugmentType,
    create_standard_active_skills,
    create_standard_augment_sigils
)


class TestMartialMatrix(unittest.TestCase):
    def setUp(self):
        self.matrix = MartialMatrix(max_active_slots=6)
        self.skills_catalog = create_standard_active_skills()
        self.sigils_catalog = create_standard_augment_sigils()

        self.char = CharacterLoadout(
            actor_id=101,
            name="Tiêu Nhất Kiếm",
            sect=MartialSect.THANH_PHONG,
            cuong_the=60,
            than_phap=80,
            than_niem=40,
            current_hp=1200.0,
            max_hp=1200.0,
            current_qi=600.0,
            max_qi=600.0,
            ward_barrier=250.0,
            max_ward_barrier=250.0,
            active_weapon_set=1,
            weapon_set_1=WeaponItem(weapon_id=1, name="Bích Thủy Kiếm", category=WeaponCategory.SWORD),
            weapon_set_2=WeaponItem(weapon_id=2, name="Huyền Thiết Bổng", category=WeaponCategory.STAFF)
        )

    def test_socket_active_chan_vo_dan(self):
        """Test socketing standard skill into slot 0."""
        skill = self.skills_catalog[1001] # Cửu Tiêu Lôi Kiếm
        success = self.matrix.socket_active_skill(slot_index=0, skill=skill)
        self.assertTrue(success)
        self.assertEqual(self.matrix.get_active_skill(0), skill)

    def test_tag_compatibility_enforcement(self):
        """Sigil requiring PROJECTILE must NOT link to a purely MELEE skill."""
        # Skill 1007: Liệt Hỏa Toàn Phong Trảm (MELEE, AOE, CHANNELING, FIRE)
        melee_skill = self.skills_catalog[1007]
        self.matrix.socket_active_skill(slot_index=1, skill=melee_skill)

        # Sigil 2001: Phân Quang Đa Hóa Phù (Requires PROJECTILE)
        projectile_sigil = self.sigils_catalog[2001]
        success, msg = self.matrix.link_sigil_to_skill(slot_index=1, sigil=projectile_sigil)
        self.assertFalse(success)
        self.assertIn("không tương thích", msg)

        # Sigil 2002: Bộc Phá Khuếch Đại Phù (Requires AOE) -> Compatible!
        aoe_sigil = self.sigils_catalog[2002]
        success_aoe, msg_aoe = self.matrix.link_sigil_to_skill(slot_index=1, sigil=aoe_sigil)
        self.assertTrue(success_aoe)
        self.assertEqual(len(melee_skill.linked_sigils), 1)

    def test_multi_projectile_trade_off(self):
        """
        Phân Quang Đa Hóa Phù:
        Upside: +3 projectiles.
        Downside: -35% less damage, +45% energy cost.
        """
        # Skill 1001: Cửu Tiêu Lôi Kiếm (PROJECTILE, base_damage=280, energy=45)
        skill = self.skills_catalog[1001]
        self.matrix.socket_active_skill(slot_index=0, skill=skill)

        sigil = self.sigils_catalog[2001]
        success, _ = self.matrix.link_sigil_to_skill(slot_index=0, sigil=sigil)
        self.assertTrue(success)

        stats = self.matrix.compute_modified_skill(slot_index=0)
        self.assertIsNotNone(stats)
        # 1 base + 3 bonus = 4 projectiles
        self.assertEqual(stats["projectile_count"], 4)
        # 280 * 0.65 = 182.0 effective damage
        self.assertAlmostEqual(stats["effective_damage"], 182.0, places=1)
        # 45 * 1.45 = 65.25 -> 65 energy
        self.assertEqual(stats["cost_value"], 65)
        self.assertEqual(stats["cost_type"], "CHÂN_KHÍ")

    def test_blood_magic_resource_conversion_and_ward_disabled(self):
        """
        Huyết Khí Quy Tông Phù:
        Upside: +25% damage.
        Downside: Converts cost to Life (Khí Huyết) and disables Ward Barrier completely.
        """
        skill = self.skills_catalog[1008] # Liệt Diễm Bộc Khí Trảm
        self.matrix.socket_active_skill(slot_index=2, skill=skill)

        sigil = self.sigils_catalog[2004] # Huyết Khí Quy Tông
        self.matrix.link_sigil_to_skill(slot_index=2, sigil=sigil)

        stats = self.matrix.compute_modified_skill(slot_index=2)
        self.assertIsNotNone(stats)
        self.assertEqual(stats["cost_type"], "KHÍ_HUYẾT")
        self.assertTrue(stats["ward_disabled"])
        # 320 * 1.25 = 400.0 damage
        self.assertAlmostEqual(stats["effective_damage"], 400.0, places=1)

    def test_seven_fists_martyr_recoil_damage(self):
        """
        Thất Thương Đoạt Mệnh Phù:
        Upside: +120% Crit Multiplier, +15% damage.
        Downside: 12% Recoil damage onto caster.
        """
        skill = self.skills_catalog[1001] # Cửu Tiêu Lôi Kiếm (STRIKE)
        self.matrix.socket_active_skill(slot_index=0, skill=skill)

        sigil = self.sigils_catalog[2008] # Thất Thương Đoạt Mệnh
        success, _ = self.matrix.link_sigil_to_skill(slot_index=0, sigil=sigil)
        self.assertTrue(success)

        stats = self.matrix.compute_modified_skill(slot_index=0)
        self.assertIsNotNone(stats)
        self.assertEqual(stats["recoil_damage_percent"], 0.12)
        # Base 1.50 + 1.20 = 2.70
        self.assertEqual(stats["crit_multiplier"], 2.70)

    def test_pure_physical_disables_elemental(self):
        """
        Khắc Cương Liệt Khí Phù:
        Upside: +50% Physical damage.
        Downside: Disables all elemental damage.
        """
        skill = self.skills_catalog[1009] # Kim Cang Trụy Địa Chấn (PHYSICAL)
        self.matrix.socket_active_skill(slot_index=3, skill=skill)

        sigil = self.sigils_catalog[2009] # Khắc Cương Liệt Khí
        success, _ = self.matrix.link_sigil_to_skill(slot_index=3, sigil=sigil)
        self.assertTrue(success)

        stats = self.matrix.compute_modified_skill(slot_index=3)
        self.assertIsNotNone(stats)
        self.assertTrue(stats["elemental_disabled"])
        self.assertAlmostEqual(stats["effective_damage"], 160.0 * 1.50, places=1)

    def test_character_attributes_scaling(self):
        """
        Character attributes scaling:
        cuong_the=60 -> +12% Melee damage.
        than_phap=80 -> +12% cast speed, +8% crit chance.
        than_niem=40 -> +8% elemental/spell damage.
        """
        skill = self.skills_catalog[1007] # Liệt Hỏa Toàn Phong Trảm (MELEE, FIRE)
        self.matrix.socket_active_skill(slot_index=4, skill=skill)

        stats_unscaled = self.matrix.compute_modified_skill(slot_index=4, character=None)
        stats_scaled = self.matrix.compute_modified_skill(slot_index=4, character=self.char)

        self.assertIsNotNone(stats_unscaled)
        self.assertIsNotNone(stats_scaled)

        # Scaled damage should be higher than unscaled base damage
        self.assertGreater(stats_scaled["effective_damage"], stats_unscaled["effective_damage"])
        # Scaled crit chance should be higher due to Thân Pháp
        self.assertGreater(stats_scaled["crit_chance"], stats_unscaled["crit_chance"])
        # Cast time should be reduced due to higher cast speed
        self.assertLessEqual(stats_scaled["cast_time_ms"], stats_unscaled["cast_time_ms"])

    def test_song_binh_auto_weapon_swap_success(self):
        """
        Character has Sword in Set 1, Staff in Set 2. Active is Set 1.
        Casting Staff skill (Băng Phách Hàn Quang Trận) should automatically swap to Set 2 in 0ms!
        """
        staff_skill = self.skills_catalog[1005] # Requires STAFF
        self.assertEqual(self.char.active_weapon_set, 1)

        success, new_set, msg = self.matrix.resolve_weapon_swap(self.char, staff_skill)
        self.assertTrue(success)
        self.assertEqual(new_set, 2)
        self.assertEqual(self.char.active_weapon_set, 2)
        self.assertIn("Tự động hoán đổi sang Bộ Vũ Khí 2", msg)

    def test_song_binh_weapon_swap_incompatible_rejection(self):
        """
        Character has Sword and Staff.
        Casting Blade skill (Liệt Hỏa Toàn Phong Trảm - Requires BLADE) must fail.
        """
        blade_skill = self.skills_catalog[1007]
        success, active_set, msg = self.matrix.resolve_weapon_swap(self.char, blade_skill)
        self.assertFalse(success)
        self.assertIn("đều không thỏa mãn yêu cầu", msg)

    def test_intrinsic_skill_trade_offs(self):
        """
        Verify inherent skill trade-offs:
        1. U Minh Huyết Độc Châm has 0 direct damage, dot_multiplier=1.5, speed_penalty=20%.
        2. Băng Phách Hàn Quang Trận cannot crit (cannot_crit=True).
        3. Kim Cang Bất Hoại Thể immobilizes caster (immobilized_during_effect=True).
        """
        # 1. DoT Poison Skill
        poison_skill = self.skills_catalog[1003]
        self.matrix.socket_active_skill(slot_index=0, skill=poison_skill)
        stats_poison = self.matrix.compute_modified_skill(slot_index=0)
        self.assertEqual(stats_poison["effective_damage"], 0.0)
        self.assertEqual(stats_poison["dot_multiplier"], 1.50)
        self.assertEqual(stats_poison["speed_penalty_percent"], 0.20)

        # 2. Freeze Arena Skill
        freeze_skill = self.skills_catalog[1005]
        self.matrix.socket_active_skill(slot_index=1, skill=freeze_skill)
        stats_freeze = self.matrix.compute_modified_skill(slot_index=1)
        self.assertEqual(stats_freeze["crit_chance"], 0.0)

        # 3. Defensive Fortress Skill
        fortress_skill = self.skills_catalog[1010]
        self.matrix.socket_active_skill(slot_index=2, skill=fortress_skill)
        stats_fortress = self.matrix.compute_modified_skill(slot_index=2)
        self.assertTrue(stats_fortress["immobilized_during_effect"])


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