"""
EMPIRICAL RESILIENCE & ERROR CLEANLINESS CHALLENGER HARNESS
Agent: challenger_18_4
Directory: c:/Projects/FreeExile/.agents/teamwork/challenger_18_4/

Empirically tests:
1. Primary attack (RMB and Key R) audio execution with sfxEngine.playWeaponSlash (0 TypeErrors)
2. 10 key/mouse action triggers (click, KeyQ, KeyW, KeyE, KeyR, Key1, Space, KeyC, KeyI, Escape) with 0 errors
3. Multi-resolution display scaling across 16:9, 16:10, 21:9, 32:9 viewports with 0 scrollbars
4. Server path traversal rejection on multiple exploit vectors (confirming HTTP 404)
"""

import json
import socketserver
import sys
import threading
import time
import urllib.error
import urllib.request
from pathlib import Path

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, *args):
        pass


def test_server_path_traversal(base_url: str):
    print("=== CHALLENGE 1: Server Path Traversal Rejection ===")
    traversal_vectors = [
        "/../../../../Windows/win.ini",
        "/../../.git/config",
        "/%2e%2e/%2e%2e/Windows/win.ini",
        "/C:/Windows/win.ini",
        "/web_pc/../../../../Windows/win.ini",
        "/webapp/../../../../Windows/win.ini",
        "/assets/../../../../Windows/win.ini",
        "/..%2f..%2f..%2fWindows/win.ini",
        "/..\\..\\..\\Windows\\win.ini",
        "/%2e%2e%2f%2e%2e%2fWindows%2fwin.ini",
        "/web_pc/..%2f..%2f.git/config",
    ]

    results = []
    for vector in traversal_vectors:
        url = f"{base_url}{vector}"
        try:
            req = urllib.request.Request(url)
            with urllib.request.urlopen(req) as resp:
                status = resp.status
                results.append((vector, status, "EXPLOIT_SUCCESS (FAIL)"))
        except urllib.error.HTTPError as e:
            results.append((vector, e.code, "REJECTED (PASS)" if e.code == 404 else f"UNEXPECTED_STATUS_{e.code}"))
        except Exception as e:
            results.append((vector, str(e), "ERROR"))

    all_passed = True
    for vec, status, verdict in results:
        print(f"  [{verdict}] {vec} -> {status}")
        if "PASS" not in verdict:
            all_passed = False

    return all_passed, results


