"""
Unit tests for FreeExile Dedicated PC Desktop Full-Screen Web Client (client/web_pc)
and its development runner tools/serve_web_pc.py.
"""

import inspect
import os
from pathlib import Path
import re
import unittest

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
PC_DIR = PROJECT_ROOT / "client" / "web_pc"
TOOLS_DIR = PROJECT_ROOT / "tools"


class TestPCWebClientArtifacts(unittest.TestCase):
    """Verifies physical file layout, DOM contracts, and server integration."""

    def test_pc_web_client_files_exist(self):
        """All 7 required PC web client artifacts and dev runner must exist."""
        required_files = [
            PC_DIR / "index.html",
            PC_DIR / "css" / "pc_main.css",
            PC_DIR / "css" / "pc_hud.css",
            PC_DIR / "css" / "pc_modals.css",
            PC_DIR / "js" / "pc_input_controller.js",
            PC_DIR / "js" / "pc_hud_controller.js",
            PC_DIR / "js" / "pc_main.js",
            TOOLS_DIR / "serve_web_pc.py",
        ]
        for f in required_files:
            self.assertTrue(f.is_file(), f"Required file must exist: {f}")

    def test_serve_web_pc_configuration(self):
        """tools/serve_web_pc.py must default to port 8089 and define start_server."""
        import tools.serve_web_pc as serve_web_pc

        self.assertTrue(hasattr(serve_web_pc, "start_server"))
        sig = inspect.signature(serve_web_pc.start_server)
        self.assertIn("port", sig.parameters)
        self.assertEqual(sig.parameters["port"].default, 8089, "Default port for serve_web_pc must be 8089")

    def test_serve_web_pc_path_resolution(self):
        """FreeExilePCRequestHandler resolves PC root, PC css/js, and webapp shared assets."""
        from tools.serve_web_pc import FreeExilePCRequestHandler

        handler = FreeExilePCRequestHandler.__new__(FreeExilePCRequestHandler)

        # Root index
        resolved_root = handler._resolve_static_path("")
        self.assertIsNotNone(resolved_root)
        self.assertEqual(resolved_root.name, "index.html")
        self.assertEqual(resolved_root.parent, PC_DIR)

        # PC CSS
        resolved_css = handler._resolve_static_path("css/pc_main.css")
        self.assertIsNotNone(resolved_css)
        self.assertEqual(resolved_css, PC_DIR / "css" / "pc_main.css")

        # PC JS
        resolved_js = handler._resolve_static_path("js/pc_main.js")
        self.assertIsNotNone(resolved_js)
        self.assertEqual(resolved_js, PC_DIR / "js" / "pc_main.js")

        # WebApp shared asset
        resolved_asset = handler._resolve_static_path("js/data/i18n.js")
        self.assertIsNotNone(resolved_asset)
        self.assertTrue(resolved_asset.is_file())

    def test_index_html_base_href_and_zero_mobile_artifacts(self):
        """index.html must include <base href="../webapp/"> and zero mobile simulator artifacts."""
        html_path = PC_DIR / "index.html"
        content = html_path.read_text(encoding="utf-8")

        # Must have base href to webapp
        self.assertIn('<base href="../webapp/">', content, "Must declare base href to ../webapp/")

        # Must eliminate mobile frame and simulator bar
        self.assertNotIn("iphone-frame", content, "Must NOT contain mobile iphone-frame class")
        self.assertNotIn("simulator-bar", content, "Must NOT contain mobile simulator-bar element")

        # Must contain hidden dummy joystick guard to prevent canvas_renderer.js null dereference
        self.assertIn('id="joystick-zone"', content)
        self.assertIn('id="joystick-stick"', content)
        self.assertIn('id="joy-key-w"', content)
        self.assertIn("display: none;", content)

    def test_index_html_action_hud_and_dual_orbs(self):
        """index.html must have symmetrical dual fluid orbs and PoE2-style action bar."""
        content = (PC_DIR / "index.html").read_text(encoding="utf-8")

        # Dual fluid orbs
        self.assertIn('id="life-orb-canvas"', content)
        self.assertIn('id="mana-orb-canvas"', content)
        self.assertIn('id="txt-life-cur"', content)
        self.assertIn('id="txt-mana-cur"', content)

        # Flask slots 1 to 5
        for i in range(1, 6):
            self.assertIn(f'id="skill-flask-{i}"', content)

        # Skill slots QWER, LMB, RMB, Space
        self.assertIn('id="skill-q"', content)
        self.assertIn('id="skill-w"', content)
        self.assertIn('id="skill-e"', content)
        self.assertIn('id="skill-r"', content)
        self.assertIn('id="skill-lmb"', content)
        self.assertIn('id="skill-rmb"', content)
        self.assertIn('id="skill-dodge"', content)
        self.assertIn('id="player-exp-bar"', content)
        self.assertIn('id="badge-iframe"', content, "Must contain badge-iframe element to prevent canvas_renderer.js null dereference")

    def test_index_html_minimap_chat_and_telemetry(self):
        """index.html must contain 180x130 minimap, telemetry, and collapsible chat dock."""
        content = (PC_DIR / "index.html").read_text(encoding="utf-8")

        self.assertIn('id="minimap-canvas"', content)
        self.assertIn('id="minimap-coords"', content)
        self.assertIn('id="minimap-zone-label"', content)

        self.assertIn('id="hud-fps"', content)
        self.assertIn('id="hud-ping"', content)

        self.assertIn('id="chat-dock"', content)
        self.assertIn('id="chat-minimized-bar"', content)
        self.assertIn('id="chat-expanded-panel"', content)
        self.assertIn('id="chat-input"', content)

    def test_index_html_9_languages_parity(self):
        """index.html lang-select must contain all 9 canonical languages."""
        content = (PC_DIR / "index.html").read_text(encoding="utf-8")
        locales = ["vi", "en", "zh", "ja", "ko", "th", "de", "ru", "es"]
        for loc in locales:
            self.assertIn(f'value="{loc}"', content, f"Locale {loc} must exist in lang-select")

    def test_pc_input_controller_logic(self):
        """pc_input_controller.js must disable WASD movement and implement click-to-move and skills."""
        js_path = PC_DIR / "js" / "pc_input_controller.js"
        content = js_path.read_text(encoding="utf-8")

        # Decouple WASD movement
        self.assertIn("window.enableWasdMovement = false", content)
        self.assertIn("window.keys = {}", content)

        # Click-to-Move and Hold-to-Move
        self.assertIn("isoToWorld", content)
        self.assertIn("player.destination", content)
        self.assertIn("isLmbHeld", content)

        # Context menu suppression
        self.assertIn("contextmenu", content)
        self.assertIn("preventDefault", content)

        # Spacebar dodge
        self.assertIn("doDodge", content)

        # Flasks 1 to 5
        self.assertIn("useFlask", content)

    def test_pc_stylesheets_structure(self):
        """CSS files must implement full-screen, orbs, action bar, and modal docking."""
        main_css = (PC_DIR / "css" / "pc_main.css").read_text(encoding="utf-8")
        hud_css = (PC_DIR / "css" / "pc_hud.css").read_text(encoding="utf-8")
        modals_css = (PC_DIR / "css" / "pc_modals.css").read_text(encoding="utf-8")

        # Zero chassis in main
        self.assertIn("#app-viewport", main_css)
        self.assertIn("100vw", main_css)
        self.assertIn("100vh", main_css)
        self.assertIn("overflow: hidden", main_css)

        # Dual orbs & action bar in hud
        self.assertIn(".orb-globe", hud_css)
        self.assertIn(".flask-slot", hud_css)
        self.assertIn(".skill-slot", hud_css)
        self.assertIn(".hud-skill-bar-pc", hud_css)

        # Dual docking modals
        self.assertIn("#modal-character", modals_css)
        self.assertIn("#modal-bag", modals_css)
        self.assertIn("left: 24px", modals_css)
        self.assertIn("right: 24px", modals_css)


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