"""
Adversarial Stress Audit for FreeExile PC Desktop Web Client.
Tests:
1. Path traversal and edge-case URL resolution in FreeExilePCRequestHandler
2. Fallback routing correctness and 404 security
3. Kinematics edge cases (large coords, zero dist, NaN resilience)
4. Headless browser rapid multi-input stress test
"""

import math
import socketserver
import threading
import urllib.request
import urllib.error
from pathlib import Path
import sys

PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from tools.serve_web_pc import FreeExilePCRequestHandler, PC_DIR, WEBAPP_DIR


def test_server_security_and_routing():
    print("[*] Testing server security, path traversal and routing...")
    handler = FreeExilePCRequestHandler.__new__(FreeExilePCRequestHandler)

    # 1. Path traversal attacks
    traversal_paths = [
        "../../../../Windows/win.ini",
        "..%2f..%2f..%2fWindows%2fwin.ini",
        "....//....//....//config.json",
        "/etc/passwd",
        "/web_pc/../../server/auth/database.py",
    ]
    for tp in traversal_paths:
        resolved = handler._resolve_static_path(tp)
        # Should NOT resolve to any file outside intended directories
        if resolved is not None:
            # Check if resolved is within PC_DIR or WEBAPP_DIR
            is_in_pc = str(resolved.resolve()).startswith(str(PC_DIR.resolve()))
            is_in_webapp = str(resolved.resolve()).startswith(str(WEBAPP_DIR.resolve()))
            assert is_in_pc or is_in_webapp, f"SECURITY VIOLATION: Traversal {tp} escaped to {resolved}"
    print("  -> Path traversal attempts safely rejected or contained within root: PASS")

    # 2. Asset fallback routing
    prefixes = [
        "web_pc/css/assets/skills/martial_skills_atlas.png",
        "web_pc/assets/skills/martial_skills_atlas.png",
        "assets/skills/martial_skills_atlas.png",
        "webapp/assets/skills/martial_skills_atlas.png",
    ]
    for p in prefixes:
        res = handler._resolve_static_path(p)
        assert res is not None and res.is_file(), f"Fallback route failed for {p}"
        assert res.name == "martial_skills_atlas.png"
    print("  -> All 4 asset fallback prefixes resolve to real file: PASS")

    # 3. Invalid paths return None (404)
    invalid_paths = [
        "assets/skills/does_not_exist_xyz.png",
        "web_pc/css/non_existent.css",
        "completely_bogus_file.bin",
    ]
    for ip in invalid_paths:
        res = handler._resolve_static_path(ip)
        assert res is None, f"Invalid path should resolve to None, got {res}"
    print("  -> Invalid paths correctly resolve to None (yielding 404): PASS")


def test_kinematics_boundary_stress():
    print("[*] Testing kinematics and mathematical edge cases...")
    # Zero distance
    player_wx, player_wy = 5.0, 5.0
    dest_wx, dest_wy = 5.0, 5.0
    destDx = dest_wx - player_wx
    destDy = dest_wy - player_wy
    dist = math.hypot(destDx, destDy)
    assert dist == 0.0
    assert not (dist > 0.22), "Zero dist must not trigger movement"

    # Minimal distance near threshold
    near_wx, near_wy = 5.0 + 0.15, 5.0 + 0.15
    near_dist = math.hypot(near_wx - player_wx, near_wy - player_wy)
    assert near_dist <= 0.22, "Near threshold must snap inactive"

    # Large coordinates
    big_wx, big_wy = 1_000_000.0, -1_000_000.0
    big_dx = big_wx - player_wx
    big_dy = big_wy - player_wy
    big_dist = math.hypot(big_dx, big_dy)
    assert big_dist > 0.22
    norm_x = big_dx / big_dist
    norm_y = big_dy / big_dist
    assert math.isclose(math.hypot(norm_x, norm_y), 1.0, rel_tol=1e-9)

    # Angle edge cases
    assert math.isclose(math.atan2(0.0, 0.0), 0.0)
    assert math.isclose(math.atan2(0.0, -1.0), math.pi)
    assert math.isclose(math.atan2(-0.0, -1.0), -math.pi)
    print("  -> Kinematics mathematical bounds: PASS")


def test_live_browser_stress():
    print("[*] Testing live browser under heavy input stress...")
    from playwright.sync_api import sync_playwright

    server = socketserver.TCPServer(("127.0.0.1", 0), FreeExilePCRequestHandler)
    port = server.server_address[1]
    threading.Thread(target=server.serve_forever, daemon=True).start()

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

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

        page_errors = []
        console_errors = []
        page.on("pageerror", lambda e: page_errors.append(str(e)))
        page.on("console", lambda m: console_errors.append(m.text) if m.type == "error" else None)

        page.goto(f"http://127.0.0.1:{port}/index.html")
        page.wait_for_timeout(1000)

        # Rapid fire inputs:
        # Rapid mouse clicks across random viewport positions
        import random
        for _ in range(25):
            rx = random.randint(200, 1700)
            ry = random.randint(200, 800)
            btn = random.choice(["left", "right"])
            page.mouse.click(rx, ry, button=btn)

        # Rapid keyboard mash:
        keys_to_mash = ["1", "2", "3", "4", "5", "KeyQ", "KeyW", "KeyE", "KeyR", "Space", "KeyI", "KeyC", "KeyM", "Escape"]
        for _ in range(30):
            k = random.choice(keys_to_mash)
            page.keyboard.press(k)

        page.wait_for_timeout(500)

        assert len(page_errors) == 0, f"Page errors during input stress: {page_errors}"
        # Filter out harmless 404s on optional map/favicon if any
        critical_console = [c for c in console_errors if "favicon" not in c]
        assert len(critical_console) == 0, f"Console errors during input stress: {critical_console}"

        context.close()
        b.close()

    server.shutdown()
    print("  -> Live browser input mash stress: PASS (0 errors)")


if __name__ == "__main__":
    test_server_security_and_routing()
    test_kinematics_boundary_stress()
    test_live_browser_stress()
    print("\n[SUCCESS] ALL ADVERSARIAL STRESS AUDIT CHECKS PASSED EMPIRICALLY!")