def run_browser_challenges(base_url: str):
    print("\n=== CHALLENGE 2: Multi-Resolution & Gameplay Error Cleanliness ===")
    resolutions = [
        ("1920x1080", 1920, 1080, "16:9 FHD"),
        ("1280x720", 1280, 720, "16:9 720p"),
        ("2560x1440", 2560, 1440, "16:9 QHD"),
        ("3840x2160", 3840, 2160, "16:9 4K UHD"),
        ("1920x1200", 1920, 1200, "16:10 WUXGA"),
        ("2560x1600", 2560, 1600, "16:10 WQXGA"),
        ("1680x1050", 1680, 1050, "16:10 WSXGA+"),
        ("2560x1080", 2560, 1080, "21:9 Ultrawide FHD"),
        ("3440x1440", 3440, 1440, "21:9 Ultrawide QHD"),
        ("5120x1440", 5120, 1440, "32:9 Super Ultrawide"),
        ("1024x768", 1024, 768, "4:3 Legacy"),
    ]

    with sync_playwright() as p:
        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()

        page_errors = []
        console_errors = []

        def handle_page_error(err):
            page_errors.append({
                "message": getattr(err, "message", str(err)),
                "name": getattr(err, "name", "Error"),
                "stack": getattr(err, "stack", "")
            })

        def handle_console_message(msg):
            if msg.type == "error":
                text = msg.text
                if "favicon" not in text:
                    console_errors.append(text)

        page.on("pageerror", handle_page_error)
        page.on("console", handle_console_message)

        # Set mock auth and character
        page.add_init_script("""
            try {
                localStorage.setItem('fe_sim_mode', 'fullscreen');
                localStorage.setItem('freeexile_auth_session', JSON.stringify({
                    account_id: 'acc_challenger_18_4',
                    username: 'EmpiricalChallenger',
                    status: 'ACTIVE'
                }));
                localStorage.setItem('fe_chars_acc_challenger_18_4', 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){}
        """)

        page.goto(f"{base_url}/index.html")
        page.wait_for_timeout(350)

        # 1. Multi-Resolution Audit
        print("--- Testing Multi-Resolution Display Scaling ---")
        res_results = []
        for name, w, h, category in resolutions:
            page.set_viewport_size({"width": w, "height": h})
            page.wait_for_timeout(60)
            dims = page.evaluate("""() => {
                const body = document.body;
                const html = document.documentElement;
                const vp = document.getElementById('app-viewport');
                const vpRect = vp ? vp.getBoundingClientRect() : null;
                return {
                    bodyScrollW: body.scrollWidth,
                    bodyClientW: body.clientWidth,
                    bodyScrollH: body.scrollHeight,
                    bodyClientH: body.clientHeight,
                    htmlScrollW: html.scrollWidth,
                    htmlClientW: html.clientWidth,
                    htmlScrollH: html.scrollHeight,
                    htmlClientH: html.clientHeight,
                    vpWidth: vpRect ? vpRect.width : 0,
                    vpHeight: vpRect ? vpRect.height : 0
                };
            }""")
            has_h_scroll = dims["bodyScrollW"] > dims["bodyClientW"] + 1 or dims["htmlScrollW"] > dims["htmlClientW"] + 1
            has_v_scroll = dims["bodyScrollH"] > dims["bodyClientH"] + 1 or dims["htmlScrollH"] > dims["htmlClientH"] + 1
            passed = (not has_h_scroll) and (not has_v_scroll)
            res_results.append((name, category, passed, dims))
            status_str = "PASS" if passed else "FAIL"
            print(f"  [{status_str}] {name} ({category}) -> Scrollbar H:{has_h_scroll} V:{has_v_scroll}, VP: {dims['vpWidth']}x{dims['vpHeight']}")

        # Restore 1080p
        page.set_viewport_size({"width": 1920, "height": 1080})
        page.wait_for_timeout(50)

        # 2. Audio Aliasing & Execution Audit
        print("\n--- Testing Primary Attack Audio Aliasing & Execution ---")
        audio_check = page.evaluate("""() => {
            const hasSfx = typeof window.sfxEngine !== 'undefined' && window.sfxEngine !== null;
            const hasPlayWeaponSlash = hasSfx && typeof window.sfxEngine.playWeaponSlash === 'function';
            const hasPlayBladeSlash = hasSfx && typeof window.sfxEngine.playBladeSlash === 'function';
            const hasPrototypeAlias = typeof window.SFXEngine !== 'undefined' && typeof window.SFXEngine.prototype.playWeaponSlash === 'function';
            
            // Check calling playWeaponSlash directly
            let directCallResult = 'NOT_CALLED';
            try {
                if (hasPlayWeaponSlash) {
                    window.sfxEngine.playWeaponSlash();
                    directCallResult = 'SUCCESS';
                }
            } catch(e) {
                directCallResult = e.toString();
            }

            // Check invoking doPrimaryAttack directly
            let primaryAttackResult = 'NOT_CALLED';
            try {
                if (typeof window.doPrimaryAttack === 'function') {
                    window.doPrimaryAttack();
                    primaryAttackResult = 'SUCCESS';
                }
            } catch(e) {
                primaryAttackResult = e.toString();
            }

            return {
                hasSfx,
                hasPlayWeaponSlash,
                hasPlayBladeSlash,
                hasPrototypeAlias,
                directCallResult,
                primaryAttackResult
            };
        }""")
        print(f"  Audio check: {json.dumps(audio_check, indent=2)}")

        # 3. 10 Key/Mouse Action Triggers Sequentially
        print("\n--- Testing 10 Key/Mouse Action Triggers Sequentially ---")
        actions = [
            ("1. Mouse Click (LMB Move)", lambda: page.mouse.click(1060, 580)),
            ("2. KeyQ (Fire Skill)", lambda: page.keyboard.press("KeyQ")),
            ("3. KeyW (Thunder Skill)", lambda: page.keyboard.press("KeyW")),
            ("4. KeyE (Frost Skill)", lambda: page.keyboard.press("KeyE")),
            ("5. KeyR (Primary Attack)", lambda: page.keyboard.press("KeyR")),
            ("6. Key1 (Survival Flask)", lambda: page.keyboard.press("1")),
            ("7. Space (Huyễn Ảnh Bộ Dodge Roll)", lambda: page.keyboard.press("Space")),
            ("8. KeyC (Character Sheet Toggle)", lambda: page.keyboard.press("KeyC")),
            ("9. KeyI (Inventory Bag Toggle)", lambda: page.keyboard.press("KeyI")),
            ("10. Escape (Pause Menu Toggle)", lambda: page.keyboard.press("Escape")),
        ]

        action_results = []
        for name, act in actions:
            err_count_before = len(page_errors) + len(console_errors)
            act()
            page.wait_for_timeout(60)
            err_count_after = len(page_errors) + len(console_errors)
            diff = err_count_after - err_count_before
            status = "PASS" if diff == 0 else f"FAIL ({diff} errors)"
            print(f"  [{status}] {name}")
            action_results.append((name, status))

        # Close any opened modals / pause
        page.keyboard.press("Escape")
        page.wait_for_timeout(60)

        # 4. Right Mouse Click (RMB Primary Attack Trigger)
        print("\n--- Testing RMB Primary Attack Trigger on Canvas ---")
        err_before_rmb = len(page_errors) + len(console_errors)
        page.mouse.click(980, 560, button="right")
        page.wait_for_timeout(100)
        err_after_rmb = len(page_errors) + len(console_errors)
        rmb_status = "PASS" if (err_after_rmb == err_before_rmb) else "FAIL"
        print(f"  [{rmb_status}] Right-Click (RMB Primary Attack)")

        # 5. Dodge i-Frame Lifecycle & Badge Expiration Test
        print("\n--- Testing Space Dodge i-Frame Expiration (0.25s) ---")
        err_before_dodge = len(page_errors) + len(console_errors)
        page.keyboard.press("Space")
        # Wait 350ms to allow iFrameTimer (0.25s) to expire and trigger classList.add('hidden')
        page.wait_for_timeout(350)
        err_after_dodge = len(page_errors) + len(console_errors)
        dodge_status = "PASS" if (err_after_dodge == err_before_dodge) else "FAIL"
        print(f"  [{dodge_status}] Dodge roll i-frame lifecycle and expiration (0.25s)")

        # Inspect DOM for #badge-iframe presence
        badge_exists = page.evaluate("() => !!document.getElementById('badge-iframe')")
        print(f"  DOM element #badge-iframe exists: {badge_exists}")

        browser.close()

    print("\n--- Final Browser Error Summary ---")
    print(f"  Page errors intercepted: {len(page_errors)}")
    for e in page_errors:
        print(f"    - {e['name']}: {e['message']}")
    print(f"  Console errors intercepted: {len(console_errors)}")
    for c in console_errors:
        print(f"    - {c}")

    return {
        "res_results": res_results,
        "audio_check": audio_check,
        "action_results": action_results,
        "rmb_status": rmb_status,
        "dodge_status": dodge_status,
        "badge_exists": badge_exists,
        "page_errors": page_errors,
        "console_errors": console_errors
    }


