"""
Milestone M6 Phase 2 Challenger Empirical Adversarial Stress Suite (Part 2).
Stress Tests:
1. Fog of War and Minimap HUD:
   - Corrupted/truncated Base64 strings, dimension mismatches, malformed encodings.
   - Extreme zoom levels, dynamic window resizing, rapid pause/unpause toggling.
2. AI Pathfinding & Combat Encounter:
   - Massive concurrent monster chase steering across complex obstacles.
   - Multi-pack simultaneous wipes with atomic Boss Gate unlocking.
   - Waypoint safe zone boundary thrashing under pursuit.
"""

from __future__ import annotations
import json
import pathlib
import subprocess
import unittest

PROJECT_ROOT = pathlib.Path(__file__).resolve().parent.parent.parent


def _run_node_json(script: str) -> dict:
    """Executes a Node.js ES-module snippet and parses the final JSON line."""
    proc = subprocess.run(
        ["node", "--input-type=module", "-e", script],
        capture_output=True,
        text=True,
        cwd=str(PROJECT_ROOT),
        check=True,
    )
    lines = [ln.strip() for ln in proc.stdout.splitlines() if ln.strip()]
    return json.loads(lines[-1])


class TestChallengerM6FogAndMinimapStress(unittest.TestCase):
    """Adversarial stress testing of Fog of War persistence and Minimap HUD."""

    def test_corrupted_and_truncated_base64_fog(self) -> None:
        """Verify robust error recovery against corrupted Base64 strings."""
        script = """
        import { unpackFogBits, initFog, FOG_STATE } from './client/webapp/js/ui/war_fog.js';
        const w = 60, h = 45;
        const grid = initFog(w, h, 'zone_test', 42);

        // 1. Truncated Base64 (empty, 1 char, partial header)
        const t1 = unpackFogBits('', grid, w, h);
        const t2 = unpackFogBits('A', grid, w, h);
        const t3 = unpackFogBits('AAAA', grid, w, h); // w=0, h=0 -> mismatch

        // 2. Corrupted Base64 characters
        const c1 = unpackFogBits('!!!@@@###$$$', grid, w, h);
        const c2 = unpackFogBits('====', grid, w, h);

        // 3. Dimension mismatch (encoded for 10x10, unpack for 60x45)
        const header10x10 = Buffer.from([10, 0, 10, 0, 255]).toString('base64');
        const m1 = unpackFogBits(header10x10, grid, w, h);

        console.log(JSON.stringify({ t1, t2, t3, c1, c2, m1 }));
        """
        res = _run_node_json(script)
        self.assertFalse(res["t1"])
        self.assertFalse(res["t2"])
        self.assertFalse(res["t3"])
        self.assertFalse(res["c1"])
        self.assertFalse(res["c2"])
        self.assertFalse(res["m1"])

    def test_extreme_zoom_and_window_resizing(self) -> None:
        """Verify Minimap HUD survives extreme scale, zoom, and 0-dimension states."""
        script = """
        import { MinimapHUD } from './client/webapp/js/ui/minimap_hud.js';
        const hud = new MinimapHUD();

        // 0 dimensions
        hud.setMap(new Uint8Array(0), 0, 0, 1);
        const p0 = hud.worldToMinimap(10, 10);

        // Huge dimensions
        hud.setMap(new Uint8Array(1000 * 1000), 1000, 1000, 1);
        const pHuge = hud.worldToMinimap(500, 500);

        // Out of bounds / extreme coordinates
        const pNeg = hud.worldToMinimap(-9999, -9999);
        const pInf = hud.worldToMinimap(1e6, 1e6);

        console.log(JSON.stringify({
          p0Valid: Number.isFinite(p0.x) && Number.isFinite(p0.y),
          pHugeValid: Number.isFinite(pHuge.x) && Number.isFinite(pHuge.y),
          pNegValid: Number.isFinite(pNeg.x) && Number.isFinite(pNeg.y),
          pInfValid: Number.isFinite(pInf.x) && Number.isFinite(pInf.y)
        }));
        """
        res = _run_node_json(script)
        self.assertTrue(res["p0Valid"])
        self.assertTrue(res["pHugeValid"])
        self.assertTrue(res["pNegValid"])
        self.assertTrue(res["pInfValid"])

    def test_rapid_pause_unpause_toggles(self) -> None:
        """Verify 1,000 rapid pause toggles do not desynchronize engine state."""
        script = """
        global.isGamePaused = false;
        let toggleCount = 0;
        for (let i = 0; i < 1000; i++) {
          global.isGamePaused = !global.isGamePaused;
          toggleCount++;
        }
        console.log(JSON.stringify({ toggleCount, isPaused: global.isGamePaused }));
        """
        res = _run_node_json(script)
        self.assertEqual(res["toggleCount"], 1000)
        self.assertFalse(res["isPaused"])


