"""
Milestone M6 Phase 2 Challenger Empirical Adversarial Stress Suite (Part 1).
Stress Tests:
1. Procedural Generation with extreme seeds (0, -1, 2**31-1, 2**32-1, float, string).
2. Binary serialization with corrupted payloads (truncated buffers, invalid magic, size mismatches).
3. Runtime tile collision and 2-axis sliding physics with high-speed vectors and corner pinches.
"""

from __future__ import annotations
import math
import struct
import unittest
from typing import List, Tuple, Any

from server.world.map_data_types import (
    TileType,
    TileCell,
    MapGridData,
    EncounterZone,
)
from server.world.wilderness_map_generator import (
    WildernessMapGenerator,
    ZONE_DEFAULT_SIZES,
)
from server.world.map_binary_serializer import (
    serialize_map_grid,
    deserialize_map_grid,
    HEADER_MAGIC,
    HEADER_SIZE,
)
from tests.unit.test_tile_collision import (
    is_position_blocked,
    resolve_movement_with_sliding,
)


class TestChallengerM6ProcgenAndCompression(unittest.TestCase):
    """Stress tests procedural map generation and binary serialization/deserialization."""

    def test_extreme_seeds_generation(self) -> None:
        """Verify generation succeeds and satisfies invariants across extreme seeds."""
        extreme_seeds: List[Any] = [
            0,
            -1,
            2**31 - 1,
            2**32 - 1,
            -2**31,
            -999999999,
            3.1415926535,
            -0.0001,
            "seed_string_adversarial",
            "1234567890",
        ]
        gen = WildernessMapGenerator(width=60, height=45, biome_id="BLEACHED_BONE_CANYON")
        for seed in extreme_seeds:
            # Hash string or float seed if required by random.Random
            int_seed = hash(seed) if isinstance(seed, (str, float)) else seed
            grid = gen.generate(seed=int_seed, zone_id="zone_tang_kiem_nhai")
            self.assertEqual(grid.width, 60)
            self.assertEqual(grid.height, 45)
            self.assertIsNotNone(grid.boss_gate)
            self.assertGreaterEqual(len(grid.poi_points), 1)
            self.assertGreaterEqual(len(grid.encounter_zones), 2)

            # Spawn clearing verification (r=8.0)
            sx, sy = grid.spawn_point
            for dy in range(-8, 9):
                for dx in range(-8, 9):
                    if dx * dx + dy * dy <= 64:
                        tx, ty = sx + dx, sy + dy
                        if 0 <= tx < grid.width and 0 <= ty < grid.height:
                            cell = grid.tiles[ty][tx]
                            self.assertIn(
                                cell.tile_type,
                                (TileType.FLOOR, TileType.PATH),
                                f"Non-floor/path tile at ({tx},{ty}) for seed {seed}",
                            )
                            self.assertTrue(cell.walkable)
                            self.assertFalse(cell.blocks_vision)

    def test_corrupted_binary_truncations(self) -> None:
        """Verify deserializer raises ValueError on truncated byte buffers."""
        valid_grid = WildernessMapGenerator(width=60, height=45).generate(seed=42)
        valid_bytes = serialize_map_grid(valid_grid)

        # Buffer shorter than 16-byte fixed header
        for truncate_len in [0, 1, 5, 10, 15]:
            with self.assertRaises(ValueError):
                deserialize_map_grid(valid_bytes[:truncate_len])

        # Buffer truncated in POI table, Encounter table, or Tile payload
        truncation_points = [
            HEADER_SIZE,
            HEADER_SIZE + 2,
            HEADER_SIZE + 10,
            len(valid_bytes) - 1,
            len(valid_bytes) - 500,
        ]
        for t_len in truncation_points:
            if t_len < len(valid_bytes):
                with self.assertRaises(ValueError):
                    deserialize_map_grid(valid_bytes[:t_len])

    def test_corrupted_header_payloads(self) -> None:
        """Verify rejection of invalid magic bytes and mismatched dimensions."""
        valid_grid = WildernessMapGenerator(width=60, height=45).generate(seed=42)
        valid_bytes = bytearray(serialize_map_grid(valid_grid))

        # Invalid magic bytes
        for bad_magic in [b"XX", b"\x00\x00", b"fe", b"ZZ"]:
            corrupted = bytearray(valid_bytes)
            corrupted[0:2] = bad_magic
            with self.assertRaises(ValueError):
                deserialize_map_grid(bytes(corrupted))

        # Mismatched high dimensions with insufficient bytes
        corrupted_dim = bytearray(valid_bytes)
        struct.pack_into("<HH", corrupted_dim, 4, 1000, 1000)
        with self.assertRaises(ValueError):
            deserialize_map_grid(bytes(corrupted_dim))

    def test_unknown_tile_codes_graceful_fallback(self) -> None:
        """Verify unknown tile codes in valid-length payload fallback to WALL."""
        valid_grid = WildernessMapGenerator(width=60, height=45).generate(seed=42)
        valid_bytes = bytearray(serialize_map_grid(valid_grid))

        # Corrupt the first tile byte with an invalid tile code (e.g. 250)
        tile_offset = HEADER_SIZE + len(valid_grid.poi_points) * 3 + len(valid_grid.encounter_zones) * 5
        valid_bytes[tile_offset] = 250

        deserialized = deserialize_map_grid(bytes(valid_bytes))
        self.assertEqual(deserialized.tiles[0][0].tile_type, TileType.WALL)