def main():
    server = socketserver.TCPServer(("127.0.0.1", 0), SilentPCServer)
    port = server.server_address[1]
    threading.Thread(target=server.serve_forever, daemon=True).start()
    base_url = f"http://127.0.0.1:{port}"

    try:
        traversal_pass, traversal_results = test_server_path_traversal(base_url)
        browser_report = run_browser_challenges(base_url)

        print("\n================ OVERALL VERDICT ================")
        all_passed = (
            traversal_pass
            and all(r[2] for r in browser_report["res_results"])
            and browser_report["audio_check"]["directCallResult"] == "SUCCESS"
            and browser_report["audio_check"]["primaryAttackResult"] == "SUCCESS"
            and all("PASS" in a[1] for a in browser_report["action_results"])
            and browser_report["rmb_status"] == "PASS"
            and browser_report["dodge_status"] == "PASS"
            and browser_report["badge_exists"] is True
            and len(browser_report["page_errors"]) == 0
            and len(browser_report["console_errors"]) == 0
        )
        if all_passed:
            print("VERDICT: APPROVE (100% Empirical Tests Passed with 0 Errors)")
        else:
            print("VERDICT: REJECT (Defects Detected)")
    finally:
        server.shutdown()
        server.server_close()


if __name__ == "__main__":
    main()
