"""
Unit tests for 2.5D Isometric Math and Server Engine Loop in FreeExile.
Verifies projection consistency, tick execution, spatial AOI culling, and combat integration.
"""

import sys
import os
import unittest

# Ensure server module is in sys.path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

from world.isometric_math import IsometricMath, Vector2D
from world.server_engine_loop import (
    ServerEngineLoop,
    ClientMoveCommand,
    ClientSkillCommand,
    FiveElements
)


class TestIsometricMathAndEngineLoop(unittest.TestCase):
    def test_isometric_projection_and_unprojection(self):
        """Test world to screen and screen to world reverse projection consistency."""
        world_x, world_y, world_z = 10.0, 5.0, 1.5
        screen_pt = IsometricMath.world_to_screen(world_x, world_y, world_z)

        # Unproject back to world plane at z=1.5
        recovered_world = IsometricMath.screen_to_world(screen_pt.x, screen_pt.y, world_z)

        self.assertAlmostEqual(recovered_world.x, world_x, places=4)
        self.assertAlmostEqual(recovered_world.y, world_y, places=4)

    def test_depth_sorting_key(self):
        """Objects further down on the screen (larger Y) must have higher depth keys."""
        depth_near = IsometricMath.calculate_depth_key(world_y=10.0, world_z=0.0)
        depth_far = IsometricMath.calculate_depth_key(world_y=2.0, world_z=0.0)
        self.assertGreater(depth_near, depth_far)

    def test_height_overlap(self):
        """Entities on different elevation tiers outside capsule threshold should not collide."""
        self.assertTrue(IsometricMath.check_height_overlap(0.0, 1.2))
        self.assertFalse(IsometricMath.check_height_overlap(0.0, 3.5))

    def test_server_engine_tick_lifecycle(self):
        """Test server tick execution with movement and AOI snapshot generation."""
        engine = ServerEngineLoop(cell_size=64.0)

        # Register 2 players in neighboring positions
        engine.register_player(entity_id=1, initial_x=10.0, initial_y=10.0, element=FiveElements.KIM)
        engine.register_player(entity_id=2, initial_x=20.0, initial_y=20.0, element=FiveElements.HOA)

        # Queue move command for player 1 (move along +X)
        engine.queue_move_command(ClientMoveCommand(
            entity_id=1,
            dir_x=1.0,
            dir_y=0.0,
            sequence=1,
            timestamp_ms=engine.current_time_ms
        ))

        # Run 1 tick (33.33ms)
        tick_res = engine.step_tick(dt=0.03333)

        self.assertEqual(tick_res.tick_number, 1)
        self.assertEqual(tick_res.processed_moves, 1)
        self.assertEqual(tick_res.entities_count, 2)
        self.assertGreater(tick_res.snapshots_generated, 0)

        # Player 1 should have moved forward by ~0.2m (6.0m/s * 0.03333s)
        p1 = engine.active_entities[1]
        self.assertGreater(p1.x, 10.15)

        # Player 2 should see Player 1 in its 9-cell AOI
        snapshots = engine.get_aoi_snapshots_for_entity(observer_id=2)
        visible_ids = [s.entity_id for s in snapshots]
        self.assertIn(1, visible_ids)
        self.assertIn(2, visible_ids)

    def test_combat_and_evasion_in_tick_loop(self):
        """Test skill execution and 250ms phantom evasion i-frame during engine tick."""
        engine = ServerEngineLoop(cell_size=64.0)

        engine.register_player(entity_id=101, initial_x=0.0, initial_y=0.0, element=FiveElements.HOA)
        engine.register_player(entity_id=102, initial_x=2.0, initial_y=0.0, element=FiveElements.KIM)

        # Queue attack from 101 to 102
        engine.queue_skill_command(ClientSkillCommand(
            attacker_id=101,
            target_id=102,
            skill_id=1,
            raw_damage=100.0,
            element=FiveElements.HOA,
            sequence=1,
            timestamp_ms=engine.current_time_ms
        ))

        # Step tick 1
        tick_res = engine.step_tick(dt=0.03333)
        self.assertEqual(tick_res.processed_skills, 1)

        # Player 102 (Kim) hit by Player 101 (Hoa: Hỏa khắc Kim bonus 1.3x)
        # Defender 102 should take damage or have reduced HP if applied
        p102_actor = engine.combat_engine.actors[102]
        self.assertIsNotNone(p102_actor)

        # Trigger Huyễn Ảnh Bộ on Player 102
        engine.queue_evasion(entity_id=102, timestamp_ms=engine.current_time_ms)
        engine.step_tick(dt=0.01)

        # Confirm evasion state is active in AOI snapshot
        p101_views = engine.get_aoi_snapshots_for_entity(observer_id=101)
        p102_snapshot = next(s for s in p101_views if s.entity_id == 102)
        self.assertTrue(p102_snapshot.is_evading)


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