"""
Empirical Challenger Test Suite: Adversarial Stress-Testing of Reactive i18n & Chat UI (Milestone 2).
Validates:
1. Sub-second rapid programmatic switching between all 9 languages (< 1000ms SLA).
2. Deep DOM update veracity for non-Latin scripts (Chinese, Japanese, Korean, Thai, German umlauts, Russian Cyrillic).
3. Zero console errors or unhandled page exceptions under stress.
4. Resilience against race conditions, rapid interleaved UI events, and headless timeouts.
"""

from __future__ import annotations

import functools
import http.server
import sys
import threading
import time
import unittest
from pathlib import Path
from typing import Any, Dict, List

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")

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

ALL_9_LOCALES = ["vi", "en", "zh", "ja", "ko", "th", "de", "ru", "es"]

NON_LATIN_EXPECTATIONS: Dict[str, Dict[str, str]] = {
    "zh": {
        "tab_1": "世界",
        "tab_6": "系统",
        "send_btn": "发送",
        "badge_locked": "已锁定",
        "placeholder_sub": "世界",
        "sys_warn_sub": "系统频道仅供服务器公告使用",
    },
    "ja": {
        "tab_1": "ワールド",
        "tab_6": "システム",
        "send_btn": "送信",
        "badge_locked": "ロック",
        "placeholder_sub": "ワールド",
        "sys_warn_sub": "システムチャンネルはサーバー告知専用です",
    },
    "ko": {
        "tab_1": "월드",
        "tab_6": "시스템",
        "send_btn": "전송",
        "badge_locked": "잠김",
        "placeholder_sub": "월드",
        "sys_warn_sub": "시스템 채널은 서버 공지 전용입니다",
    },
    "th": {
        "tab_1": "โลก",
        "tab_6": "ระบบ",
        "send_btn": "ส่ง",
        "badge_locked": "ล็อค",
        "placeholder_sub": "โลก",
        "sys_warn_sub": "ช่องระบบสำหรับประกาศจากเซิร์ฟเวอร์เท่านั้น",
    },
    "de": {
        "tab_1": "Welt",
        "tab_5": "Flüstern",
        "tab_6": "System",
        "send_btn": "Senden",
        "badge_locked": "Gesperrt",
        "placeholder_sub": "Welt",
        "sys_warn_sub": "Der Systemkanal ist für Serverankündigungen reserviert",
    },
    "ru": {
        "tab_1": "Мир",
        "tab_5": "Шепот",
        "tab_6": "Система",
        "send_btn": "Отправить",
        "badge_locked": "Блок",
        "placeholder_sub": "Мир",
        "sys_warn_sub": "Системный канал только для оповещений сервера",
    },
    "es": {
        "tab_1": "Mundo",
        "tab_8": "Opinión",
        "tab_6": "Sistema",
        "send_btn": "Enviar",
        "badge_locked": "Bloqueado",
        "placeholder_sub": "Mundo",
        "sys_warn_sub": "El canal de sistema está reservado para anuncios del servidor",
    },
}


class QuietHTTPHandler(http.server.SimpleHTTPRequestHandler):
    """Suppresses stdout access logging during adversarial test execution."""

    def log_message(self, format: str, *args: Any) -> None:
        pass