class TestChallengerM6CombatAndAIStress(unittest.TestCase):
    """Adversarial stress testing of concurrent AI pathfinding, wipes, and leashing."""

    def test_massive_concurrent_monster_chase_steering(self) -> None:
        """100 concurrent monsters steering around U-shaped obstacle must not penetrate or NaN."""
        script = """
        import { steerMonsterChase, findPath } from './client/webapp/js/engine/grid_pathfinder.js';
        global.currentMapWidth = 50;
        global.currentMapHeight = 50;
        const grid = new Uint8Array(50 * 50);
        grid.fill(1); // FLOOR

        // U-shaped wall: x=20..30, y=20 (bottom), x=20, y=10..20 (left), x=30, y=10..20 (right)
        for (let x = 20; x <= 30; x++) grid[20 * 50 + x] = 2;
        for (let y = 10; y <= 20; y++) {
          grid[y * 50 + 20] = 2;
          grid[y * 50 + 30] = 2;
        }
        global.getTileAt = (tx, ty) => (tx < 0 || tx >= 50 || ty < 0 || ty >= 50) ? 2 : grid[ty * 50 + tx];

        // 100 monsters inside U-pocket trying to chase player outside at (25, 5)
        const playerX = 25.5, playerY = 5.5;
        let successSteers = 0;
        let naNEncountered = false;

        for (let i = 0; i < 100; i++) {
          const mob = { wx: 21.0 + (i % 8), wy: 15.0 + Math.floor(i / 20) };
          const steer = steerMonsterChase(mob, playerX, playerY, 0.016, 1.0);
          if (Number.isNaN(steer.vx) || Number.isNaN(steer.vy)) {
            naNEncountered = true;
          }
          if (steer.hasPath || (steer.vx !== 0 || steer.vy !== 0)) {
            successSteers++;
          }
        }

        console.log(JSON.stringify({ total: 100, successSteers, naNEncountered }));
        """
        res = _run_node_json(script)
        self.assertFalse(res["naNEncountered"])
        self.assertGreater(res["successSteers"], 80)

    def test_multi_pack_simultaneous_wipes(self) -> None:
        """Simultaneous kill of 5 packs must atomically increment kills and unlock Boss Gate."""
        script = """
        import { BossGateController } from './client/webapp/js/engine/boss_gate_controller.js';
        import { registerMonsterKill } from './client/webapp/js/engine/monster_pack_system.js';
        const z = 'zone_tang_kiem_nhai';
        global.currentMapGrid = new Uint8Array(60 * 45);
        global.currentMapWidth = 60;
        global.currentMapGrid[15 * 60 + 20] = 10; // BOSS_GATE

        global.zoneEncounterProgress = {
          [z]: { zoneId: z, totalPacks: 5, alivePacks: 5, clearedPacks: 0, totalKills: 0, requiredPacks: 5 }
        };
        const bgc = new BossGateController({ gateX: 20, gateY: 15, zoneId: z, requiredKills: 5 });
        global.bossGateController = bgc;

        const mobs = [];
        for (let p = 0; p < 5; p++) {
          mobs.push({ packId: 'pack_' + p, hp: 0 });
        }

        // Wipe all 5 packs simultaneously
        for (let m of mobs) {
          registerMonsterKill(m, mobs, z);
        }

        console.log(JSON.stringify({
          clearedPacks: global.zoneEncounterProgress[z].clearedPacks,
          alivePacks: global.zoneEncounterProgress[z].alivePacks,
          gateState: bgc.state,
          tileAfter: global.currentMapGrid[15 * 60 + 20]
        }));
        """
        res = _run_node_json(script)
        self.assertEqual(res["clearedPacks"], 5)
        self.assertEqual(res["alivePacks"], 0)
        self.assertEqual(res["gateState"], "UNLOCKED")
        self.assertEqual(res["tileAfter"], 1)  # FLOOR

    def test_waypoint_safe_zone_boundary_thrashing(self) -> None:
        """Rapidly crossing r=8.0 safe boundary 1,000 times under chase preserves sanity."""
        script = """
        import { isInWaypointSafeRadius } from './client/webapp/js/data/wilderness_zone_packs.js';
        const z = 'zone_tang_kiem_nhai';
        let safeCount = 0;
        let unsafeCount = 0;

        for (let i = 0; i < 1000; i++) {
          // Thrash between 7.95 (inside safe) and 8.05 (outside safe)
          const dist = (i % 2 === 0) ? 7.95 : 8.05;
          const isSafe = isInWaypointSafeRadius(dist, 0.0, z);
          if (isSafe) safeCount++;
          else unsafeCount++;
        }

        console.log(JSON.stringify({ safeCount, unsafeCount }));
        """
        res = _run_node_json(script)
        self.assertEqual(res["safeCount"], 500)
        self.assertEqual(res["unsafeCount"], 500)
