"""
Adversarial Challenge Harness for Spacebar Dodge Controls & Charge Accounting.
Agent: challenger_18_3
"""
from __future__ import annotations
import math
import socketserver
import sys
import threading
import time
from pathlib import Path
from typing import Any

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_adversarial_challenge():
    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}"

    results = {
        "passed": 0,
        "failed": 0,
        "checks": {},
        "page_errors": [],
        "console_errors": []
    }

    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})
            page.on("pageerror", lambda err: results["page_errors"].append(f"PageError: {err}"))
            page.on("console", lambda m: results["console_errors"].append(f"ConsoleError: {m.text}") if m.type == "error" and "favicon" not in m.text 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_function("() => window.player && window.skillBarController && window.PcInputController")

            # -------------------------------------------------------------
            # CHALLENGE 1: Initial State & Charge Accounting
            # -------------------------------------------------------------
            init_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            init_max = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.maxCharges")
            badge_text = page.evaluate("() => document.querySelector('#skill-dodge .skill-charges-badge')?.textContent?.trim()")
            init_iframe = page.evaluate("() => window.player?.isIFrame")
            badge_iframe_hidden = page.evaluate("() => document.getElementById('badge-iframe')?.classList.contains('hidden')")

            c1_pass = (init_charges == 3 and init_max == 3 and badge_text == "3" and init_iframe is False and badge_iframe_hidden is True)
            results["checks"]["1_initial_state"] = {
                "pass": c1_pass,
                "init_charges": init_charges,
                "badge_text": badge_text,
                "init_iframe": init_iframe,
                "badge_iframe_hidden": badge_iframe_hidden
            }
            if c1_pass: results["passed"] += 1
            else: results["failed"] += 1

            # -------------------------------------------------------------
            # CHALLENGE 2: Single Spacebar Tap -> Charges 3 -> 2, i-Frame true
            # -------------------------------------------------------------
            page.keyboard.press("Space")
            page.wait_for_timeout(40)

            c2_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c2_iframe = page.evaluate("() => window.player?.isIFrame")
            c2_timer = page.evaluate("() => window.player?.iFrameTimer")
            c2_anim = page.evaluate("() => window.player?.animState")
            c2_badge_visible = page.evaluate("() => !document.getElementById('badge-iframe')?.classList.contains('hidden')")

            c2_pass = (c2_charges == 2 and c2_iframe is True and c2_timer > 0 and c2_anim == "dodge" and c2_badge_visible)
            results["checks"]["2_single_tap_charge_and_iframe"] = {
                "pass": c2_pass,
                "charges_after_tap": c2_charges,
                "is_iframe": c2_iframe,
                "iframe_timer": c2_timer,
                "anim_state": c2_anim,
                "badge_iframe_visible": c2_badge_visible
            }
            if c2_pass: results["passed"] += 1
            else: results["failed"] += 1

            # -------------------------------------------------------------
            # CHALLENGE 3: Spam Spacebar During Active i-Frame (0 Wasted Charges)
            # -------------------------------------------------------------
            spam_count = 8
            for _ in range(spam_count):
                page.keyboard.press("Space")
                page.wait_for_timeout(15)

            c3_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c3_pass = (c3_charges == 2)
            results["checks"]["3_spam_during_iframe_no_waste"] = {
                "pass": c3_pass,
                "charges_after_spam": c3_charges,
                "spam_press_count": spam_count
            }
            if c3_pass: results["passed"] += 1
            else: results["failed"] += 1

            # -------------------------------------------------------------
            # CHALLENGE 4: i-Frame Window Expiration & ClassList Exception Check
            # -------------------------------------------------------------
            # Wait for 0.25s i-frame window to finish
            page.wait_for_function("() => !window.player?.isIFrame", timeout=2000)
            page.wait_for_timeout(50)

            c4_iframe = page.evaluate("() => window.player?.isIFrame")
            c4_badge_hidden = page.evaluate("() => document.getElementById('badge-iframe')?.classList.contains('hidden')")
            c4_errors_count = len(results["page_errors"])

            c4_pass = (c4_iframe is False and c4_badge_hidden is True and c4_errors_count == 0)
            results["checks"]["4_iframe_expiration_zero_errors"] = {
                "pass": c4_pass,
                "is_iframe": c4_iframe,
                "badge_hidden": c4_badge_hidden,
                "page_errors": list(results["page_errors"])
            }
            if c4_pass: results["passed"] += 1
            else: results["failed"] += 1

            # -------------------------------------------------------------
            # CHALLENGE 5: Second Valid Tap -> Charges 2 -> 1
            # -------------------------------------------------------------
            page.keyboard.press("Space")
            page.wait_for_timeout(40)

            c5_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c5_iframe = page.evaluate("() => window.player?.isIFrame")

            c5_pass = (c5_charges == 1 and c5_iframe is True)
            results["checks"]["5_second_tap_charge_drop"] = {
                "pass": c5_pass,
                "charges_after_2nd_tap": c5_charges,
                "is_iframe": c5_iframe
            }
            if c5_pass: results["passed"] += 1
            else: results["failed"] += 1

            # Wait for 2nd iframe to elapse
            page.wait_for_function("() => !window.player?.isIFrame", timeout=2000)
            page.wait_for_timeout(50)

            # -------------------------------------------------------------
            # CHALLENGE 6: Third Valid Tap -> Charges 1 -> 0, Cooldown State
            # -------------------------------------------------------------
            page.keyboard.press("Space")
            page.wait_for_timeout(40)

            c6_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c6_is_cd = page.evaluate("() => window.skillBarController?.isSlotOnCooldown('dodge')")
            c6_dom_cd = page.evaluate("() => document.getElementById('skill-dodge')?.classList.contains('is-on-cooldown')")
            c6_badge_depleted = page.evaluate("() => document.querySelector('#skill-dodge .skill-charges-badge')?.classList.contains('is-depleted')")
            c6_badge_val = page.evaluate("() => document.querySelector('#skill-dodge .skill-charges-badge')?.textContent?.trim()")

            c6_pass = (c6_charges == 0 and c6_is_cd is True and c6_dom_cd is True and c6_badge_depleted is True and c6_badge_val == "0")
            results["checks"]["6_third_tap_depletion_and_cooldown"] = {
                "pass": c6_pass,
                "charges_after_3rd_tap": c6_charges,
                "is_on_cooldown": c6_is_cd,
                "dom_is_on_cooldown": c6_dom_cd,
                "badge_is_depleted": c6_badge_depleted,
                "badge_val": c6_badge_val
            }
            if c6_pass: results["passed"] += 1
            else: results["failed"] += 1

            # Wait for 3rd iframe to elapse
            page.wait_for_function("() => !window.player?.isIFrame", timeout=2000)
            page.wait_for_timeout(50)

            # -------------------------------------------------------------
            # CHALLENGE 7: Pressing Spacebar while charges == 0 -> Remains 0, Feedback Animation
            # -------------------------------------------------------------
            page.keyboard.press("Space")
            page.wait_for_timeout(50)

            c7_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c7_iframe = page.evaluate("() => window.player?.isIFrame")
            c7_overlay_opacity = page.evaluate("() => window.getComputedStyle(document.querySelector('#skill-dodge .cooldown-overlay')).opacity")
            c7_cd_sweep = page.evaluate("() => document.querySelector('#skill-dodge .cooldown-overlay')?.style.getPropertyValue('--cooldown-sweep')")
            c7_errors_count = len(results["page_errors"])

            c7_pass = (c7_charges == 0 and c7_iframe is False and float(c7_overlay_opacity or 0) > 0 and c7_errors_count == 0)
            results["checks"]["7_press_at_zero_charges"] = {
                "pass": c7_pass,
                "charges_remain_zero": c7_charges == 0,
                "charges": c7_charges,
                "is_iframe": c7_iframe,
                "overlay_opacity": c7_overlay_opacity,
                "cooldown_sweep": c7_cd_sweep,
                "page_errors_count": c7_errors_count
            }
            if c7_pass: results["passed"] += 1
            else: results["failed"] += 1

            # -------------------------------------------------------------
            # CHALLENGE 8: Attack Cancellation via Dodge Roll
            # -------------------------------------------------------------
            # Reset dodge charges to 3 to test attack cancellation cleanly
            page.evaluate("""() => {
                const slot = window.skillBarController.slots.get('dodge');
                if (slot) {
                    slot.charges = 3;
                    slot.cooldownRemaining = 0;
                    slot.rechargeRemaining = 0;
                    window.dodgeCharges = 3;
                    window.skillBarController._updateChargeBadge(slot);
                    window.skillBarController._markSlotCooldownState(slot, false);
                }
                window.player.isIFrame = false;
                window.player.iFrameTimer = 0;
            }""")

            # Atomically trigger primary attack and immediately interrupt with dodge
            atk_cancel_res = page.evaluate("""() => {
                window.doPrimaryAttack();
                const stateDuringAttack = window.player.animState;
                const clipDuringAttack = window.player.anim ? window.player.anim.currentClip : null;
                const actionLockedBefore = window.player.anim ? window.player.anim.isActionLocked : null;

                // Press Spacebar to cancel attack
                window.PcInputController.onKeyDown({ code: 'Space', key: ' ', preventDefault: () => {}, stopImmediatePropagation: () => {} });

                const stateAfterDodge = window.player.animState;
                const clipAfterDodge = window.player.anim ? window.player.anim.currentClip : null;
                const isIFrame = window.player.isIFrame;
                const attackTimer = window.player.attackTimer;
                const charges = window.skillBarController.slots.get('dodge').charges;

                return {
                    stateDuringAttack,
                    clipDuringAttack,
                    actionLockedBefore,
                    stateAfterDodge,
                    clipAfterDodge,
                    isIFrame,
                    attackTimer,
                    charges
                };
            }""")

            c8_pass = (
                atk_cancel_res["stateDuringAttack"] == "attack" and
                atk_cancel_res["stateAfterDodge"] == "dodge" and
                atk_cancel_res["isIFrame"] is True and
                atk_cancel_res["attackTimer"] == 0 and
                atk_cancel_res["charges"] == 2
            )
            results["checks"]["8_attack_cancel_into_dodge"] = {
                "pass": c8_pass,
                **atk_cancel_res
            }
            if c8_pass: results["passed"] += 1
            else: results["failed"] += 1

            # Wait for attack-cancelled dodge iframe to expire
            page.wait_for_function("() => !window.player?.isIFrame", timeout=2000)
            page.wait_for_timeout(50)

            # -------------------------------------------------------------
            # CHALLENGE 9: Kinematic Velocity & Boost during Dodge Roll
            # -------------------------------------------------------------
            # Verify 1.75x velocity boost formula in kinematic engine
            boost_res = page.evaluate("""() => {
                window.player.isIFrame = false;
                window.player.iFrameTimer = 0;
                window.player.speed = 6.0;
                const dt = 0.1;

                const normalBoost = window.player.isIFrame ? 1.75 : 1.0;
                const normalDist = window.player.speed * normalBoost * dt;

                window.doDodge();
                const dodgeBoost = window.player.isIFrame ? 1.75 : 1.0;
                const dodgeDist = window.player.speed * dodgeBoost * dt;

                return {
                    normalBoost,
                    normalDist,
                    dodgeBoost,
                    dodgeDist,
                    boostRatio: dodgeDist / normalDist,
                    isIFrame: window.player.isIFrame
                };
            }""")

            c9_pass = (math.isclose(boost_res["boostRatio"], 1.75, rel_tol=1e-5) and boost_res["dodgeBoost"] == 1.75 and boost_res["normalBoost"] == 1.0)
            results["checks"]["9_kinematic_velocity_boost"] = {
                "pass": c9_pass,
                **boost_res
            }
            if c9_pass: results["passed"] += 1
            else: results["failed"] += 1

            # Wait for dodge to finish
            page.wait_for_function("() => !window.player?.isIFrame", timeout=2000)

            # -------------------------------------------------------------
            # CHALLENGE 10: Automatic Charge Recharge over Time (3.0s per charge)
            # -------------------------------------------------------------
            # Ensure charges < maxCharges to observe recharge
            cur_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            page.wait_for_timeout(3500)
            recharged_charges = page.evaluate("() => window.skillBarController?.slots?.get('dodge')?.charges")
            c10_pass = (recharged_charges > cur_charges)
            results["checks"]["10_charge_recharge_loop"] = {
                "pass": c10_pass,
                "charges_before_wait": cur_charges,
                "charges_after_3.5s": recharged_charges
            }
            if c10_pass: results["passed"] += 1
            else: results["failed"] += 1

            # Final check of overall errors
            results["total_page_errors"] = len(results["page_errors"])
            results["total_console_errors"] = len(results["console_errors"])

            page.close()
            browser.close()
    finally:
        server.shutdown()
        server.server_close()

    print("\n" + "="*70)
    print(f"ADVERSARIAL CHALLENGE EXECUTION SUMMARY: {results['passed']} PASSED, {results['failed']} FAILED")
    print("="*70)
    for name, res in results["checks"].items():
        status = "PASS" if res.get("pass") else "FAIL"
        print(f"[{status}] {name}: {res}")
    print(f"\nCaptured Page Errors ({results['total_page_errors']}): {results['page_errors']}")
    print(f"Captured Console Errors ({results['total_console_errors']}): {results['console_errors']}")
    print("="*70)

    return results


if __name__ == "__main__":
    res = run_adversarial_challenge()
    if res["failed"] > 0 or res["total_page_errors"] > 0:
        sys.exit(1)
    sys.exit(0)