class TestChallengerM6CollisionAndPhysics(unittest.TestCase):
    """Stress tests runtime tile collision and 2-axis sliding physics."""

    def setUp(self) -> None:
        self.w, self.h = 50, 50
        self.tiles = [
            [TileCell(x=x, y=y, tile_type=TileType.FLOOR, walkable=True) for x in range(self.w)]
            for y in range(self.h)
        ]
        # Build an impassable corner pinch barrier at x=20..25, y=20..25
        for x in range(20, 26):
            self.tiles[20][x].tile_type = TileType.WALL
            self.tiles[25][x].tile_type = TileType.WALL
        for y in range(20, 26):
            self.tiles[y][20].tile_type = TileType.WALL
            self.tiles[y][25].tile_type = TileType.WALL
        # Diagonal wall staircase
        for i in range(5, 15):
            self.tiles[i][i].tile_type = TileType.WALL

        self.grid = MapGridData(
            width=self.w,
            height=self.h,
            seed=1337,
            biome="BLEACHED_BONE_CANYON",
            tiles=self.tiles,
        )

    def test_high_speed_movement_vectors_no_tunneling(self) -> None:
        """High speed vectors (dx=100, dy=100) must slide or stop without penetrating."""
        start_x, start_y = 19.0, 19.0
        radius = 0.35
        high_speed_vectors: List[Tuple[float, float]] = [
            (100.0, 100.0),
            (-50.0, -50.0),
            (1000.0, 0.0),
            (0.0, 1000.0),
            (-100.0, 100.0),
        ]
        for vx, vy in high_speed_vectors:
            rx, ry, _ = resolve_movement_with_sliding(self.grid, start_x, start_y, vx, vy, radius)
            self.assertFalse(
                is_position_blocked(self.grid, rx, ry, radius),
                f"Position blocked after high-speed vector ({vx},{vy}): ({rx},{ry})",
            )
            # Must remain within world bounds
            self.assertGreaterEqual(rx, 0.0)
            self.assertLess(rx, float(self.w))
            self.assertGreaterEqual(ry, 0.0)
            self.assertLess(ry, float(self.h))

    def test_corner_pinch_sliding(self) -> None:
        """Sliding into a concave corner pinch must not penetrate or lock up."""
        radius = 0.35
        # Player right outside corner (20, 20)
        cur_x, cur_y = 19.5, 19.5
        # Push hard diagonally into the corner (vx=1.0, vy=1.0)
        rx, ry, is_sliding = resolve_movement_with_sliding(self.grid, cur_x, cur_y, 1.0, 1.0, radius)
        self.assertFalse(is_position_blocked(self.grid, rx, ry, radius))
        self.assertTrue(is_sliding or (rx == cur_x and ry == cur_y))

    def test_diagonal_wall_sliding(self) -> None:
        """Sliding along diagonal wall steps should allow axial progress."""
        radius = 0.35
        cur_x, cur_y = 6.0, 4.0
        # Move downward towards diagonal wall
        for _ in range(10):
            rx, ry, _ = resolve_movement_with_sliding(self.grid, cur_x, cur_y, 0.2, 0.5, radius)
            self.assertFalse(is_position_blocked(self.grid, rx, ry, radius))
            cur_x, cur_y = rx, ry
