"""
Empirical Adversarial Stress Test Harness for FreeExile PC Desktop Web Client.
Executed by challenger_18_1 (Empirical Controls & Kinematics Challenger).
Tests:
1. Kinematics & Extreme Coordinate Stress (Rapid clicks, Hold-to-Move dragging, Zero-dist, NaN guard)
2. Movement Decoupling & Key W Isolation (No WASD movement, Key W fires Martial Skill 2)
3. Spacebar Dodge Roll & Attack Interruption (0.25s i-frame, attack cancel, 3 charges accounting)
4. Flask 1-5 Spamming & Cooldown Integrity (Rapid 1-5 spam, buff application, cooldown locking)
5. Chat Typing Focus Guard (Exclusion of QWER12345 while focused, instant re-enable on blur)
6. Combined Concurrency Soak Test (Console error traps, unhandled exceptions)
"""

from __future__ import annotations
import json
import math
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
from playwright.sync_api import sync_playwright


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


def run_stress_test() -> Dict[str, Any]:
    # Start ephemeral HTTP server
    server = socketserver.TCPServer(("127.0.0.1", 0), SilentPCServer)
    port = server.server_address[1]
    server_thread = threading.Thread(target=server.serve_forever, daemon=True)
    server_thread.start()
    base_url = f"http://127.0.0.1:{port}"

    results: Dict[str, Any] = {
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "suites": {},
        "metrics": {},
        "defects": [],
        "verdict": "PENDING"
    }

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

            page = browser.new_page(viewport={"width": 1920, "height": 1080})
            
            # Trap all console logs and page errors
            all_page_errors: List[str] = []
            all_console_errors: List[str] = []
            page.on("pageerror", lambda err: all_page_errors.append(str(err)))
            page.on("console", lambda m: all_console_errors.append(f"ConsoleError: {m.text}") if m.type == "error" else None)

            page.add_init_script("try { localStorage.setItem('fe_sim_mode', 'fullscreen'); } catch(e){}")
            page.goto(f"{base_url}/index.html")
            page.wait_for_timeout(400)
            page.wait_for_function("() => window.player && window.PcInputController")

            # -------------------------------------------------------------
            # SUITE 1: KINEMATICS & EXTREME COORDINATE STRESS TEST
            # -------------------------------------------------------------
            print("\n[SUITE 1] Testing Kinematics & Extreme Coordinates...")
            suite1_metrics = {"rapid_clicks": 100, "nan_detected": False, "dest_tracking_ok": True}
            
            coords_to_click = [
                (0, 0), (1920, 1080), (960, 540), (100, 900), (1800, 200),
                (960 + 50, 540 - 50), (960 - 200, 540 + 200)
            ]
            for i in range(100):
                cx, cy = coords_to_click[i % len(coords_to_click)]
                page.mouse.click(cx, cy)
            
            page.wait_for_timeout(50)
            dest_state = page.evaluate("() => ({ wx: window.player?.destination?.wx, wy: window.player?.destination?.wy, active: window.player?.destination?.active, p_wx: window.player?.wx, p_wy: window.player?.wy, dirX: window.player?.dirX, dirY: window.player?.dirY })")
            for k, val in dest_state.items():
                if val is not None and isinstance(val, (int, float)):
                    if math.isnan(val) or math.isinf(val):
                        suite1_metrics["nan_detected"] = True
            
            # Zero-distance click directly on character
            zero_dist_res = page.evaluate("""() => {
                const p = window.player;
                const pPos = window.worldToIso ? window.worldToIso(p.wx, p.wy) : { x: 960, y: 540 };
                window.PcInputController.onPointerDown({ button: 0, clientX: pPos.x, clientY: pPos.y });
                return {
                    wx: p.wx, wy: p.wy,
                    destWx: p.destination?.wx, destWy: p.destination?.wy,
                    destActive: p.destination?.active,
                    dirX: p.dirX, dirY: p.dirY
                };
            }""")
            if any(math.isnan(v) for v in [zero_dist_res['wx'], zero_dist_res['wy'], zero_dist_res['dirX'], zero_dist_res['dirY']] if v is not None):
                suite1_metrics["nan_detected"] = True

            # Continuous Hold-to-Move dragging
            page.mouse.move(960, 540)
            page.mouse.down()
            drag_trajectory = [(1000, 600), (1100, 650), (1200, 700), (1100, 750), (950, 500), (850, 400)]
            drag_success = True
            for tx, ty in drag_trajectory:
                page.mouse.move(tx, ty, steps=2)
                cur_dest = page.evaluate("() => ({ isHeld: window.PcInputController?.isLmbHeld, active: window.player?.destination?.active })")
                if not cur_dest['isHeld'] or not cur_dest['active']:
                    drag_success = False
            page.mouse.up()
            is_held_after_up = page.evaluate("() => window.PcInputController?.isLmbHeld")
            suite1_metrics["hold_to_move_ok"] = drag_success and (is_held_after_up is False)

            # Bring player to complete standstill for clean state
            page.evaluate("""() => {
                if (window.player.destination) window.player.destination.active = false;
                window.player.dirX = 0;
                window.player.dirY = 0;
            }""")

            results["suites"]["suite1_kinematics"] = {
                "status": "PASS" if not suite1_metrics["nan_detected"] and suite1_metrics["hold_to_move_ok"] else "FAIL",
                "details": suite1_metrics
            }

            # -------------------------------------------------------------
            # SUITE 2: W DECOUPLING & KEYBOARD ISOLATION
            # -------------------------------------------------------------
            print("\n[SUITE 2] Testing Key W Decoupling from Movement...")
            suite2_metrics = {}

            wasd_disabled = page.evaluate("() => window.enableWasdMovement === false && Object.keys(window.keys || {}).length === 0")
            suite2_metrics["wasd_flags_ok"] = wasd_disabled

            pos_before = page.evaluate("() => ({ wx: window.player.wx, wy: window.player.wy })")
            page.keyboard.down("KeyW")
            page.wait_for_timeout(350)
            page.keyboard.up("KeyW")
            pos_after = page.evaluate("() => ({ wx: window.player.wx, wy: window.player.wy })")

            pos_diff = math.hypot(pos_after['wx'] - pos_before['wx'], pos_after['wy'] - pos_before['wy'])
            suite2_metrics["position_drift_on_w"] = pos_diff
            suite2_metrics["w_does_not_move"] = pos_diff < 0.001

            keys_has_w = page.evaluate("() => !!(window.keys && (window.keys['w'] || window.keys['W']))")
            suite2_metrics["keys_dict_clean"] = not keys_has_w

            results["suites"]["suite2_w_decoupling"] = {
                "status": "PASS" if suite2_metrics["w_does_not_move"] and suite2_metrics["keys_dict_clean"] else "FAIL",
                "details": suite2_metrics
            }

            # -------------------------------------------------------------
            # SUITE 3: SPACEBAR DODGE ROLL, I-FRAME & ATTACK CANCELLATION
            # -------------------------------------------------------------
            print("\n[SUITE 3] Testing Spacebar Dodge, i-Frame & Attack Cancel...")
            suite3_metrics = {}

            initial_charges = page.evaluate("""() => {
                const s = window.skillBarController?.slots?.get('dodge');
                return s ? s.charges : window.dodgeCharges;
            }""")
            suite3_metrics["initial_dodge_charges"] = initial_charges

            # Initiate attack
            page.evaluate("""() => {
                window.player.animState = 'attack';
                window.player.attackTimer = 0.3;
                window.player.isChanneling = true;
            }""")

            # Press Spacebar to cancel attack and initiate dodge
            page.keyboard.press("Space")
            page.wait_for_timeout(30)

            cancel_results = page.evaluate("""() => {
                const p = window.player;
                const slot = window.skillBarController?.slots?.get('dodge');
                return {
                    animState: p.animState,
                    attackTimer: p.attackTimer,
                    isChanneling: p.isChanneling,
                    isIFrame: p.isIFrame,
                    iFrameTimer: p.iFrameTimer,
                    ghostTrailsCount: p.ghostTrails ? p.ghostTrails.length : 0,
                    chargesAfter1stDodge: slot ? slot.charges : window.dodgeCharges
                };
            }""")
            suite3_metrics["attack_cancelled"] = (cancel_results["attackTimer"] == 0 and not cancel_results["isChanneling"])
            suite3_metrics["dodge_anim_set"] = cancel_results["animState"] in ["dodge", "run", "idle"]
            suite3_metrics["iframe_granted"] = cancel_results["isIFrame"] is True
            suite3_metrics["charges_after_1st_dodge"] = cancel_results["chargesAfter1stDodge"]

            # Double-decrement defect check: expected initial_charges - 1 (2), actual was 1
            if cancel_results["chargesAfter1stDodge"] == 1 and initial_charges == 3:
                suite3_metrics["double_decrement_detected"] = True
                results["defects"].append({
                    "id": "DEFECT-DODGE-DOUBLE-DECREMENT",
                    "severity": "HIGH",
                    "description": "Spacebar dodge roll decrements charges twice (from 3 to 1) per single press due to dual invocation in combat_skills.js and pc_input_controller.js."
                })
            else:
                suite3_metrics["double_decrement_detected"] = False

            # Wait for i-frame timer expiration (> 250ms) to detect badge-iframe crash
            errors_before = len(all_page_errors)
            page.wait_for_timeout(300)
            errors_after = len(all_page_errors)
            
            iframe_expired = page.evaluate("() => window.player?.isIFrame")
            suite3_metrics["iframe_expired"] = (iframe_expired is False)

            if errors_after > errors_before:
                new_err = all_page_errors[-1]
                if "classList" in new_err and "null" in new_err:
                    suite3_metrics["badge_iframe_null_crash"] = True
                    results["defects"].append({
                        "id": "DEFECT-BADGE-IFRAME-NULL-CRASH",
                        "severity": "CRITICAL",
                        "description": "canvas_renderer.js:219 crashes with 'Cannot read properties of null (reading classList)' when iFrame expires because #badge-iframe is missing from client/web_pc/index.html."
                    })

            results["suites"]["suite3_dodge_iframe"] = {
                "status": "FAIL" if suite3_metrics.get("double_decrement_detected") or suite3_metrics.get("badge_iframe_null_crash") else "PASS",
                "details": suite3_metrics
            }

            # -------------------------------------------------------------
            # SUITE 4: FLASK 1-5 SPAMMING & COOLDOWN INTEGRITY
            # -------------------------------------------------------------
            print("\n[SUITE 4] Testing Flask 1-5 Spamming & Cooldowns...")
            suite4_metrics = {}

            for key in ["1", "2", "3", "4", "5"]:
                page.keyboard.press(key)
                page.wait_for_timeout(10)

            flasks_state = page.evaluate("""() => {
                const now = performance.now() / 1000;
                const flasks = window.PcInputController?.flasks || {};
                const res = {};
                for (let i = 1; i <= 5; i++) {
                    const f = flasks[i];
                    res[i] = f ? { cd: f.cd, timer: f.timer, onCd: f.timer > now } : null;
                }
                const p = window.player;
                return {
                    flasks: res,
                    playerSpeed: p?.speed,
                    defenseBuff: p?.defenseBuff,
                    damageMultiplier: p?.damageMultiplier
                };
            }""")
            suite4_metrics["flasks_state"] = flasks_state["flasks"]
            suite4_metrics["all_flasks_triggered"] = all(f and f.get("onCd") for f in flasks_state["flasks"].values())

            # Spam flask 1
            for _ in range(10):
                page.keyboard.press("1")
                page.wait_for_timeout(5)
            f1_post = page.evaluate("() => window.PcInputController?.flasks?.[1]?.timer")
            suite4_metrics["f1_timer_stable"] = (f1_post is not None and not math.isnan(f1_post))

            results["suites"]["suite4_flask_spam"] = {
                "status": "PASS" if suite4_metrics["all_flasks_triggered"] and suite4_metrics["f1_timer_stable"] else "FAIL",
                "details": suite4_metrics
            }

            # -------------------------------------------------------------
            # SUITE 5: CHAT INPUT TYPING FOCUS GUARD
            # -------------------------------------------------------------
            print("\n[SUITE 5] Testing Chat Typing Focus Guard...")
            suite5_metrics = {}

            page.evaluate("""() => {
                document.getElementById('chat-minimized-bar')?.click();
                document.getElementById('chat-expanded-panel')?.classList.remove('hidden');
                document.getElementById('chat-input')?.focus();
            }""")
            page.wait_for_timeout(50)

            cd_before = page.evaluate("""() => {
                const sQ = window.skillBarController?.slots?.get('q')?.cooldownRemaining || 0;
                const sW = window.skillBarController?.slots?.get('w')?.cooldownRemaining || 0;
                const charges = window.skillBarController?.slots?.get('dodge')?.charges || 0;
                return { q: sQ, w: sW, charges };
            }""")

            page.keyboard.type("QWER12345 ")
            typed_val = page.evaluate("() => document.getElementById('chat-input')?.value")
            suite5_metrics["typed_value"] = typed_val
            suite5_metrics["typed_received"] = ("QWER12345 " in (typed_val or ""))

            cd_after = page.evaluate("""() => {
                const sQ = window.skillBarController?.slots?.get('q')?.cooldownRemaining || 0;
                const sW = window.skillBarController?.slots?.get('w')?.cooldownRemaining || 0;
                const charges = window.skillBarController?.slots?.get('dodge')?.charges || 0;
                return { q: sQ, w: sW, charges };
            }""")

            suite5_metrics["skills_suppressed"] = (cd_after["q"] == cd_before["q"] and cd_after["w"] == cd_before["w"])
            suite5_metrics["dodge_suppressed"] = (cd_after["charges"] == cd_before["charges"])

            page.evaluate("() => document.getElementById('chat-input')?.blur()")
            page.wait_for_timeout(50)

            results["suites"]["suite5_chat_focus_guard"] = {
                "status": "PASS" if suite5_metrics["typed_received"] and suite5_metrics["skills_suppressed"] and suite5_metrics["dodge_suppressed"] else "FAIL",
                "details": suite5_metrics
            }

            # -------------------------------------------------------------
            # SUITE 6: COMBINED CONCURRENCY SOAK & ERROR FORENSICS
            # -------------------------------------------------------------
            print("\n[SUITE 6] Running Concurrency Soak & Error Forensics...")
            for i in range(25):
                page.mouse.click(960 + (i * 10) % 200, 540 + (i * 8) % 150)
                page.keyboard.press("KeyW")
                page.keyboard.press("2")
                page.mouse.click(960, 540, button="right")
                page.wait_for_timeout(20)

            unexpected_errors = [e for e in all_page_errors + all_console_errors if "favicon" not in e and "404" not in e and "api/map" not in e]
            suite6_metrics = {
                "total_errors": len(unexpected_errors),
                "errors": unexpected_errors,
                "soak_iterations": 25
            }
            results["suites"]["suite6_concurrency_soak"] = {
                "status": "PASS" if len(unexpected_errors) == 0 else "FAIL",
                "details": suite6_metrics
            }

            # Overall Verdict
            all_passed = all(s["status"] == "PASS" for s in results["suites"].values())
            results["verdict"] = "APPROVE" if all_passed else "REJECT"
            results["metrics"] = {
                "total_suites": len(results["suites"]),
                "passed_suites": sum(1 for s in results["suites"].values() if s["status"] == "PASS"),
                "failed_suites": sum(1 for s in results["suites"].values() if s["status"] == "FAIL"),
                "total_defects": len(results["defects"])
            }

            page.close()
            browser.close()

    finally:
        server.shutdown()
        server.server_close()

    return results


if __name__ == "__main__":
    test_results = run_stress_test()
    output_path = Path(__file__).parent / "stress_test_results.json"
    output_path.write_text(json.dumps(test_results, indent=2), encoding="utf-8")
    print(f"\n[RESULTS] Stress test complete. Output saved to {output_path}")
    print(json.dumps(test_results, indent=2))
    sys.exit(0 if test_results["verdict"] == "APPROVE" else 1)
