"""
Comprehensive End-to-End (E2E) Test Suite for Reactive i18n & Chat UI (Milestone 2).
Validates live in-place language switching without page reload across WebApp UI:
- Dynamic updates of 8 channel tabs, input placeholder, send button, cooldown badge.
- Multi-language round-trip cycle: VI -> EN -> ZH -> JA -> KO -> VI.
- Zero page reloads (single continuous DOM session).
- Zero uncaught JavaScript errors or console exceptions.
"""

from __future__ import annotations

import functools
import http.server
import socket
import sys
import threading
import time
import unittest
from pathlib import Path
from typing import Any, 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"


class QuietHandler(http.server.SimpleHTTPRequestHandler):
    """Silent HTTP handler to suppress server access log noise during tests."""

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


def get_free_port() -> int:
    """Finds an available TCP port for ephemeral test server."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("127.0.0.1", 0))
        return int(s.getsockname()[1])


class TestI18nReactiveSwitchingE2E(unittest.TestCase):
    """Automated E2E test verifying live reactive language switching without page reload."""

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

    @classmethod
    def setUpClass(cls) -> None:
        cls.server_port = get_free_port()
        cls.server_url = f"http://127.0.0.1:{cls.server_port}"
        handler = functools.partial(QuietHandler, directory=str(WEBAPP_DIR))
        cls.server = http.server.ThreadingHTTPServer(("127.0.0.1", cls.server_port), handler)
        cls.server_thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.server_thread.start()
        time.sleep(0.1)

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

    def test_e2e_live_reactive_language_switching_zero_reload(self) -> None:
        """Runs full browser headless E2E verification of reactive language switching."""
        from playwright.sync_api import sync_playwright

        js_errors: List[str] = []

        with sync_playwright() as p:
            browser = p.chromium.launch(channel="msedge", headless=True)
            page = browser.new_page()

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

            def on_console(msg: Any) -> None:
                if msg.type == "error":
                    txt = msg.text
                    if "404" not in txt and "favicon" not in txt and "api/map" not in txt:
                        js_errors.append(f"ConsoleError: {txt}")

            page.on("console", on_console)

            # Navigate to index.html
            page.goto(f"{self.server_url}/index.html")
            page.wait_for_timeout(400)

            # Hide initial character creation modal and expand chat dock
            page.evaluate(
                "() => { document.getElementById('modal-char-create')?.classList.add('hidden'); "
                "window.chatUI?.toggleChatDock(true); }"
            )
            page.wait_for_timeout(100)

            # Setup navigation spy to detect any illegal page reloads
            page.evaluate(
                "window.__sessionNavCount = 1; "
                "window.addEventListener('beforeunload', () => { window.__sessionNavCount++; });"
            )

            # -------------------------------------------------------------
            # STEP 1: Verify Initial Vietnamese State (VI)
            # -------------------------------------------------------------
            tab1_vi = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]')
            tab2_vi = page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="2"]')
            send_btn_vi = page.inner_text("#chat-send-btn")
            placeholder_vi = page.get_attribute("#chat-input", "placeholder") or ""

            self.assertEqual(tab1_vi, "Thế Giới", "Initial Tab 1 must be 'Thế Giới'")
            self.assertEqual(tab2_vi, "Khu Vực", "Initial Tab 2 must be 'Khu Vực'")
            self.assertEqual(send_btn_vi, "Gửi", "Initial Send button must be 'Gửi'")
            self.assertTrue("Thế Giới" in placeholder_vi or "Nhập tin nhắn" in placeholder_vi)

            # -------------------------------------------------------------
            # STEP 2 & 3: Switch to English (EN) & Assert Dynamic Updates
            # -------------------------------------------------------------
            page.select_option("#lang-select", "en")
            page.wait_for_timeout(100)

            # Assert ZERO page reloads occurred
            nav_session = page.evaluate("window.__sessionNavCount")
            self.assertEqual(nav_session, 1, "Language switch must be 100% reactive with zero page reloads")

            # Assert all 8 channel tabs dynamically updated in English
            expected_en_tabs = {
                1: "World", 2: "Zone", 3: "Guild", 4: "Party",
                5: "Whisper", 6: "System", 7: "Recruit", 8: "Feedback"
            }
            for cid, label in expected_en_tabs.items():
                tab_text = page.inner_text(f'#chat-dock .chat-channel-tab[data-channel-id="{cid}"]')
                self.assertEqual(tab_text, label, f"Tab {cid} must update to '{label}' in English")

            send_btn_en = page.inner_text("#chat-send-btn")
            self.assertEqual(send_btn_en, "Send", "Send button must update to 'Send'")

            placeholder_en = page.get_attribute("#chat-input", "placeholder") or ""
            self.assertTrue("World" in placeholder_en or "Enter message" in placeholder_en)

            # -------------------------------------------------------------
            # STEP 4: Test Permission Feedback in EN (System Channel Click)
            # -------------------------------------------------------------
            page.click('#chat-dock .chat-channel-tab[data-channel-id="6"]')
            page.wait_for_timeout(100)

            sys_placeholder = page.get_attribute("#chat-input", "placeholder") or ""
            self.assertTrue(
                "System channel is reserved" in sys_placeholder,
                f"Expected system announcement text, got: {sys_placeholder}"
            )

            badge_text = page.inner_text("#chat-cooldown-badge")
            self.assertEqual(badge_text, "Locked", "System channel badge must show 'Locked'")

            # -------------------------------------------------------------
            # STEP 5: Multi-Language Cycle (ZH -> JA -> KO -> VI)
            # -------------------------------------------------------------
            # Chinese (ZH)
            page.select_option("#lang-select", "zh")
            page.wait_for_timeout(50)
            self.assertEqual(page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]'), "世界")
            self.assertEqual(page.inner_text("#chat-send-btn"), "发送")

            # Japanese (JA)
            page.select_option("#lang-select", "ja")
            page.wait_for_timeout(50)
            self.assertEqual(page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]'), "ワールド")
            self.assertEqual(page.inner_text("#chat-send-btn"), "送信")

            # Korean (KO)
            page.select_option("#lang-select", "ko")
            page.wait_for_timeout(50)
            self.assertEqual(page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]'), "월드")
            self.assertEqual(page.inner_text("#chat-send-btn"), "전송")

            # Return to Vietnamese (VI)
            page.select_option("#lang-select", "vi")
            page.wait_for_timeout(50)
            self.assertEqual(page.inner_text('#chat-dock .chat-channel-tab[data-channel-id="1"]'), "Thế Giới")
            self.assertEqual(page.inner_text("#chat-send-btn"), "Gửi")

            # -------------------------------------------------------------
            # STEP 6: Console Error Gate (Zero Unhandled Errors)
            # -------------------------------------------------------------
            browser.close()
            self.assertEqual(len(js_errors), 0, f"Detected unhandled JavaScript/console errors: {js_errors}")


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