"""
Adversarial and empirical verification suite for Milestone 1 (R1: Wilderness Map Expansion).

Directly challenges and stress-tests:
1. Extreme coordinates in zone_tang_kiem_nhai (x=999.0 valid, x=1001.0 rejected).
2. Boundary expansion transition (old edge x=500.0 vs new valid coordinate x=800.0).
3. Sanctuary zone bounds rejection (x=401.0 rejected in zone_player_hideout, half-width 400.0).
4. JS camera bounds in ZONE_BOUNDS (minCamWx < maxCamWx, minCamWy < maxCamWy for all zones).
5. All 9 wilderness zones upper/lower boundary stress.
6. Non-finite coordinate handling (inf, -inf, nan).
"""

from pathlib import Path
import re
import pytest

from server.world.zone_engine import ZoneEngine
from server.world.zone_types import ZoneType


@pytest.fixture
def zone_engine() -> ZoneEngine:
    return ZoneEngine()


class TestAdversarialZoneBounds:
    """Empirical challenges against zone bounds expansion and safety constraints."""

    def test_extreme_coordinates_tang_kiem_nhai(self, zone_engine: ZoneEngine) -> None:
        """
        Challenge 1: Extreme coordinates in zone_tang_kiem_nhai.
        Half-width is 1000.0 (bounds 2000.0 x 2000.0).
        x=999.0 must succeed; x=1001.0 must be rejected.
        """
        player_id = "adv_player_tang_kiem"
        zone_engine.spawn_player(player_id, "zone_tang_kiem_nhai")

        # 1. Inside boundary (x=999.0)
        ok_999, err_999 = zone_engine.update_player_position(player_id, 999.0, 0.0)
        assert ok_999 is True, f"Expected x=999.0 to succeed, failed with: {err_999}"
        assert err_999 == ""
        assert zone_engine.get_player_location(player_id).x == 999.0

        # Exact boundary (x=1000.0)
        ok_1000, err_1000 = zone_engine.update_player_position(player_id, 1000.0, 0.0)
        assert ok_1000 is True, f"Expected exact boundary x=1000.0 to succeed, failed: {err_1000}"

        # 2. Outside boundary (x=1001.0)
        ok_1001, err_1001 = zone_engine.update_player_position(player_id, 1001.0, 0.0)
        assert ok_1001 is False, "Expected x=1001.0 to be rejected, but succeeded!"
        assert "Vượt quá ranh giới bản đồ" in err_1001

        # 3. Negative extreme coordinates (-999.0 valid, -1001.0 rejected)
        ok_neg_999, _ = zone_engine.update_player_position(player_id, -999.0, 0.0)
        assert ok_neg_999 is True

        ok_neg_1001, err_neg_1001 = zone_engine.update_player_position(player_id, -1001.0, 0.0)
        assert ok_neg_1001 is False
        assert "Vượt quá ranh giới bản đồ" in err_neg_1001

        # 4. Y-axis extremes
        ok_y_999, _ = zone_engine.update_player_position(player_id, 0.0, 999.0)
        assert ok_y_999 is True

        ok_y_1001, err_y_1001 = zone_engine.update_player_position(player_id, 0.0, 1001.0)
        assert ok_y_1001 is False
        assert "Vượt quá ranh giới bản đồ" in err_y_1001

    def test_old_boundary_vs_new_expanded_boundary(self, zone_engine: ZoneEngine) -> None:
        """
        Challenge 2: Verify old boundary x=500.0 was previously the edge,
        but now x=800.0 is valid and freely navigable.
        """
        player_id = "adv_player_expansion"
        zone_engine.spawn_player(player_id, "zone_tang_kiem_nhai")

        # x=500.0 is well within current bounds
        ok_500, err_500 = zone_engine.update_player_position(player_id, 500.0, 500.0)
        assert ok_500 is True
        assert err_500 == ""

        # x=800.0 was previously outside the old 500.0 half-bound, now must succeed
        ok_800, err_800 = zone_engine.update_player_position(player_id, 800.0, 800.0)
        assert ok_800 is True, f"x=800.0 failed: {err_800}"
        assert err_800 == ""

        # Negative quadrant (-800.0, -800.0)
        ok_neg_800, err_neg_800 = zone_engine.update_player_position(player_id, -800.0, -800.0)
        assert ok_neg_800 is True
        assert err_neg_800 == ""

    def test_sanctuary_zone_bounds_rejection(self, zone_engine: ZoneEngine) -> None:
        """
        Challenge 3: Test sanctuary zone bounds rejection: player moving to
        x=401.0 in zone_player_hideout must be rejected (half-width 400.0).
        """
        player_id = "adv_player_hideout"
        zone_engine.spawn_player(player_id, "zone_player_hideout")

        # Exact half-width (x=400.0)
        ok_400, err_400 = zone_engine.update_player_position(player_id, 400.0, 0.0)
        assert ok_400 is True, f"x=400.0 failed: {err_400}"

        # Outside half-width (x=401.0) must be rejected
        ok_401, err_401 = zone_engine.update_player_position(player_id, 401.0, 0.0)
        assert ok_401 is False, "Expected x=401.0 in hideout to be rejected, but it passed!"
        assert "Vượt quá ranh giới bản đồ" in err_401

        # Y outside half-width (y=401.0) must be rejected
        ok_y_401, err_y_401 = zone_engine.update_player_position(player_id, 0.0, 401.0)
        assert ok_y_401 is False
        assert "Vượt quá ranh giới bản đồ" in err_y_401

        # Also verify boundless sanctuary rejection at 401.0
        p2 = "adv_player_sanctuary"
        zone_engine.spawn_player(p2, "zone_boundless_sanctuary")
        ok_sanc_401, err_sanc_401 = zone_engine.update_player_position(p2, 401.0, 0.0)
        assert ok_sanc_401 is False
        assert "Vượt quá ranh giới bản đồ" in err_sanc_401

    def test_camera_bounds_in_js_zone_bounds(self) -> None:
        """
        Challenge 4: Test camera bounds in JS: ensure minCamWx < maxCamWx
        and minCamWy < maxCamWy for all zones in ZONE_BOUNDS.
        Also check minWx <= minCamWx <= maxCamWx <= maxWx.
        """
        collision_file = Path("client/webapp/js/engine/collision_engine.js")
        assert collision_file.exists()
        content = collision_file.read_text(encoding="utf-8")

        # Extract zone blocks in ZONE_BOUNDS
        zone_pattern = re.compile(
            r"(\w+):\s*\{[^}]*minWx:\s*([-\d.]+),\s*maxWx:\s*([-\d.]+),\s*"
            r"minWy:\s*([-\d.]+),\s*maxWy:\s*([-\d.]+),\s*"
            r"minCamWx:\s*([-\d.]+),\s*maxCamWx:\s*([-\d.]+),\s*"
            r"minCamWy:\s*([-\d.]+),\s*maxCamWy:\s*([-\d.]+)"
        )
        matches = zone_pattern.findall(content)
        assert len(matches) >= 11, f"Expected at least 11 zone entries, found {len(matches)}"

        for match in matches:
            zone_id = match[0]
            min_wx = float(match[1])
            max_wx = float(match[2])
            min_wy = float(match[3])
            max_wy = float(match[4])
            min_cam_wx = float(match[5])
            max_cam_wx = float(match[6])
            min_cam_wy = float(match[7])
            max_cam_wy = float(match[8])

            # 1. Camera bounds strictly non-degenerate
            assert min_cam_wx < max_cam_wx, (
                f"Zone {zone_id}: minCamWx ({min_cam_wx}) >= maxCamWx ({max_cam_wx})"
            )
            assert min_cam_wy < max_cam_wy, (
                f"Zone {zone_id}: minCamWy ({min_cam_wy}) >= maxCamWy ({max_cam_wy})"
            )

            # 2. World bounds strictly non-degenerate
            assert min_wx < max_wx, f"Zone {zone_id}: minWx >= maxWx"
            assert min_wy < max_wy, f"Zone {zone_id}: minWy >= maxWy"

            # 3. Camera frustum safely within world bounds
            assert min_wx <= min_cam_wx, (
                f"Zone {zone_id}: minWx ({min_wx}) > minCamWx ({min_cam_wx})"
            )
            assert max_cam_wx <= max_wx, (
                f"Zone {zone_id}: maxCamWx ({max_cam_wx}) > maxWx ({max_wx})"
            )
            assert min_wy <= min_cam_wy, (
                f"Zone {zone_id}: minWy ({min_wy}) > minCamWy ({min_cam_wy})"
            )
            assert max_cam_wy <= max_wy, (
                f"Zone {zone_id}: maxCamWy ({max_cam_wy}) > maxWy ({max_wy})"
            )

    @pytest.mark.parametrize(
        "zone_id",
        [
            "zone_tang_kiem_nhai",
            "zone_ancient_sword_barrow",
            "zone_boundless_sandstorm",
            "zone_blood_scale_ruins",
            "zone_five_elements_altar",
            "zone_abyssal_ice_pond",
            "zone_infinite_blood_rift",
            "zone_purgatory_lava_cavern",
            "zone_boundless_celestial_palace",
        ],
    )
    def test_all_wilderness_zones_boundary_limits(
        self, zone_engine: ZoneEngine, zone_id: str
    ) -> None:
        """Adversarial boundary check across all 9 wilderness zones."""
        zone = zone_engine.get_zone(zone_id)
        assert zone is not None
        assert zone.zone_type == ZoneType.OPEN_WORLD

        half_w = zone.bounds_width / 2.0
        half_h = zone.bounds_height / 2.0

        pid = f"adv_stress_{zone_id}"
        zone_engine.spawn_player(pid, zone_id)

        # Inside by 1.0 unit -> OK
        ok_in, err_in = zone_engine.update_player_position(pid, half_w - 1.0, half_h - 1.0)
        assert ok_in is True, f"Failed at ({half_w - 1.0}, {half_h - 1.0}) in {zone_id}: {err_in}"

        # Outside by 1.0 unit in X -> Rejected
        ok_out_x, err_out_x = zone_engine.update_player_position(pid, half_w + 1.0, 0.0)
        assert ok_out_x is False, f"Unexpectedly allowed x={half_w + 1.0} in {zone_id}"
        assert "Vượt quá ranh giới bản đồ" in err_out_x

        # Outside by 1.0 unit in Y -> Rejected
        ok_out_y, err_out_y = zone_engine.update_player_position(pid, 0.0, half_h + 1.0)
        assert ok_out_y is False, f"Unexpectedly allowed y={half_h + 1.0} in {zone_id}"
        assert "Vượt quá ranh giới bản đồ" in err_out_y

    def test_infinite_coordinates_rejected(self, zone_engine: ZoneEngine) -> None:
        """Adversarial stress: infinite coordinates must be rejected."""
        pid = "adv_inf_player"
        zone_engine.spawn_player(pid, "zone_tang_kiem_nhai")

        ok_inf_pos, _ = zone_engine.update_player_position(pid, float("inf"), 0.0)
        assert ok_inf_pos is False

        ok_inf_neg, _ = zone_engine.update_player_position(pid, float("-inf"), 0.0)
        assert ok_inf_neg is False
