"""
Unit Test Suite for FreeExile PC Desktop Input Controller (client/web_pc/js/pc_input_controller.js).
Verifies:
- Kinematics & 2:1 isometric coordinate transformation invariants
- Click-to-Move arrival threshold (<= 0.22) and continuous Hold-to-Move logic
- Decoupling of key 'W' from movement and mapping to Martial Skill 2
- 1-5 Survival Flask properties and cooldowns
- Huyễn Ảnh Bộ (Spacebar dodge) 0.25s i-frame window
- Input field typing focus exclusion guard
- Code hygiene & line cap limits (<= 350 lines soft cap)
"""

from __future__ import annotations
import math
import re
import sys
from pathlib import Path
import pytest

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
PC_INPUT_JS = PROJECT_ROOT / "client" / "web_pc" / "js" / "pc_input_controller.js"


class TestPcInputControllerUnit:
    """Empirical unit tests for PC Input Controller kinematics and contracts."""

    def test_file_exists_and_hygiene(self) -> None:
        """Verifies pc_input_controller.js exists and complies with line caps."""
        assert PC_INPUT_JS.exists(), "pc_input_controller.js must exist"
        lines = PC_INPUT_JS.read_text(encoding="utf-8").splitlines()
        assert len(lines) <= 350, f"File length {len(lines)} exceeds 350 lines soft cap"
        assert len(lines) >= 80, "File should have substantive logic"

    def test_wasd_decoupling_and_w_key_isolation(self) -> None:
        """Verifies window.enableWasdMovement is set false and W is mapped to skill."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        assert "window.enableWasdMovement = false" in content
        assert "window.keys = {}" in content
        # Key W triggers doThunder / martial skill, not movement
        assert "key === 'W'" in content
        assert "doThunder" in content

    def test_keybinding_registration(self) -> None:
        """Verifies all required PC ARPG hotkeys are handled in onKeyDown."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        # Flasks 1-5
        for num in ["1", "2", "3", "4", "5"]:
            assert f"'{num}'" in content
        # Martial skills Q, W, E, R
        for key in ["Q", "W", "E", "R"]:
            assert f"'{key}'" in content
        # Dodge
        assert "Space" in content
        # Modals
        assert "'I'" in content
        assert "'C'" in content
        assert "'ESCAPE'" in content

    def test_click_to_move_arrival_kinematics(self) -> None:
        """Verifies arrival threshold mathematical formula (0.22 world units)."""
        # Mathematical simulation of arrival threshold
        player_wx, player_wy = 10.0, 10.0
        # Point far away (> 0.22)
        dest_wx, dest_wy = 10.5, 10.5
        dist = math.hypot(dest_wx - player_wx, dest_wy - player_wy)
        assert dist > 0.22, "Destination must be active when distance > 0.22"
        mx = (dest_wx - player_wx) / dist
        my = (dest_wy - player_wy) / dist
        assert math.isclose(math.hypot(mx, my), 1.0, rel_tol=1e-5)

        # Point close by (<= 0.22)
        close_wx, close_wy = 10.1, 10.1
        close_dist = math.hypot(close_wx - player_wx, close_wy - player_wy)
        assert close_dist <= 0.22, "Destination should snap inactive when <= 0.22"

    def test_continuous_hold_to_move_logic(self) -> None:
        """Verifies isLmbHeld state and pointermove continuous update."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        assert "isLmbHeld: false" in content
        assert "this.isLmbHeld = true" in content
        assert "this.isLmbHeld = false" in content
        assert "this.executeMoveTo(this.cursorWorld.wx, this.cursorWorld.wy" in content

    def test_contextmenu_prevention_on_rmb(self) -> None:
        """Verifies right-click suppresses contextmenu and triggers primary skill."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        assert "e.preventDefault()" in content
        assert "contextmenu" in content
        assert "e.button === 2" in content
        assert "executePrimarySkill" in content

    def test_facing_angle_trigonometry(self) -> None:
        """Verifies Math.atan2 correctly calculates 8-directional facing angle."""
        player_x, player_y = 0.0, 0.0
        # Right (0 rad)
        assert math.isclose(math.atan2(0.0, 1.0), 0.0)
        # Down (pi/2 rad)
        assert math.isclose(math.atan2(1.0, 0.0), math.pi / 2)
        # Left (pi rad)
        assert math.isclose(math.atan2(0.0, -1.0), math.pi)
        # Up (-pi/2 rad)
        assert math.isclose(math.atan2(-1.0, 0.0), -math.pi / 2)

    def test_spacebar_dodge_iframe_window(self) -> None:
        """Verifies Space triggers 0.25s i-frame, dodge animState, and 3.0s cooldown."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        assert "doDodge" in content
        assert "player.isIFrame = true" in content
        assert "player.iFrameTimer = 0.25" in content
        assert "player.animState = 'dodge'" in content
        assert "triggerUiCooldown('dodge', 3.0)" in content

    def test_survival_flask_specifications(self) -> None:
        """Verifies all 5 flasks are specified with accurate durations and effects."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        # Flask 1: Health (+450 HP)
        assert "450" in content
        # Flask 2: Mana (+50 Qi)
        assert "50" in content
        # Flask 3: Quicksilver (+35% move speed -> 1.35)
        assert "1.35" in content
        # Flask 4: Granite (defenseBuff 0.5)
        assert "defenseBuff = 0.5" in content
        # Flask 5: Diamond (damageMultiplier 1.5)
        assert "damageMultiplier = 1.5" in content

    def test_input_typing_focus_guard(self) -> None:
        """Verifies isTypingFocus inspects activeElement tag to avoid hotkey misfires."""
        content = PC_INPUT_JS.read_text(encoding="utf-8")
        assert "isTypingFocus" in content
        assert "'INPUT'" in content
        assert "'TEXTAREA'" in content
        assert "isContentEditable" in content

    def test_isometric_math_roundtrip_invariance(self) -> None:
        """Verifies 2:1 isometric coordinate math round-trip consistency."""
        tile_w, tile_h = 64.0, 32.0
        cx, cy = 960.0, 540.0
        cam_x, cam_y = 5.0, 10.0

        for test_wx, test_wy in [(0.0, 0.0), (12.5, -8.3), (100.0, 250.0)]:
            # worldToIso
            rel_wx = test_wx - cam_x
            rel_wy = test_wy - cam_y
            sx = (rel_wx - rel_wy) * (tile_w / 2.0) + cx
            sy = (rel_wx + rel_wy) * (tile_h / 2.0) + cy

            # isoToWorld
            diff_sx = sx - cx
            diff_sy = sy - cy
            calc_rel_wx = (diff_sx / (tile_w / 2.0) + diff_sy / (tile_h / 2.0)) / 2.0
            calc_rel_wy = (diff_sy / (tile_h / 2.0) - diff_sx / (tile_w / 2.0)) / 2.0
            calc_wx = calc_rel_wx + cam_x
            calc_wy = calc_rel_wy + cam_y

            assert math.isclose(test_wx, calc_wx, abs_tol=1e-6)
            assert math.isclose(test_wy, calc_wy, abs_tol=1e-6)
