"""
Adversarial Multi-Resolution, Layout, i18n, Dual-Docking & Cleanliness Test Harness.
Empirically executed by challenger_18_2.
"""

from __future__ import annotations
import json
import socketserver
import sys
import threading
import time
from pathlib import Path
from typing import Any, Dict, List

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from tools.serve_web_pc import FreeExilePCRequestHandler


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


def run_adversarial_suite():
    # 1. Start ephemeral HTTP server
    server = socketserver.TCPServer(("127.0.0.1", 0), SilentPCRequestHandler)
    port = server.server_address[1]
    server_thread = threading.Thread(target=server.serve_forever, daemon=True)
    server_thread.start()
    server_url = f"http://127.0.0.1:{port}"
    print(f"[TEST_SERVER] Started ephemeral PC server at {server_url}")

    from playwright.sync_api import sync_playwright

    report: Dict[str, Any] = {
        "server_url": server_url,
        "resolutions_tested": [],
        "dynamic_resizes_tested": [],
        "mobile_artifacts_audit": {},
        "i18n_cycling_metrics": {},
        "dual_docking_metrics": {},
        "network_and_console_audit": {
            "page_errors": [],
            "console_errors": [],
            "http_404s": [],
            "http_500s": [],
            "failed_requests": [],
            "atlas_image_status": {},
        },
        "verdict": "PENDING",
    }

    p = sync_playwright().start()
    try:
        try:
            browser = p.chromium.launch(channel="msedge", headless=True)
        except Exception:
            browser = p.chromium.launch(headless=True)

        context = browser.new_context(viewport={"width": 1920, "height": 1080})
        page = context.new_page()

        # Wire event listeners
        page.on("pageerror", lambda err: report["network_and_console_audit"]["page_errors"].append(str(err)))
        
        def on_console(msg):
            if msg.type == "error":
                report["network_and_console_audit"]["console_errors"].append(msg.text)
        page.on("console", on_console)

        def on_response(res):
            if res.status == 404:
                report["network_and_console_audit"]["http_404s"].append(res.url)
            elif res.status >= 500:
                report["network_and_console_audit"]["http_500s"].append(f"{res.status}: {res.url}")
        page.on("response", on_response)

        page.on("requestfailed", lambda req: report["network_and_console_audit"]["failed_requests"].append(f"{req.url} -> {req.failure}"))

        # Pre-seed fullscreen mode & authenticated session to prevent char creation modal intercept
        page.add_init_script("""
            try {
                localStorage.setItem('fe_sim_mode', 'fullscreen');
                localStorage.setItem('freeexile_auth_session', JSON.stringify({
                    account_id: 'acc_challenger_test',
                    username: 'ChallengerPC',
                    status: 'ACTIVE'
                }));
                localStorage.setItem('fe_chars_acc_challenger_test', JSON.stringify([
                    { character_id: 'char_challenger_1', character_name: '劍神', character_class: 'char_sword_master', level: 1 }
                ]));
                localStorage.setItem('freeexile_active_char_id', 'char_challenger_1');
            } catch(e){}
        """)

        # Navigate to PC client
        page.goto(f"{server_url}/index.html", wait_until="networkidle")
        page.wait_for_timeout(300)

        # Ensure any char-create modal is hidden
        page.evaluate("() => { document.getElementById('modal-char-create')?.classList.add('hidden'); }")

        # -------------------------------------------------------------
        # TEST 1: MULTI-RESOLUTION & VIEWPORT SCALING
        # -------------------------------------------------------------
        print("\n--- [TEST 1] Multi-Resolution & Viewport Scaling Stress ---")
        resolutions = [
            {"name": "16:9 Full HD", "width": 1920, "height": 1080},
            {"name": "16:9 720p HD", "width": 1280, "height": 720},
            {"name": "16:9 QHD", "width": 2560, "height": 1440},
            {"name": "16:9 4K UHD", "width": 3840, "height": 2160},
            {"name": "16:10 WUXGA", "width": 1920, "height": 1200},
            {"name": "16:10 WQXGA", "width": 2560, "height": 1600},
            {"name": "16:10 WSXGA+", "width": 1680, "height": 1050},
            {"name": "21:9 Ultrawide FHD", "width": 2560, "height": 1080},
            {"name": "21:9 Ultrawide QHD", "width": 3440, "height": 1440},
            {"name": "32:9 Super Ultrawide", "width": 5120, "height": 1440},
            {"name": "4:3 Legacy Desktop", "width": 1024, "height": 768},
            {"name": "16:9 Laptop", "width": 1366, "height": 768},
        ]

        for res in resolutions:
            w, h = res["width"], res["height"]
            page.set_viewport_size({"width": w, "height": h})
            page.wait_for_timeout(100)

            metrics = page.evaluate("""() => {
                const body = document.body;
                const doc = document.documentElement;
                const vp = document.getElementById('app-viewport');
                const vpRect = vp ? vp.getBoundingClientRect() : null;
                const canvas = document.getElementById('game-canvas');
                const canvasRect = canvas ? canvas.getBoundingClientRect() : null;
                return {
                    winWidth: window.innerWidth,
                    winHeight: window.innerHeight,
                    bodyScrollWidth: body.scrollWidth,
                    bodyClientWidth: body.clientWidth,
                    bodyScrollHeight: body.scrollHeight,
                    bodyClientHeight: body.clientHeight,
                    docScrollWidth: doc.scrollWidth,
                    docClientWidth: doc.clientWidth,
                    docScrollHeight: doc.scrollHeight,
                    docClientHeight: doc.clientHeight,
                    vpRect: vpRect ? { w: vpRect.width, h: vpRect.height, top: vpRect.top, left: vpRect.left } : null,
                    canvasRect: canvasRect ? { w: canvasRect.width, h: canvasRect.height } : null
                };
            }""")

            h_scroll = (metrics["bodyScrollWidth"] > metrics["bodyClientWidth"] + 1) or (metrics["docScrollWidth"] > metrics["docClientWidth"] + 1)
            v_scroll = (metrics["bodyScrollHeight"] > metrics["bodyClientHeight"] + 1) or (metrics["docScrollHeight"] > metrics["docClientHeight"] + 1)
            vp_match = metrics["vpRect"] and (abs(metrics["vpRect"]["w"] - w) <= 1) and (abs(metrics["vpRect"]["h"] - h) <= 1)

            res_result = {
                "name": res["name"],
                "resolution": f"{w}x{h}",
                "aspect": f"{w/h:.2f}:1",
                "h_scrollbar": h_scroll,
                "v_scrollbar": v_scroll,
                "viewport_100vw_100vh": vp_match,
                "metrics": metrics,
                "pass": (not h_scroll) and (not v_scroll) and vp_match,
            }
            report["resolutions_tested"].append(res_result)
            status_str = "PASS" if res_result["pass"] else "FAIL"
            print(f"  [{status_str}] {res['name']} ({w}x{h}): H-Scroll={h_scroll}, V-Scroll={v_scroll}, 100vw/vh={vp_match}")

        # Dynamic Viewport Resize Jitter
        print("\n--- [TEST 1B] Dynamic Rapid Resize Stress ---")
        jitter_seq = [
            (1920, 1080), (2560, 1080), (1280, 720), (3440, 1440), (1024, 768), (1920, 1080)
        ]
        for w, h in jitter_seq:
            page.set_viewport_size({"width": w, "height": h})
            page.wait_for_timeout(30)
            check = page.evaluate("() => ({ sw: document.documentElement.scrollWidth, cw: document.documentElement.clientWidth, sh: document.documentElement.scrollHeight, ch: document.documentElement.clientHeight })")
            no_scroll = (check["sw"] <= check["cw"] + 1) and (check["sh"] <= check["ch"] + 1)
            report["dynamic_resizes_tested"].append({"size": f"{w}x{h}", "pass": no_scroll})
        print(f"  Dynamic resize sequence completed: all {len(jitter_seq)} steps checked.")

        # Reset to standard 1920x1080
        page.set_viewport_size({"width": 1920, "height": 1080})
        page.wait_for_timeout(100)

        # -------------------------------------------------------------
        # TEST 2: ABSENCE OF MOBILE SIMULATION ARTIFACTS
        # -------------------------------------------------------------
        print("\n--- [TEST 2] Absence of Mobile Simulation Elements ---")
        mobile_audit = page.evaluate("""() => {
            const iphoneFrames = document.querySelectorAll('.iphone-frame').length;
            const simBars = document.querySelectorAll('#simulator-bar').length;
            const modeButtons = document.querySelectorAll('#mode-iphone, #mode-ipad, #mode-desktop').length;
            const joystickZone = document.getElementById('joystick-zone');
            let joystickVisible = false;
            if (joystickZone) {
                const style = window.getComputedStyle(joystickZone);
                joystickVisible = (style.display !== 'none' && style.visibility !== 'hidden' && joystickZone.offsetParent !== null);
            }
            const touchControls = document.querySelectorAll('.touch-control, .mobile-hud-btn').length;
            return {
                iphone_frames: iphoneFrames,
                sim_bars: simBars,
                mode_buttons: modeButtons,
                joystick_zone_present: !!joystickZone,
                joystick_zone_visible: joystickVisible,
                touch_controls: touchControls
            };
        }""")
        report["mobile_artifacts_audit"] = mobile_audit
        mobile_clean = (
            mobile_audit["iphone_frames"] == 0 and
            mobile_audit["sim_bars"] == 0 and
            mobile_audit["mode_buttons"] == 0 and
            not mobile_audit["joystick_zone_visible"]
        )
        print(f"  Mobile Elements Audit: clean={mobile_clean} details={mobile_audit}")

        # -------------------------------------------------------------
        # TEST 3: RAPID 9-LANGUAGE IN-PLACE CYCLING
        # -------------------------------------------------------------
        print("\n--- [TEST 3] Rapid 9-Language In-Place Cycling ---")
        page.evaluate("""() => {
            window.__sessionNavCount = 1;
            window.addEventListener('beforeunload', () => { window.__sessionNavCount++; });
        }""")

        languages = ["vi", "en", "zh", "ja", "ko", "th", "de", "ru", "es"]
        cycling_log = []
        # Cycle 2 full rounds (18 changes)
        for round_idx in [1, 2]:
            for lang in languages:
                t0 = time.time()
                page.select_option("#lang-select", lang)
                page.wait_for_timeout(40)
                dt = (time.time() - t0) * 1000

                lang_state = page.evaluate("""(lang) => {
                    const select = document.getElementById('lang-select');
                    const navCount = window.__sessionNavCount;
                    const minimapZone = document.getElementById('minimap-zone-label')?.textContent || '';
                    const chatPlaceholder = document.getElementById('chat-input')?.placeholder || '';
                    const pauseText = document.getElementById('overlay-pause')?.textContent || '';
                    const tooltipTitle = document.getElementById('tooltip-title')?.textContent || '';

                    // Check for corrupted string tokens
                    const allText = document.body.innerText;
                    const hasUndefined = allText.includes('undefined');
                    const hasMissingKey = allText.includes('MISSING_KEY');
                    const hasNull = allText.includes('null') && !allText.includes('nullify');

                    return {
                        selectedLang: select?.value,
                        targetLang: lang,
                        navCount: navCount,
                        minimapZoneSample: minimapZone.substring(0, 30),
                        hasUndefined: hasUndefined,
                        hasMissingKey: hasMissingKey,
                        hasNull: hasNull
                    };
                }""", lang)

                entry = {
                    "round": round_idx,
                    "lang": lang,
                    "duration_ms": round(dt, 2),
                    "selected": lang_state["selectedLang"],
                    "nav_count": lang_state["navCount"],
                    "corrupted_tokens": lang_state["hasUndefined"] or lang_state["hasMissingKey"],
                    "pass": (lang_state["selectedLang"] == lang) and (lang_state["navCount"] == 1) and not (lang_state["hasUndefined"] or lang_state["hasMissingKey"])
                }
                cycling_log.append(entry)

        report["i18n_cycling_metrics"] = {
            "total_transitions": len(cycling_log),
            "nav_count_final": page.evaluate("() => window.__sessionNavCount"),
            "all_passed": all(e["pass"] for e in cycling_log),
            "transitions": cycling_log
        }
        print(f"  Completed {len(cycling_log)} language transitions: all_passed={report['i18n_cycling_metrics']['all_passed']}, nav_count={report['i18n_cycling_metrics']['nav_count_final']}")

        # -------------------------------------------------------------
        # TEST 4: SIMULTANEOUS DUAL-DOCKING MODALS
        # -------------------------------------------------------------
        print("\n--- [TEST 4] Concurrent Dual-Docking Modals ---")
        # Ensure modals closed initially by hiding if already open
        page.evaluate("""() => {
            document.getElementById('modal-character')?.classList.add('hidden');
            document.getElementById('modal-bag')?.classList.add('hidden');
        }""")
        page.wait_for_timeout(50)

        # Press C to open Character Sheet
        page.keyboard.press("KeyC")
        page.wait_for_timeout(80)

        # Press I to open Bag
        page.keyboard.press("KeyI")
        page.wait_for_timeout(80)

        docking_metrics = page.evaluate("""() => {
            const charModal = document.getElementById('modal-character');
            const bagModal = document.getElementById('modal-bag');
            const winW = window.innerWidth;
            const winH = window.innerHeight;

            const charVisible = charModal && !charModal.classList.contains('hidden') && window.getComputedStyle(charModal).display !== 'none';
            const bagVisible = bagModal && !bagModal.classList.contains('hidden') && window.getComputedStyle(bagModal).display !== 'none';

            const charRect = charModal ? charModal.getBoundingClientRect() : null;
            const bagRect = bagModal ? bagModal.getBoundingClientRect() : null;

            let overlap = false;
            let centerClearance = 0;
            if (charRect && bagRect && charVisible && bagVisible) {
                // Check horizontal overlap
                overlap = !(charRect.right <= bagRect.left || bagRect.right <= charRect.left);
                centerClearance = bagRect.left - charRect.right;
            }

            return {
                charVisible: Boolean(charVisible),
                bagVisible: Boolean(bagVisible),
                charRect: charRect ? { left: charRect.left, right: charRect.right, top: charRect.top, width: charRect.width, height: charRect.height } : null,
                bagRect: bagRect ? { left: bagRect.left, right: bagRect.right, top: bagRect.top, width: bagRect.width, height: bagRect.height } : null,
                charDockedLeft: charRect ? (charRect.left < winW * 0.4) : false,
                bagDockedRight: bagRect ? (bagRect.right > winW * 0.6) : false,
                overlap: overlap,
                centerClearancePx: centerClearance,
                winWidth: winW,
                winHeight: winH
            };
        }""")

        report["dual_docking_metrics"] = docking_metrics
        dual_pass = (
            docking_metrics["charVisible"] and
            docking_metrics["bagVisible"] and
            docking_metrics["charDockedLeft"] and
            docking_metrics["bagDockedRight"] and
            not docking_metrics["overlap"] and
            docking_metrics["centerClearancePx"] > 0
        )
        print(f"  Dual Docking: pass={dual_pass}, charLeft={docking_metrics['charDockedLeft']}, bagRight={docking_metrics['bagDockedRight']}, clearance={docking_metrics['centerClearancePx']}px, overlap={docking_metrics['overlap']}")

        # Close modals via hotkeys
        page.keyboard.press("KeyC")
        page.wait_for_timeout(40)
        page.keyboard.press("KeyI")
        page.wait_for_timeout(40)

        # -------------------------------------------------------------
        # TEST 5: GAMEPLAY INTERACTIONS, ATLAS ASSET & CONSOLE CLEANLINESS
        # -------------------------------------------------------------
        print("\n--- [TEST 5] Gameplay Interactions & Clean Console Verification ---")
        # Movement click
        page.mouse.click(960 + 80, 540 + 40)
        page.wait_for_timeout(40)

        # RMB primary skill
        page.mouse.click(960 - 60, 540 + 60, button="right")
        page.wait_for_timeout(40)

        # Flasks 1-5
        for f in ["1", "2", "3", "4", "5"]:
            page.keyboard.press(f)
            page.wait_for_timeout(20)

        # Martial skills Q, W, E, R
        for k in ["KeyQ", "KeyW", "KeyE", "KeyR"]:
            page.keyboard.press(k)
            page.wait_for_timeout(30)

        # Space dodge
        page.keyboard.press("Space")
        page.wait_for_timeout(40)

        # Hover over action bar slots to trigger tooltips
        for slot_id in ["#skill-flask-1", "#skill-flask-2", "#skill-flask-3", "#skill-flask-4", "#skill-flask-5",
                        "#skill-q", "#skill-w", "#skill-e", "#skill-r", "#skill-lmb", "#skill-rmb", "#skill-dodge"]:
            slot = page.locator(slot_id)
            if slot.count() > 0:
                slot.hover(force=True)
                page.wait_for_timeout(20)

        # Verify martial_skills_atlas.png dimensions
        atlas_check = page.evaluate("""() => {
            return new Promise((resolve) => {
                const img = new Image();
                img.onload = () => {
                    resolve({
                        loaded: true,
                        naturalWidth: img.naturalWidth,
                        naturalHeight: img.naturalHeight,
                        src: img.src
                    });
                };
                img.onerror = (e) => {
                    resolve({
                        loaded: false,
                        error: 'Image onerror triggered',
                        src: img.src
                    });
                };
                img.src = '/webapp/assets/skills/martial_skills_atlas.png';
            });
        }""")
        report["network_and_console_audit"]["atlas_image_status"] = atlas_check
        print(f"  Atlas Image Check: loaded={atlas_check.get('loaded')} dimensions={atlas_check.get('naturalWidth')}x{atlas_check.get('naturalHeight')}")

        # Wait to catch any delayed errors
        page.wait_for_timeout(200)

        # Check final console and network errors
        p_errors = report["network_and_console_audit"]["page_errors"]
        c_errors = report["network_and_console_audit"]["console_errors"]
        h_404s = report["network_and_console_audit"]["http_404s"]
        h_500s = report["network_and_console_audit"]["http_500s"]

        print(f"\n--- [AUDIT SUMMARY] ---")
        print(f"  Page Errors: {len(p_errors)} -> {p_errors}")
        print(f"  Console Errors: {len(c_errors)} -> {c_errors}")
        print(f"  HTTP 404s: {len(h_404s)} -> {h_404s}")
        print(f"  HTTP 500s: {len(h_500s)} -> {h_500s}")

        # Assess overall verdict
        resolutions_all_pass = all(r["pass"] for r in report["resolutions_tested"])
        resizes_all_pass = all(r["pass"] for r in report["dynamic_resizes_tested"])
        i18n_all_pass = report["i18n_cycling_metrics"]["all_passed"]
        clean_errors = (len(p_errors) == 0 and len(c_errors) == 0 and len(h_404s) == 0 and len(h_500s) == 0)
        atlas_pass = atlas_check.get("loaded") is True and atlas_check.get("naturalWidth") == 1024

        overall_pass = (
            resolutions_all_pass and
            resizes_all_pass and
            mobile_clean and
            i18n_all_pass and
            dual_pass and
            clean_errors and
            atlas_pass
        )

        report["verdict"] = "APPROVE" if overall_pass else "REJECT"
        print(f"\n==========================================")
        print(f"OVERALL EMPIRICAL VERDICT: {report['verdict']}")
        print(f"==========================================")

        page.close()
        context.close()
        browser.close()
    finally:
        p.stop()
        server.shutdown()
        server.server_close()

    # Save report to working directory
    out_file = Path(__file__).resolve().parent / "empirical_stress_report.json"
    out_file.write_text(json.dumps(report, indent=2), encoding="utf-8")
    print(f"\nReport written to: {out_file}")
    return report


if __name__ == "__main__":
    run_adversarial_suite()
