"""
Unit tests for Character Animation State Machine and 3 Distinct Martial Skill VFX Systems in FreeExile.
Verifies:
1. Animation State Machine: automatic recovery from 'attack' and 'cast' back to 'idle'/'run' (zero freeze).
2. 8-directional isometric kinematics and velocity tilt/bobbing calculations.
3. 3 Distinct Martial Skill Systems:
   - Fire: Infernal Crescent Slash Wave (vfx_slashes, ground scorch, conflagration blast)
   - Thunder: Heavenly Lightning Bolt (vfx_lightnings, procedural branches, shockwave ring)
   - Frost: Sub-Zero Glacial Spikes Nova (vfx_spikes, crystal facets, frost shatter shards)
4. martial_skills_atlas asset registration and metadata loading.
5. Dynamic Screen Shake camera transformation and high-fidelity Ghost Trail afterimages.
"""

import unittest
import os
import re
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
WEBAPP_DIR = PROJECT_ROOT / "client" / "webapp"
INDEX_HTML = WEBAPP_DIR / "index.html"


class TestCharacterAnimationAndSkillsVFX(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.assertTrue(INDEX_HTML.exists(), "client/webapp/index.html must exist")
        # Load index.html plus all modular JS and CSS files in client/webapp bundle
        parts = []
        with open(INDEX_HTML, "r", encoding="utf-8") as f:
            parts.append(f.read())
        for js_file in sorted((WEBAPP_DIR / "js").glob("**/*.js")):
            with open(js_file, "r", encoding="utf-8") as f:
                parts.append(f.read())
        for css_file in sorted((WEBAPP_DIR / "css").glob("**/*.css")):
            with open(css_file, "r", encoding="utf-8") as f:
                parts.append(f.read())
        cls.html = "\n".join(parts)

    def test_martial_skills_atlas_registered_in_asset_list(self):
        """martial_skills_atlas must be registered in ASSETS preloader."""
        self.assertIn("martial_skills_atlas", self.html, "martial_skills_atlas must be registered in ASSETS assetList")
        self.assertIn("./assets/skills/martial_skills_atlas.png", self.html)

    def test_animation_state_machine_attack_auto_recovery(self):
        """Attack state must have an automatic recovery timer to prevent permanent freezing."""
        # Must have attackTimer or attackDuration logic that resets animState back to idle/run
        has_attack_timer = "attackTimer" in self.html or "attackDuration" in self.html
        self.assertTrue(has_attack_timer, "Player must have an attackTimer to automatically recover from attack state")

        # Must have logic resetting player.animState when attack duration finishes
        self.assertRegex(
            self.html,
            r"player\.animState\s*=\s*(mag\s*>\s*0\.05|\(?\w+\s*>\s*0\.05\)?)\s*\?\s*['\"]run['\"]\s*:\s*['\"]idle['\"]",
            "Attack state must automatically transition to 'run' or 'idle' when attack timer completes"
        )

    def test_directional_kinematics_and_facing(self):
        """Must calculate 8-directional isometric facing and body lean tilt during running."""
        # Must support heading / angle calculations for directional combat and movement
        has_heading = "facingAngle" in self.html or "heading8Dir" in self.html or "targetAngle" in self.html
        self.assertTrue(has_heading, "Must compute directional angle for 8-way isometric facing")

        # Must have dynamic velocity tilt or footstep dust
        has_tilt_or_dust = "leanAngle" in self.html or "footstep" in self.html or "footstepDust" in self.html
        self.assertTrue(has_tilt_or_dust, "Must implement dynamic body lean or footstep dust when running")

    def test_three_distinct_martial_skill_systems(self):
        """Must implement 3 completely distinct VFX systems for Fire, Thunder, and Frost skills."""
        # 1. Fire: Infernal Crescent Slash Wave
        has_slash_wave = "vfx_slashes" in self.html or "spawnInfernalSlash" in self.html or "castFireSlash" in self.html
        self.assertTrue(has_slash_wave, "Fire skill must spawn an Infernal Crescent Slash Wave, not generic dots")

        # 2. Thunder: Celestial Lightning Bolt
        has_lightning_bolt = "vfx_lightnings" in self.html or "spawnLightningStrike" in self.html or "castThunderSmite" in self.html
        self.assertTrue(has_lightning_bolt, "Thunder skill must spawn a Celestial Lightning Bolt from heaven")

        # 3. Frost: Glacial Spikes Nova
        has_glacial_spikes = "vfx_spikes" in self.html or "spawnGlacialSpikes" in self.html or "castFrostNova" in self.html
        self.assertTrue(has_glacial_spikes, "Frost skill must spawn Glacial Spikes erupting from the ground")

    def test_screen_shake_camera_system(self):
        """Must implement dynamic screen shake camera controller for impactful combat feel."""
        self.assertIn("screenShake", self.html, "Must have screenShake camera system")
        self.assertIn("triggerScreenShake", self.html, "Must have triggerScreenShake function")

    def test_high_fidelity_ghost_trails(self):
        """Phantom evasion (dodge) must render character silhouette ghost trails, not simple colored dots."""
        # Check that ghost trails render using hero sprite or silhouette transform
        has_silhouette = "drawImage" in self.html and ("ghostTrails" in self.html or "ghost" in self.html)
        self.assertTrue(has_silhouette, "Ghost trails must render sprite silhouette for authentic PoE2 phantom evasion")

    def test_origin_target_flying_projectiles_trajectory(self):
        """Combat skills must calculate origin (caster) to target trajectory with flight progress."""
        # Must track origin and target coordinates
        self.assertIn("originX", self.html, "Projectiles must define originX")
        self.assertIn("targetX", self.html, "Projectiles must define targetX")
        self.assertIn("progress", self.html, "Projectiles must interpolate with progress along trajectory")
        self.assertIn("duration", self.html, "Projectiles must have flight duration based on distance")

    def test_delayed_impact_damage_on_arrival(self):
        """Monster impact damage and reactions must trigger upon projectile arrival at target, not immediately."""
        self.assertIn("progress >= 1.0", self.html, "Impact damage must trigger when projectile progress reaches 1.0")
        self.assertIn("impactTriggered", self.html, "Must guard against duplicate impact triggering")

    def test_monster_status_vfx_feedback(self):
        """Monster must display distinct status reactions for burning, shock, and chill/freeze."""
        has_status = "monster.burnTimer" in self.html or "monster.shockTimer" in self.html or "monster.freezeTimer" in self.html or "monsterStatus" in self.html
        self.assertTrue(has_status, "Monster must react with distinct status VFX (burn, shock, freeze) to skills")

    def test_savage_primal_exile_character_assets_and_naming(self):
        """Character must be styled and named as Savage Exile, eliminating traditional wuxia swordsman."""
        # Must register savage_primal_exile asset
        self.assertIn("savage_primal_exile", self.html, "savage_primal_exile must be registered in ASSETS")
        # Must define localized exile name in Vietnamese and English
        self.assertIn("Kẻ Bị Lưu Đày", self.html, "Character name must be 'Kẻ Bị Lưu Đày'")
        self.assertIn("The Forsaken Exile", self.html, "Character name must be 'The Forsaken Exile'")


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