class TestChallengerI18nM2Stress(unittest.TestCase):
    """Adversarial stress-testing suite for FreeExile reactive i18n subsystem."""

    server: http.server.ThreadingHTTPServer | None = None
    server_thread: threading.Thread | None = None
    server_port: int = 0
    server_url: str = ""

    @classmethod
    def setUpClass(cls) -> None:
        handler = functools.partial(QuietHTTPHandler, directory=str(WEBAPP_DIR))
        # Bind directly to port 0 to prevent TOCTOU socket race conditions
        cls.server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler)
        cls.server_port = int(cls.server.server_address[1])
        cls.server_url = f"http://127.0.0.1:{cls.server_port}"
        cls.server_thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.server_thread.start()

    @classmethod
    def tearDownClass(cls) -> None:
        if cls.server:
            cls.server.shutdown()
            cls.server.server_close()

    def _init_page(self, playwright_instance: Any) -> tuple[Any, Any, List[str]]:
        browser = playwright_instance.chromium.launch(channel="msedge", headless=True)
        page = browser.new_page()
        js_errors: List[str] = []

        page.on("pageerror", lambda err: js_errors.append(f"PageError: {err}"))

        def handle_console(msg: Any) -> None:
            if msg.type == "error":
                txt = msg.text
                if not any(ign in txt for ign in ["404", "favicon", "api/map"]):
                    js_errors.append(f"ConsoleError: {txt}")

        page.on("console", handle_console)
        page.goto(f"{self.server_url}/index.html", wait_until="domcontentloaded")
        page.wait_for_selector("#chat-dock", timeout=5000)
        page.evaluate(
            "() => { document.getElementById('modal-char-create')?.classList.add('hidden'); "
            "window.chatUI?.toggleChatDock(true); }"
        )
        return browser, page, js_errors

    def test_01_rapid_programmatic_switching_sub_second(self) -> None:
        """Asserts rapid switching across all 9 languages in under 1 second (< 1000ms)."""
        from playwright.sync_api import sync_playwright

        with sync_playwright() as p:
            browser, page, js_errors = self._init_page(p)
            try:
                # Execute 3 complete 9-language roundtrips (27 switches) programmatically
                perf_result = page.evaluate("""() => {
                    const locales = ['vi', 'en', 'zh', 'ja', 'ko', 'th', 'de', 'ru', 'es'];
                    const t0 = performance.now();
                    for (let round = 0; round < 3; round++) {
                        for (const loc of locales) {
                            window.FreeExileI18n.setLocale(loc);
                        }
                    }
                    const totalDurationMs = performance.now() - t0;
                    return {
                        totalDurationMs,
                        currentLocale: window.FreeExileI18n.getLocale(),
                        tab1Text: document.querySelector('#chat-dock .chat-channel-tab[data-channel-id="1"]')?.textContent,
                        sendBtnText: document.getElementById('chat-send-btn')?.textContent
                    };
                }""")

                duration_ms = perf_result["totalDurationMs"]
                # SLA: 27 switches must complete in well under 1000ms (1 second)
                self.assertLess(duration_ms, 1000.0, f"Rapid 9-lang cycle took {duration_ms}ms, exceeded 1s limit")
                self.assertEqual(perf_result["currentLocale"], "es")
                self.assertEqual(perf_result["tab1Text"], "Mundo")
                self.assertEqual(perf_result["sendBtnText"], "Enviar")
                self.assertEqual(len(js_errors), 0, f"Errors logged during rapid switching: {js_errors}")
            finally:
                browser.close()

    def test_02_non_latin_dom_veracity_all_scripts(self) -> None:
        """Verifies DOM updates match catalog expectations for all non-Latin scripts."""
        from playwright.sync_api import sync_playwright

        with sync_playwright() as p:
            browser, page, js_errors = self._init_page(p)
            try:
                for loc, expected in NON_LATIN_EXPECTATIONS.items():
                    # Switch locale via FreeExileI18n engine
                    page.evaluate(f"window.FreeExileI18n.setLocale('{loc}')")

                    # Verify Tab 1 text
                    tab1 = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]')
                    self.assertEqual(tab1, expected["tab_1"], f"Locale '{loc}' tab 1 mismatch")

                    # Verify Tab 6 (System) text
                    tab6 = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="6"]')
                    self.assertEqual(tab6, expected["tab_6"], f"Locale '{loc}' tab 6 mismatch")

                    # Verify specific accented tabs (e.g. German 'Flüstern' or Spanish 'Opinión')
                    if "tab_5" in expected:
                        tab5 = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="5"]')
                        self.assertEqual(tab5, expected["tab_5"], f"Locale '{loc}' tab 5 (Flüstern) mismatch")
                    if "tab_8" in expected:
                        tab8 = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="8"]')
                        self.assertEqual(tab8, expected["tab_8"], f"Locale '{loc}' tab 8 (Opinión) mismatch")

                    # Verify Send Button text
                    send_btn = page.inner_text("#chat-send-btn")
                    self.assertEqual(send_btn, expected["send_btn"], f"Locale '{loc}' send btn mismatch")

                    # Verify Input Placeholder in World channel
                    page.evaluate("window.chatUI.switchChannel(1)")
                    placeholder = page.get_attribute("#chat-input", "placeholder") or ""
                    self.assertIn(
                        expected["placeholder_sub"],
                        placeholder,
                        f"Locale '{loc}' placeholder did not contain '{expected['placeholder_sub']}'"
                    )

                    # Verify System Channel permission lock and badge text
                    page.evaluate("window.chatUI.switchChannel(6)")
                    sys_placeholder = page.get_attribute("#chat-input", "placeholder") or ""
                    self.assertIn(
                        expected["sys_warn_sub"],
                        sys_placeholder,
                        f"Locale '{loc}' system lock text missing from placeholder"
                    )

                    badge_text = page.inner_text("#chat-cooldown-badge")
                    self.assertEqual(badge_text, expected["badge_locked"], f"Locale '{loc}' badge text mismatch")

                self.assertEqual(len(js_errors), 0, f"Errors encountered during non-Latin checks: {js_errors}")
            finally:
                browser.close()

    def test_03_zero_console_errors_under_burst_events_and_tooltips(self) -> None:
        """Stress-tests interleaved locale changes, tooltip opens, and channel switches with 0 errors."""
        from playwright.sync_api import sync_playwright

        with sync_playwright() as p:
            browser, page, js_errors = self._init_page(p)
            try:
                # Add item message and test live reactive tooltip translation under stress
                stress_script = """() => {
                    const testItem = {
                        itemUuid: 'stress_item_001',
                        signature: 'valid_sig_123',
                        itemName: 'Thái Cổ Tà Đao',
                        rarity: 5,
                        element: 4,
                        quality: 20,
                        itemLevel: 85,
                        crafterName: 'Thiên Công',
                        affixes: [{ statName: 'Sát Thương Hỏa', statValue: 125, isImplicit: true }],
                        isVerified: true
                    };
                    window.chatUI.registerItemSnapshot(testItem);
                    window.chatUI.openItemTooltip(testItem);

                    const locales = ['zh', 'ja', 'ko', 'th', 'de', 'ru', 'es', 'vi', 'en'];
                    for (let i = 0; i < 50; i++) {
                        const loc = locales[i % locales.length];
                        window.FreeExileI18n.setLocale(loc);
                        window.chatUI.switchChannel((i % 8) + 1);
                    }
                    return {
                        tooltipVisible: !document.getElementById('modal-item-link-tooltip').classList.contains('hidden'),
                        tooltipSub: document.getElementById('item-tooltip-sub')?.textContent,
                        hmacBadge: document.getElementById('item-tooltip-hmac-badge')?.textContent
                    };
                }"""
                res = page.evaluate(stress_script)
                self.assertTrue(res["tooltipVisible"], "Tooltip should remain open and reactive during switches")
                # After ending on EN (50th iteration: index 49 % 9 = 4 -> 'ko', wait, let's verify text)
                self.assertIsNotNone(res["tooltipSub"], "Tooltip subtext must not be null")
                self.assertIsNotNone(res["hmacBadge"], "HMAC badge must not be null")
                self.assertEqual(len(js_errors), 0, f"Detected console errors under burst: {js_errors}")
            finally:
                browser.close()

    def test_04_race_conditions_and_headless_timeout_resilience(self) -> None:
        """Verifies resilience against concurrent async events, storage sync, and rapid unsubscription."""
        from playwright.sync_api import sync_playwright

        with sync_playwright() as p:
            browser, page, js_errors = self._init_page(p)
            try:
                race_res = page.evaluate("""() => {
                    let callCounts = { a: 0, b: 0, c: 0 };
                    const unsubA = window.FreeExileI18n.subscribe(() => { callCounts.a++; });
                    const unsubB = window.FreeExileI18n.subscribe(() => { callCounts.b++; });
                    const unsubC = window.FreeExileI18n.subscribe(() => { callCounts.c++; });

                    // Concurrently dispatch custom events and direct setLocale
                    for (let i = 0; i < 20; i++) {
                        if (i === 5) unsubA();
                        if (i === 10) unsubB();
                        window.FreeExileI18n.setLocale(i % 2 === 0 ? 'vi' : 'en');
                        window.dispatchEvent(new CustomEvent('freeexile:localeChanged', {
                            detail: { locale: 'en' }
                        }));
                    }
                    unsubC();

                    return {
                        callCounts,
                        finalLocale: window.FreeExileI18n.getLocale(),
                        listenerCount: window.FreeExileI18n.listeners.length
                    };
                }""")
                self.assertEqual(race_res["callCounts"]["a"], 5, "Unsubscribed listener A must not receive events")
                self.assertEqual(race_res["callCounts"]["b"], 10, "Unsubscribed listener B must not receive events")
                self.assertEqual(race_res["callCounts"]["c"], 20, "Listener C must receive all 20 calls")
                self.assertEqual(len(js_errors), 0, f"Errors during race condition stress: {js_errors}")
            finally:
                browser.close()


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