"""
Unit tests for FreeExile Mobile WebApp & PWA Native Simulator.
Verifies Web App Manifest validity, viewport meta tags, safe area insets,
virtual joystick ergonomics, and local server LAN IP resolution.
"""

import unittest
import json
import os
import re
from pathlib import Path

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


class TestMobileWebApp(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        parts = []
        index_path = WEBAPP_DIR / "index.html"
        if index_path.exists():
            with open(index_path, "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.bundle = "\n".join(parts)

    def test_pwa_manifest_validity(self):
        """Manifest exists, parses as JSON, and specifies standalone mobile settings."""
        manifest_path = WEBAPP_DIR / "manifest.json"
        self.assertTrue(manifest_path.exists(), "manifest.json must exist in client/webapp")

        with open(manifest_path, "r", encoding="utf-8") as f:
            data = json.load(f)

        self.assertEqual(data.get("display"), "standalone")
        self.assertIn("landscape", data.get("orientation", ""))
        self.assertIn("name", data)
        self.assertIn("icons", data)

    def test_mobile_native_viewport_and_safe_areas(self):
        """Index.html must have apple-mobile-web-app-capable and safe-area-inset styling."""
        index_path = WEBAPP_DIR / "index.html"
        self.assertTrue(index_path.exists(), "index.html must exist in client/webapp")

        html = self.bundle

        # Check essential mobile tags
        self.assertIn('apple-mobile-web-app-capable', html)
        self.assertIn('viewport-fit=cover', html)
        self.assertIn('user-scalable=no', html)
        self.assertIn('safe-area-inset-top', html)
        self.assertIn('safe-area-inset-bottom', html)

    def test_virtual_joystick_and_combat_controls(self):
        """Index.html must implement floating joystick and ergonomic combat skills."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        self.assertIn('joystick-zone', html)
        self.assertIn('skill-dodge', html)
        self.assertIn('skill-fire', html)
        self.assertIn('skill-thunder', html)
        self.assertIn('skill-frost', html)
        self.assertIn('badge-iframe', html)

    def test_nine_languages_in_webapp(self):
        """Webapp dictionary must contain all 9 target global languages."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        for lang in ['vi', 'en', 'zh', 'ja', 'ko', 'th', 'de', 'ru', 'es']:
            self.assertIn(f"{lang}:", html, f"Language key '{lang}' must exist in webapp dictionary")

    def test_server_local_ip_resolution(self):
        """tools/serve_webapp.py get_local_ip must return valid IPv4 and default port must be 8088."""
        import sys
        import inspect
        sys.path.insert(0, str(PROJECT_ROOT / "tools"))
        import serve_webapp
        from serve_webapp import get_local_ip

        ip = get_local_ip()
        self.assertIsInstance(ip, str)
        # IPv4 pattern match (e.g. 192.168.1.1 or 127.0.0.1)
        pattern = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
        self.assertTrue(re.match(pattern, ip), f"IP '{ip}' does not match standard IPv4 format")

        sig = inspect.signature(serve_webapp.start_server)
        self.assertEqual(sig.parameters['port'].default, 8088, "Default port for serve_webapp must be 8088")

    def test_agent_orb_ui_and_llm_elements(self):
        """Index.html must implement Agent_Orb modal, harvest report, and Multi-LLM provider options."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check Agent Orb UI components
        self.assertIn('btn-open-agent-orb', html)
        self.assertIn('modal-agent-orb', html)
        self.assertIn('modal-harvest-report', html)
        self.assertIn('hud-agent-banner', html)
        self.assertIn('hud-agent-thought-container', html)
        self.assertIn('agent-thought-stream', html)

        # Check Multi-LLM providers in UI
        for provider in ['LOCAL_TACTICAL', 'DEEPSEEK', 'OPENROUTER', 'OPENAI', 'ANTHROPIC', 'GEMINI', 'KIMI']:
            self.assertIn(provider, html, f"Provider '{provider}' must be available in Agent Orb dropdown")

    def test_worldmap_and_dungeon_ui_elements(self):
        """Index.html must implement World Map, Dungeon Instance, and Secret Chamber UI and data structures."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check HUD Buttons & Modals
        self.assertIn('btn-open-worldmap', html)
        self.assertIn('modal-worldmap', html)
        self.assertIn('btn-open-dungeon', html)
        self.assertIn('modal-dungeon', html)

        # Check Zone & Dungeon elements
        self.assertIn('CANONICAL_ZONES', html)
        self.assertIn('zone_boundless_sanctuary', html)
        self.assertIn('zone_tang_kiem_nhai', html)
        self.assertIn('zone_ancient_sword_barrow', html)
        self.assertIn('zone_boundless_sandstorm', html)
        self.assertIn('zone_blood_scale_ruins', html)
        self.assertIn('zone_five_elements_altar', html)
        self.assertIn('zone_abyssal_ice_pond', html)
        self.assertIn('zone_infinite_blood_rift', html)
        self.assertIn('zone_purgatory_lava_cavern', html)
        self.assertIn('zone_boundless_celestial_palace', html)

        # Check Dungeons & Secret Chamber components
        self.assertIn('phongma-current-layer', html)
        self.assertIn('btn-advance-phongma', html)
        self.assertIn('btn-fight-flame-demon', html)
        self.assertIn('btn-trigger-secret-demo', html)
        self.assertIn('secret-timer-1', html)
        self.assertIn('teleportToZone', html)

    def test_settings_and_feedback_ui_elements(self):
        """Index.html must implement Settings modal, Feedback button inside settings, and Feedback modal."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check Settings Button & Modal
        self.assertIn('btn-open-settings', html, "Settings button must exist in header")
        self.assertIn('modal-settings', html, "modal-settings must exist")
        self.assertIn('btn-close-settings', html, "btn-close-settings must exist")

        # Check Feedback Button inside Settings Modal
        self.assertIn('btn-open-feedback-form', html, "Feedback trigger button must exist inside Settings")

        # Check Feedback Modal and Form elements
        self.assertIn('modal-feedback', html, "modal-feedback must exist")
        self.assertIn('btn-close-feedback', html, "btn-close-feedback must exist")
        self.assertIn('select-feedback-category', html, "Category selector must exist")
        self.assertIn('input-feedback-title', html, "Title input must exist")
        self.assertIn('textarea-feedback-content', html, "Content textarea must exist")
        self.assertIn('btn-submit-feedback', html, "Submit feedback button must exist")
        self.assertIn('panel-feedback-history', html, "History panel must exist")
        self.assertIn('txt-diag-coords', html, "Telemetry coordinates element must exist")

        # Check all 5 categories
        for cat in ['BUG_REPORT', 'GAMEPLAY_SUGGESTION', 'BALANCE_ADJUSTMENT', 'LOCALIZATION', 'OTHER']:
            self.assertIn(cat, html, f"Feedback category '{cat}' must exist in select options")

    def test_forge_salvage_and_corruption_ui_elements(self):
        """Index.html must implement Savage Forge modal, Salvage button, and Feral Corruption button."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check Forge elements
        self.assertIn('modal-forge', html, "modal-forge must exist")
        self.assertIn('btn-close-forge', html, "btn-close-forge must exist")
        self.assertIn('btn-forge-salvage', html, "btn-forge-salvage must exist")
        self.assertIn('btn-forge-corrupt', html, "btn-forge-corrupt must exist")
        self.assertIn('txt-forge-dust', html, "txt-forge-dust element must exist")
        self.assertIn('bar-forge-dust', html, "bar-forge-dust progress bar must exist")
        self.assertIn('txt-forge-destroyed-count', html, "txt-forge-destroyed-count element must exist")

    def test_endgame_infinite_atlas_ui_elements(self):
        """Index.html must implement Endgame Infinite Atlas modal, node inspector, and content encounters."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check Endgame Atlas Modal & HUD buttons
        self.assertIn('modal-endgame-atlas', html, "modal-endgame-atlas must exist")
        self.assertIn('btn-open-atlas', html, "btn-open-atlas button must exist")
        self.assertIn('btn-close-atlas', html, "btn-close-atlas button must exist")
        self.assertIn('atlas-canvas-container', html, "atlas-canvas-container must exist")
        self.assertIn('atlas-node-inspector', html, "atlas-node-inspector must exist")
        self.assertIn('btn-atlas-activate-device', html, "btn-atlas-activate-device button must exist")
        self.assertIn('btn-atlas-clear-node', html, "btn-atlas-clear-node button must exist")
        self.assertIn('atlas-contents-list', html, "atlas-contents-list container must exist")
        self.assertIn('badge-atlas-points', html, "badge-atlas-points must exist")

    def test_portal_and_device_interaction_system(self):
        """Index.html must implement unified portal proximity prompts, raycasting, and notice modals."""
        index_path = WEBAPP_DIR / "index.html"
        html = self.bundle

        # Check HUD Proximity Interaction Prompt & Notice Modal
        self.assertIn('hud-interaction-prompt', html, "hud-interaction-prompt button must exist")
        self.assertIn('interaction-icon', html, "interaction-icon element must exist")
        self.assertIn('interaction-title', html, "interaction-title element must exist")
        self.assertIn('modal-game-notice', html, "modal-game-notice dialog must exist")
        self.assertIn('btn-close-notice', html, "btn-close-notice button must exist")

        # Check Interaction System Logic & Callbacks
        self.assertIn('currentNearbyInteraction', html, "currentNearbyInteraction state must exist")
        self.assertIn('executeNearbyInteraction', html, "executeNearbyInteraction function must exist")
        self.assertIn('getAvailablePortalTargets', html, "getAvailablePortalTargets function must exist")
        self.assertIn('enterMapPortal', html, "enterMapPortal callback must exist")
        self.assertIn('enterWorldGate', html, "enterWorldGate callback must exist")
        self.assertIn('enterQuestGate', html, "enterQuestGate callback must exist")
        self.assertIn('showNoticeModal', html, "showNoticeModal function must exist")

    def test_war_fog_and_anti_bot_maze_ui_elements(self):
        """Index.html must implement Server-Authoritative War-Fog & Anti-Bot Procedural Maze modal, canvas, and controls."""
        html = self.bundle
        self.assertIn('btn-open-war-fog', html, "btn-open-war-fog HUD button must exist")
        self.assertIn('modal-war-fog', html, "modal-war-fog must exist")
        self.assertIn('btn-close-war-fog', html, "btn-close-war-fog button must exist")
        self.assertIn('warfog-canvas', html, "warfog-canvas element must exist")
        self.assertIn('btn-warfog-regen', html, "btn-warfog-regen button must exist")
        self.assertIn('btn-warfog-step', html, "btn-warfog-step button must exist")
        self.assertIn('btn-warfog-break-barricade', html, "btn-warfog-break-barricade button must exist")
        self.assertIn('txt-warfog-sinuosity', html, "txt-warfog-sinuosity element must exist")
        self.assertIn('war_fog.js', html, "war_fog.js script tag must exist")

    def test_isometric_collision_and_prop_system(self):
        """Webapp must implement zone boundaries, collision resolution, camera tracking, and prop footprints."""
        html = self.bundle
        index_path = WEBAPP_DIR / "index.html"
        with open(index_path, "r", encoding="utf-8") as f:
            index_content = f.read()

        # Check script tag for collision engine in index.html
        self.assertIn('js/engine/collision_engine.js', index_content)

        # Check collision engine exports and concepts
        self.assertIn('ZONE_BOUNDS', html)
        self.assertIn('zone_boundless_sanctuary', html)
        self.assertIn('minCamWx', html)
        self.assertIn('isPositionBlocked', html)
        self.assertIn('resolveMovementWithSliding', html)
        self.assertIn('validatePropOverlap', html)
        self.assertIn('clampPositionToBounds', html)

        # Check tracking camera system
        self.assertIn('updateCamera', html)
        self.assertIn('followSpeed', html)
        self.assertIn('deadzone', html)

        # Check prop footprints and Z-sorting
        self.assertIn('footprint', html)
        self.assertIn('isSolid', html)

        # Check zone-scoped water hazard and corner-glitch prevention
        self.assertIn("zId === 'zone_boundless_sanctuary'", html, "Water hazard must be scoped to Sanctuary only")
        self.assertIn("Math.abs(moveDistX) >= Math.abs(moveDistY)", html, "resolveMovementWithSliding must prioritize dominant axis to prevent corner-glitching")

        # Check typo fix in war_fog.js
        self.assertIn("BỊ CHẶN BỞI RÀO CẢN", html, "War fog modal notice title must use correct Vietnamese spelling 'RÀO CẢN'")

    def test_beggar_chronicle_and_quest_board_ui_elements(self):
        """Verifies presence of Beggar's Chronicle and Quest Board modal, buttons, and script integration."""
        html = self.bundle

        self.assertIn('btn-open-quests', html, "btn-open-quests must exist in HUD")
        self.assertIn('modal-story-quests', html, "modal-story-quests must exist in index.html")
        self.assertIn('tab-btn-quest-chronicle', html, "Chronicle tab button must exist")
        self.assertIn('tab-btn-quest-list', html, "Quests tab button must exist")
        self.assertIn('tab-btn-quest-milestones', html, "Milestones tab button must exist")
        self.assertIn('badge-beggar-stage', html, "Beggar stage badge element must exist")
        self.assertIn('chronicle-acts-list', html, "Chronicle acts list element must exist")
        self.assertIn('quest-list-container', html, "Quest list container element must exist")
        self.assertIn('milestone-list-container', html, "Milestone list container element must exist")

        index_path = WEBAPP_DIR / "index.html"
        with open(index_path, "r", encoding="utf-8") as f:
            index_content = f.read()

        self.assertIn('js/ui/story_quest_board.js', index_content, "story_quest_board.js must be imported in index.html")

    def test_feral_npc_and_dialogue_ui_elements(self):
        """Verifies presence of Feral NPC system, dialogue modal, choices, and script integration."""
        html = self.bundle

        # Check Dialogue Modal Elements
        self.assertIn('modal-npc-dialogue', html, "modal-npc-dialogue must exist in index.html")
        self.assertIn('dialogue-avatar-img', html, "dialogue-avatar-img must exist")
        self.assertIn('dialogue-speaker-name', html, "dialogue-speaker-name must exist")
        self.assertIn('dialogue-speaker-title', html, "dialogue-speaker-title must exist")
        self.assertIn('dialogue-text-content', html, "dialogue-text-content must exist")
        self.assertIn('dialogue-choices-container', html, "dialogue-choices-container must exist")
        self.assertIn('btn-close-dialogue', html, "btn-close-dialogue must exist")

        # Check Script and Engine Functions
        self.assertIn('js/ui/dialogue_npc.js', html, "dialogue_npc.js must be imported in index.html")
        self.assertIn('CLIENT_NPCS', html, "CLIENT_NPCS catalog must exist")
        self.assertIn('CLIENT_DIALOGUE_TREES', html, "CLIENT_DIALOGUE_TREES must exist")
        self.assertIn('openNpcDialogue', html, "openNpcDialogue function must exist")
        self.assertIn('closeNpcDialogue', html, "closeNpcDialogue function must exist")
        self.assertIn('getNpcsInCurrentZone', html, "getNpcsInCurrentZone function must exist")

        # Check Canonical NPC identities
        self.assertIn('npc_doc_co_tan_kiem', html, "Lão Độc Cốt must be present in NPC catalog")
        self.assertIn('npc_van_gioi_thuong_nhan', html, "Gian Thương Cẩu Khấu must be present")
        self.assertIn('npc_thiet_tuong_au_da_tu', html, "Thiết Tượng Huyết Đồ must be present")
        self.assertIn('npc_chan_vo_truyen_thua', html, "Huyết Vu Bà Bà must be present")
        self.assertIn('npc_sa_mac_du_than', html, "Cuồng Nhân Bão Cát must be present")


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