"""
Unit tests for FreeExile WebApp Dynamic Templates and UI Template Manager.
Verifies modular extraction of all modal markups from index.html into:
- Modular HTML templates in client/webapp/templates/
- Domain-driven JS template catalogs in client/webapp/js/ui/templates/
- UITemplateManager in client/webapp/js/ui/template_manager.js
- Minimal, clean index.html conforming to GEMINI.md Section 2.9 (<= 200 lines soft cap, <= 400 lines hard cap).
"""

import unittest
import os
import subprocess
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
WEBAPP_DIR = PROJECT_ROOT / "client" / "webapp"
TEMPLATES_DIR = WEBAPP_DIR / "templates"
JS_TEMPLATES_DIR = WEBAPP_DIR / "js" / "ui" / "templates"
INDEX_HTML = WEBAPP_DIR / "index.html"
TEMPLATE_MANAGER_JS = WEBAPP_DIR / "js" / "ui" / "template_manager.js"

EXPECTED_MODALS = [
    "modal-game-notice",
    "modal-npc-dialogue",
    "modal-meridian",
    "modal-vault",
    "modal-character",
    "modal-bag",
    "modal-loot-filter",
    "modal-megashop",
    "modal-checkout",
    "modal-web3",
    "modal-season",
    "modal-agent-orb",
    "modal-harvest-report",
    "modal-worldmap",
    "modal-dungeon",
    "modal-hideout",
    "modal-endgame-atlas",
    "modal-war-fog",
    "modal-forge",
    "modal-settings",
    "modal-feedback",
    "modal-account",
    "modal-char-create",
    "modal-story-quests",
    "modal-divine-vault",
    "modal-combat-test-dashboard",
]

EXPECTED_CATALOGS = [
    "template_catalog_system.js",
    "template_catalog_auth.js",
    "template_catalog_inventory.js",
    "template_catalog_world.js",
    "template_catalog_activities.js",
    "template_catalog_endgame.js",
]


