"""
Unit tests for NativeEngineBridge and C++ SIMD Simulation Core FFI.
Verifies dynamic library resolution, lifecycle management, fallback resilience,
SoA displacement physics, spatial hash grid AOI queries, and edge cases.
"""

import os
import sys
import unittest
from typing import Optional

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

from world.native_engine_bridge import NativeEngineBridge, resolve_sim_core_dll_path


class TestNativeEngineBridge(unittest.TestCase):
    def setUp(self) -> None:
        self.bridge = NativeEngineBridge()
        if self.bridge.is_loaded:
            self.bridge.init(cell_size=64.0)

    def test_dll_path_resolution(self) -> None:
        """Verifies resolution priority: explicit path > env var > search dirs."""
        # 1. Explicit path
        explicit = resolve_sim_core_dll_path("custom/test_core.dll")
        self.assertEqual(explicit, "custom/test_core.dll")

        # 2. Environment variable override
        old_env = os.environ.get("FREEEXILE_SIM_CORE_DLL")
        try:
            os.environ["FREEEXILE_SIM_CORE_DLL"] = "custom/env_core.dll"
            env_resolved = resolve_sim_core_dll_path()
            self.assertEqual(env_resolved, "custom/env_core.dll")
        finally:
            if old_env is None:
                os.environ.pop("FREEEXILE_SIM_CORE_DLL", None)
            else:
                os.environ["FREEEXILE_SIM_CORE_DLL"] = old_env

        # 3. Canonical resolution finds an existing library in the workspace
        resolved = resolve_sim_core_dll_path()
        self.assertTrue(os.path.isabs(resolved))
        self.assertTrue(os.path.exists(resolved), f"Resolved DLL does not exist: {resolved}")

    def test_fallback_mode_resilience(self) -> None:
        """Verifies graceful fallback behavior when native DLL path does not exist."""
        dummy_bridge = NativeEngineBridge(dll_path="non_existent_engine_core_dummy.dll")
        self.assertFalse(dummy_bridge.is_loaded)

        # All API calls must return safe fallback defaults without raising exceptions
        dummy_bridge.init(32.0)
        idx = dummy_bridge.add_entity(999, 10.0, 20.0)
        self.assertEqual(idx, -1)
        self.assertEqual(dummy_bridge.get_entity_count(), 0)
        self.assertEqual(dummy_bridge.get_entity_pos(0), (0.0, 0.0, 0.0))
        self.assertEqual(dummy_bridge.query_aoi(10.0, 20.0), [])
        dummy_bridge.set_velocity(0, 1.0, 1.0)
        dummy_bridge.set_evasion(0, True)
        dummy_bridge.step_tick(0.033)

    def test_entity_lifecycle_and_position(self) -> None:
        """Verifies entity spawning, count tracking, and position retrieval."""
        if not self.bridge.is_loaded:
            self.skipTest("Native DLL not loaded")

        self.assertEqual(self.bridge.get_entity_count(), 0)

        idx0 = self.bridge.add_entity(
            entity_id=1001,
            x=150.0,
            y=250.0,
            z=10.0,
            move_speed=8.0,
            collision_radius=0.75,
            hp=1500.0,
        )
        self.assertEqual(idx0, 0)
        self.assertEqual(self.bridge.get_entity_count(), 1)

        pos = self.bridge.get_entity_pos(idx0)
        self.assertAlmostEqual(pos[0], 150.0, places=3)
        self.assertAlmostEqual(pos[1], 250.0, places=3)
        self.assertAlmostEqual(pos[2], 10.0, places=3)

    def test_displacement_step_tick(self) -> None:
        """Verifies SIMD displacement update under velocity vector."""
        if not self.bridge.is_loaded:
            self.skipTest("Native DLL not loaded")

        idx = self.bridge.add_entity(
            entity_id=2001,
            x=100.0,
            y=100.0,
            z=0.0,
            move_speed=10.0,
            collision_radius=0.5,
            hp=1000.0,
        )

        # Move eastward (+X) at move_speed = 10.0
        self.bridge.set_velocity(idx, 1.0, 0.0)

        # Step 0.5s -> should move 5.0 units in X
        self.bridge.step_tick(0.5)

        pos = self.bridge.get_entity_pos(idx)
        self.assertAlmostEqual(pos[0], 105.0, places=3)
        self.assertAlmostEqual(pos[1], 100.0, places=3)

    def test_aoi_spatial_hash_query(self) -> None:
        """Verifies FlatSpatialGrid AOI 9-cell neighborhood proximity queries and deduplication."""
        if not self.bridge.is_loaded:
            self.skipTest("Native DLL not loaded")

        # 1. Immediate AOI query right after spawn (before first tick)
        self.bridge.add_entity(entity_id=999, x=50.0, y=50.0)
        immediate = self.bridge.query_aoi(50.0, 50.0, radius_cells=1)
        self.assertIn(999, immediate)

        # Reset for neighborhood proximity tests
        self.bridge.init(cell_size=64.0)

        # Spawn Player 1 at (100, 100) -> cell (1, 1)
        self.bridge.add_entity(entity_id=1, x=100.0, y=100.0)
        # Spawn Nearby Enemy 2 at (120, 120) -> cell (1, 1)
        self.bridge.add_entity(entity_id=2, x=120.0, y=120.0)
        # Spawn Far Enemy 3 at (900, 900) -> cell (14, 14)
        self.bridge.add_entity(entity_id=3, x=900.0, y=900.0)

        # Step tick to rebuild spatial hash buckets
        self.bridge.step_tick(0.001)

        # Query around (100, 100) with radius 1 cell
        aoi_near = self.bridge.query_aoi(100.0, 100.0, radius_cells=1)
        self.assertIn(1, aoi_near)
        self.assertIn(2, aoi_near)
        self.assertNotIn(3, aoi_near)

        # Query around (900, 900)
        aoi_far = self.bridge.query_aoi(900.0, 900.0, radius_cells=1)
        self.assertIn(3, aoi_far)
        self.assertNotIn(1, aoi_far)
        self.assertNotIn(2, aoi_far)

        # 2. Collision cells deduplication test: entity at (64, 64) queried from (0, 0)
        # Must return exactly one instance of entity_id (not duplicated due to hash collision)
        self.bridge.init(cell_size=64.0)
        self.bridge.add_entity(entity_id=101, x=64.0, y=64.0)
        self.bridge.step_tick(0.001)
        aoi_origin = self.bridge.query_aoi(0.0, 0.0, radius_cells=1)
        self.assertEqual(aoi_origin.count(101), 1)

        # 3. Large radius DOS resistance: extremely large radius is clamped safely and runs instantaneously
        clamped_aoi = self.bridge.query_aoi(0.0, 0.0, radius_cells=5000)
        self.assertIsInstance(clamped_aoi, list)

    def test_evasion_and_edge_cases(self) -> None:
        """Verifies phantom evasion toggle, out-of-bounds indices, and reset."""
        if not self.bridge.is_loaded:
            self.skipTest("Native DLL not loaded")

        # Invalid index handling
        self.bridge.set_velocity(-1, 5.0, 5.0)
        self.bridge.set_velocity(99999, 5.0, 5.0)
        self.bridge.set_evasion(-1, True)
        self.bridge.set_evasion(99999, True)
        invalid_pos = self.bridge.get_entity_pos(-1)
        self.assertEqual(invalid_pos, (0.0, 0.0, 0.0))

        # Query empty AOI area
        empty_aoi = self.bridge.query_aoi(-5000.0, -5000.0, radius_cells=1)
        self.assertEqual(empty_aoi, [])

        # Reset engine
        self.bridge.init(cell_size=32.0)
        self.assertEqual(self.bridge.get_entity_count(), 0)


if __name__ == "__main__":
    unittest.main()
