"""
Comprehensive Unit Test Suite for FreeExile Milestone 1:
- Dual Fluid HUD Orbs (Canvas 2D Sinusoidal Crest & Dynamic Vignette)
- Modern ARPG 7-Slot Action Bar & Radial Cooldown Sweeps
- Joystick PointerEvents & Dead-Zone Clamping Engine
- Backward Compatibility and File Hygiene Caps
"""

import unittest
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
WEBAPP_DIR = PROJECT_ROOT / "client" / "webapp"
INDEX_HTML = WEBAPP_DIR / "index.html"
HUD_ORBS_JS = WEBAPP_DIR / "js" / "ui" / "hud_orbs.js"
SKILL_BAR_JS = WEBAPP_DIR / "js" / "ui" / "skill_bar_controller.js"
HUD_SKILLS_CSS = WEBAPP_DIR / "css" / "hud_skills.css"
JOYSTICK_JS = WEBAPP_DIR / "js" / "engine" / "joystick.js"
COMBAT_SKILLS_JS = WEBAPP_DIR / "js" / "engine" / "combat_skills.js"


class TestHudDualOrbs(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.html_content = INDEX_HTML.read_text(encoding="utf-8")
        cls.orbs_js = HUD_ORBS_JS.read_text(encoding="utf-8")
        cls.css_content = HUD_SKILLS_CSS.read_text(encoding="utf-8")

    def test_dual_orbs_dom_elements_exist(self):
        """Verifies index.html contains all Dual Orbs DOM elements and links."""
        expected_ids = [
            "hud-orbs-container", "hud-life-orb", "hud-mana-orb",
            "life-orb-canvas", "mana-orb-canvas",
            "txt-life-cur", "txt-life-max", "txt-mana-cur", "txt-mana-max",
            "hud-low-hp-vignette"
        ]
        for eid in expected_ids:
            self.assertIn(f'id="{eid}"', self.html_content, f"Missing id '{eid}'")
        self.assertIn('href="css/hud_skills.css"', self.html_content)
        self.assertIn('src="js/ui/hud_orbs.js"', self.html_content)

    def test_hud_orbs_sinusoidal_wave_formula(self):
        """Verifies sinusoidal liquid wave crest sin(x * 0.05 + time * 3.0) formula."""
        self.assertIn("class HudOrbs", self.orbs_js)
        self.assertIn("sin(0 * 0.05 + time * 3.0)", self.orbs_js)
        self.assertIn("sin(x * 0.05 + time * 3.0)", self.orbs_js)
        self.assertIn("LERP_SPEED", self.orbs_js)

    def test_hud_orbs_low_hp_warning_and_pulse(self):
        """Verifies <25% low HP threshold and heartbeat pulse trigger."""
        self.assertIn("LOW_HP_THRESHOLD = 0.25", self.orbs_js)
        self.assertIn("orb-low-pulse", self.orbs_js)
        self.assertIn("@keyframes crimsonHeartbeat", self.css_content)
        self.assertIn("#hud-low-hp-vignette", self.css_content)

    def test_hud_orbs_canvas_and_retina_spec(self):
        """Verifies 180x180 retina canvas rendering, sheen, and bezel."""
        self.assertIn("CANVAS_SIZE = 180", self.orbs_js)
        self.assertIn("ORB_RADIUS = 78", self.orbs_js)
        self.assertIn(".orb-sheen", self.css_content)
        self.assertIn(".orb-bezel", self.css_content)


class TestSkillBarAndCooldownController(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.html_content = INDEX_HTML.read_text(encoding="utf-8")
        cls.bar_js = SKILL_BAR_JS.read_text(encoding="utf-8")
        cls.css_content = HUD_SKILLS_CSS.read_text(encoding="utf-8")

    def test_seven_action_slots_in_html(self):
        """Verifies all 7 action slots exist in index.html with backward compatibility."""
        slots = [
            "skill-potion", "skill-fire", "skill-slot-w",
            "skill-thunder", "skill-frost", "skill-primary", "skill-dodge"
        ]
        for slot in slots:
            self.assertIn(f'id="{slot}"', self.html_content, f"Missing slot '{slot}'")

    def test_skill_bar_controller_api_contract(self):
        """Verifies SkillBarController public API methods exist."""
        methods = [
            "registerSlot(", "triggerCooldown(", "onReady(",
            "activateSlot(", "isSlotOnCooldown(", "getRemainingCooldown("
        ]
        for m in methods:
            self.assertIn(m, self.bar_js, f"Missing method '{m}' in controller")

    def test_canonical_atlas_map_metadata(self):
        """Verifies all 10 canonical active skills are mapped in SKILL_ATLAS_MAP."""
        canonical_skills = [
            "cuu_tieu_loi_kiem", "thanh_phong_van_kiem_vu",
            "u_minh_huyet_doc_cham", "thien_ma_boc_doc_kinh",
            "bang_phach_han_quang_tran", "bang_tien_xuyen_tam",
            "liet_hoa_toan_phong_tram", "liet_diem_boc_khi_tram",
            "kim_cang_truy_dia_chan", "kim_cang_bat_hoai_the"
        ]
        for s in canonical_skills:
            self.assertIn(s, self.bar_js, f"Skill '{s}' missing from SKILL_ATLAS_MAP")

    def test_conic_radial_cooldown_sweep_and_counter(self):
        """Verifies 360-deg clockwise conic-gradient sweep and numeric counter."""
        self.assertIn(".cooldown-overlay", self.css_content)
        self.assertIn("conic-gradient", self.css_content)
        self.assertIn("--cooldown-sweep", self.css_content)
        self.assertIn(".cooldown-counter", self.css_content)

    def test_luminous_ready_flash_and_blocked_feedback(self):
        """Verifies 0.25s golden ready pulse and cooldown blocked shake."""
        self.assertIn("@keyframes skill-ready-pulse", self.css_content)
        self.assertIn("0.25s", self.css_content)
        self.assertIn("rgba(251, 191, 36", self.css_content)
        self.assertIn(".is-cooldown-blocked", self.css_content)
        self.assertIn("@keyframes skill-blocked-shake", self.css_content)


class TestJoystickAndInputErgonomics(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.joy_js = JOYSTICK_JS.read_text(encoding="utf-8")
        cls.combat_js = COMBAT_SKILLS_JS.read_text(encoding="utf-8")

    def test_pointer_events_and_capture(self):
        """Verifies W3C Pointer Events with setPointerCapture and active pointer tracking."""
        self.assertIn("pointerdown", self.joy_js)
        self.assertIn("setPointerCapture", self.joy_js)
        self.assertIn("activePointerId", self.joy_js)
        self.assertIn("releasePointerCapture", self.joy_js)

    def test_dead_zone_clamping(self):
        """Verifies dead-zone threshold > 0.15 is implemented."""
        self.assertIn("DEAD_ZONE = 0.15", self.joy_js)
        self.assertIn("JOYSTICK_DEAD_ZONE", self.joy_js)

    def test_backward_compatibility_handles(self):
        """Verifies joystickActive and handleJoystickPointerMove are preserved."""
        self.assertIn("joystickActive", self.joy_js)
        self.assertIn("handleJoystickPointerMove", self.joy_js)
        self.assertIn("doPrimaryAttack", self.combat_js)
        self.assertIn("dodgeCharges", self.combat_js)


class TestCodeHygieneAndLineCaps(unittest.TestCase):
    def test_file_line_caps(self):
        """Asserts all modified and created files comply with soft/hard caps."""
        caps = [
            (HUD_ORBS_JS, 350, "Soft Cap"),
            (SKILL_BAR_JS, 350, "Soft Cap"),
            (HUD_SKILLS_CSS, 350, "Soft Cap"),
            (JOYSTICK_JS, 350, "Soft Cap"),
            (COMBAT_SKILLS_JS, 500, "Hard Cap"),
            (INDEX_HTML, 400, "Hard Cap"),
        ]
        for path, max_lines, cap_name in caps:
            self.assertTrue(path.exists(), f"File {path.name} must exist")
            lines = path.read_text(encoding="utf-8").splitlines()
            self.assertLessEqual(
                len(lines), max_lines,
                f"{path.name} exceeds {cap_name} ({len(lines)} > {max_lines} lines)"
            )


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