class TestWebAppDynamicTemplates(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.index_content = INDEX_HTML.read_text(encoding="utf-8")
        cls.index_lines = cls.index_content.splitlines()

    def test_01_index_html_line_count_and_caps(self):
        """index.html must adhere to GEMINI.md Section 2.9 (<= 200 lines soft cap, <= 400 lines hard cap)."""
        line_count = len(self.index_lines)
        self.assertLessEqual(line_count, 200, f"index.html exceeds 200 lines soft cap: {line_count}")
        self.assertLessEqual(line_count, 400, f"index.html exceeds 400 lines hard cap: {line_count}")

    def test_02_index_html_modal_container_mount(self):
        """index.html must provide #modal-container mount point for dynamic modal injection."""
        self.assertIn('id="modal-container"', self.index_content, "index.html must have #modal-container")
        container_idx = self.index_content.index('id="modal-container"')
        tooltip_idx = self.index_content.index('id="game-tooltip"')
        self.assertLess(container_idx, tooltip_idx, "#modal-container must be inside viewport before tooltips")

    def test_03_index_html_imports_template_catalogs_and_manager(self):
        """index.html must import all template catalogs and template_manager.js before UI controllers."""
        for cat in EXPECTED_CATALOGS:
            self.assertIn(f"js/ui/templates/{cat}", self.index_content, f"Missing {cat} import in index.html")
        self.assertIn("js/ui/template_manager.js", self.index_content, "Missing template_manager.js in index.html")

        mgr_idx = self.index_content.index("js/ui/template_manager.js")
        meridian_idx = self.index_content.index("js/ui/meridian_vault.js")
        self.assertLess(mgr_idx, meridian_idx, "template_manager.js must be imported before UI controllers")

    def test_04_html_template_files_exist_and_bounded(self):
        """All 25 modal HTML files in client/webapp/templates/ must exist and have <= 400 lines."""
        self.assertTrue(TEMPLATES_DIR.is_dir(), "templates directory must exist")
        html_files = list(TEMPLATES_DIR.glob("*.html"))
        self.assertGreaterEqual(len(html_files), 25, f"Expected at least 25 template files, found {len(html_files)}")

        for html_file in html_files:
            content = html_file.read_text(encoding="utf-8")
            lines = len(content.splitlines())
            self.assertGreater(lines, 0, f"Template {html_file.name} is empty")
            self.assertLessEqual(lines, 400, f"Template {html_file.name} exceeds 400 lines: {lines}")

    def test_05_js_catalogs_exist_and_bounded(self):
        """All 6 JS template catalogs must exist and have <= 700 lines soft cap, <= 1000 lines hard cap."""
        self.assertTrue(JS_TEMPLATES_DIR.is_dir(), "js/ui/templates directory must exist")

        for cat in EXPECTED_CATALOGS:
            cat_path = JS_TEMPLATES_DIR / cat
            self.assertTrue(cat_path.is_file(), f"Catalog {cat} must exist")
            lines = len(cat_path.read_text(encoding="utf-8").splitlines())
            self.assertLessEqual(lines, 700, f"Catalog {cat} exceeds 700 lines soft cap: {lines}")
            self.assertLessEqual(lines, 1000, f"Catalog {cat} exceeds 1000 lines hard cap: {lines}")

    def test_06_js_template_manager_bounded(self):
        """template_manager.js must exist and adhere to code soft cap (<= 350 lines)."""
        self.assertTrue(TEMPLATE_MANAGER_JS.is_file(), "template_manager.js must exist")
        lines = len(TEMPLATE_MANAGER_JS.read_text(encoding="utf-8").splitlines())
        self.assertLessEqual(lines, 350, f"template_manager.js exceeds 350 lines: {lines}")

    def test_07_all_canonical_modals_registered(self):
        """All 25 canonical modals must be registered across the template catalogs."""
        combined_catalogs = "\n".join(
            (JS_TEMPLATES_DIR / cat).read_text(encoding="utf-8") for cat in EXPECTED_CATALOGS
        )

        for modal_id in EXPECTED_MODALS:
            self.assertIn(f"'{modal_id}'", combined_catalogs, f"Modal {modal_id} not registered in catalogs")

    def test_08_node_syntax_validity(self):
        """All template catalogs and manager must pass Node.js syntax parsing check."""
        files = [TEMPLATE_MANAGER_JS] + [JS_TEMPLATES_DIR / cat for cat in EXPECTED_CATALOGS]
        for f in files:
            res = subprocess.run(["node", "--check", str(f)], capture_output=True, text=True)
            self.assertEqual(res.returncode, 0, f"Syntax error in {f.name}: {res.stderr}")

    def test_09_template_manager_normalization_and_url_resolution(self):
        """UITemplateManager must normalize IDs bidirectionally and resolve candidate template URLs."""
        node_script = """
        const fs = require('fs');
        global.window = {};
        global.document = {
            readyState: 'complete',
            getElementById: () => null,
            querySelector: () => null,
            addEventListener: () => {},
            dispatchEvent: () => {}
        };
        global.CustomEvent = class {};

        // Load system catalog and manager
        eval(fs.readFileSync('client/webapp/js/ui/templates/template_catalog_system.js', 'utf8'));
        eval(fs.readFileSync('client/webapp/js/ui/template_manager.js', 'utf8'));

        const mgr = window.UITemplateManager;
        const assert = (cond, msg) => { if (!cond) throw new Error(msg); };

        assert(mgr.has('modal-settings'), 'has(modal-settings) should be true');
        assert(mgr.has('settings'), 'has(settings) should be true');
        assert(mgr.get('settings').length > 0, 'get(settings) should return html');
        assert(mgr.get('settings') === mgr.get('modal-settings'), 'get(settings) should match get(modal-settings)');

        assert(mgr.has('combat-test-dashboard'), 'has(combat-test-dashboard) should be true');
        assert(mgr.has('modal-combat-test-dashboard'), 'has(modal-combat-test-dashboard) should be true');

        const urls1 = mgr.resolveCandidateUrls('settings');
        assert(urls1.includes('templates/settings_modal.html'), 'should include settings_modal.html: ' + urls1);

        const urls2 = mgr.resolveCandidateUrls('modal-feedback');
        assert(urls2.includes('templates/feedback_modal.html'), 'should include feedback_modal.html: ' + urls2);

        const urls3 = mgr.resolveCandidateUrls('story-quests');
        assert(urls3.includes('templates/story_quests_modal.html'), 'should include story_quests_modal.html: ' + urls3);

        console.log('ALL_CHECKS_PASSED');
        """
        res = subprocess.run(["node", "-e", node_script], cwd=str(PROJECT_ROOT), capture_output=True, text=True)
        self.assertEqual(res.returncode, 0, f"Node verification failed: {res.stderr}")
        self.assertIn("ALL_CHECKS_PASSED", res.stdout)


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

