"""AutoPOE2 - Test Suite Kiểm Chứng Minimap Navigation Adapter (Doc 69 §3.4 / Stage 5).
=====================================================================================
Kiểm thử toàn diện:
1. Ánh xạ vector và góc phương vị sang 8 hướng WASD (W, WD, D, SD, S, SA, A, WA) và deadzone.
2. Kiểm chứng chế độ chiếu Isometric World Projection (math2d.hpp).
3. Kiểm chứng quy trình tự động cắt vùng Minimap từ ảnh toàn màn hình.
4. Bất biến Dwell Time (INV-WASD-MIN-DWELL >= 0.30s).
5. Tương thích Dynamic Resolution Scaler (1080p, 1440p, Windowed 1189x891 PID 22632, 720p).
6. Tích hợp thực tế với MinimapFogExplorer trên các artifact thật từ captures/.
7. Kiểm thử điều phối phát lệnh di chuyển tới Character Controller (dispatch_to_controller).

Tuân thủ nghiêm ngặt:
- Rule 5: Sử dụng artifact thật từ captures/.
- Rule 14: Quality Gate, 100% tests PASS.
"""

from __future__ import annotations

import math
import os
import unittest
import numpy as np
from PIL import Image

from src.assistant_tool.navigation.minimap_navigation_adapter import (
    MinimapNavigationAdapter,
    ProjectionMode,
    WasdCommand,
    WasdDirection,
)
from src.common.coordinate_transform import (
    RES_1080P,
    RES_1440P,
    RES_720P,
    RES_WINDOWED_DEFAULT,
    Resolution,
)


class TestMinimapNavigationAdapter(unittest.TestCase):
    """Test suite kiểm thử MinimapNavigationAdapter."""

    def setUp(self) -> None:
        self.captures_dir = os.path.join(os.path.dirname(__file__), "..", "captures")

    def test_8way_direction_mapping_all_cardinal_and_diagonals(self) -> None:
        """Kiểm chứng ánh xạ 8 hướng WASD trong hệ Direct Screen Projection."""
        test_vectors = [
            # dx, dy, expected_dir, expected_keys
            (1.0, 0.0, WasdDirection.EAST, ("D",)),
            (1.0, 1.0, WasdDirection.SOUTH_EAST, ("S", "D")),
            (0.0, 1.0, WasdDirection.SOUTH, ("S",)),
            (-1.0, 1.0, WasdDirection.SOUTH_WEST, ("S", "A")),
            (-1.0, 0.0, WasdDirection.WEST, ("A",)),
            (-1.0, -1.0, WasdDirection.NORTH_WEST, ("W", "A")),
            (0.0, -1.0, WasdDirection.NORTH, ("W",)),
            (1.0, -1.0, WasdDirection.NORTH_EAST, ("W", "D")),
        ]

        for dx, dy, exp_dir, exp_keys in test_vectors:
            direction, keys, angle = MinimapNavigationAdapter.vector_to_8way_direction(
                dx, dy, mode=ProjectionMode.DIRECT_SCREEN
            )
            self.assertEqual(
                direction, exp_dir,
                f"Lệch hướng: ({dx}, {dy}) -> got {direction}, expected {exp_dir} (angle={angle:.1f})"
            )
            self.assertEqual(
                set(keys), set(exp_keys),
                f"Lệch tổ hợp phím: ({dx}, {dy}) -> got {keys}, expected {exp_keys}"
            )

    def test_deadzone_produces_none_direction(self) -> None:
        """Kiểm chứng vùng chết (Deadzone): Vector quá nhỏ không phát lệnh di chuyển."""
        direction, keys, angle = MinimapNavigationAdapter.vector_to_8way_direction(
            0.001, -0.002, deadzone=0.05
        )
        self.assertEqual(direction, WasdDirection.NONE)
        self.assertEqual(keys, ())
        self.assertEqual(angle, 0.0)

    def test_isometric_world_projection_mapping(self) -> None:
        """Kiểm chứng hệ chiếu Isometric World Projection theo công thức math2d.hpp."""
        # Trong hệ Isometric POE2:
        # Vector tiến thẳng (ndx=1, ndy=1) -> sy = (1+1)*0.707 > 0.38 -> W (North)
        direction, keys, _ = MinimapNavigationAdapter.vector_to_8way_direction(
            1.0, 1.0, mode=ProjectionMode.ISOMETRIC_WORLD
        )
        self.assertIn("W", keys)

        # Vector lùi lại (ndx=-1, ndy=-1) -> sy = (-1-1)*0.707 < -0.38 -> S (South)
        direction, keys, _ = MinimapNavigationAdapter.vector_to_8way_direction(
            -1.0, -1.0, mode=ProjectionMode.ISOMETRIC_WORLD
        )
        self.assertIn("S", keys)

    def test_dwell_time_invariant_min_300ms(self) -> None:
        """Kiểm chứng bất biến INV-WASD-MIN-DWELL: Dwell time luôn >= 0.30s."""
        adapter = MinimapNavigationAdapter(default_dwell_sec=0.15)  # Thử cấu hình dwell quá ngắn
        self.assertGreaterEqual(
            adapter.default_dwell_sec, 0.30,
            "Adapter phải tự động clamp dwell time tối thiểu >= 0.30s theo INV-WASD-MIN-DWELL"
        )

        fake_minimap = np.full((100, 100), 50, dtype=np.uint8)
        fake_minimap[:, 80:] = 0  # Có frontier về phía Đông
        cmd = adapter.compute_navigation_step(fake_minimap)

        if cmd.direction != WasdDirection.NONE:
            self.assertGreaterEqual(cmd.dwell_sec, 0.30)

    def test_dynamic_resolution_scaling_minimap_crop(self) -> None:
        """Kiểm chứng tự động cắt vùng Minimap trên các độ phân giải: 1080p, 1440p, Windowed 1189x891, 720p."""
        resolutions = [
            RES_1080P,
            RES_1440P,
            RES_WINDOWED_DEFAULT,  # PID 22632
            RES_720P,
        ]

        for res in resolutions:
            adapter = MinimapNavigationAdapter(target_resolution=res)
            # Tạo frame ảnh toàn màn hình giả lập
            fullscreen = Image.new("RGB", (res.width, res.height), color=(20, 20, 20))
            crop = adapter.extract_minimap_crop(fullscreen)

            self.assertIsInstance(crop, Image.Image)
            self.assertGreater(crop.width, 50)
            self.assertGreater(crop.height, 50)
            self.assertLess(crop.width, res.width)
            self.assertLess(crop.height, res.height)

    def test_end_to_end_navigation_step_on_real_capture(self) -> None:
        """Kiểm chứng trọn gói quy trình sinh lệnh WASD từ artifact thật minimap_1.png."""
        img_path = os.path.join(self.captures_dir, "minimap_1.png")
        if not os.path.exists(img_path):
            self.skipTest(f"Không tìm thấy capture: {img_path}")

        img = Image.open(img_path)
        adapter = MinimapNavigationAdapter(
            target_resolution=RES_WINDOWED_DEFAULT,
            default_dwell_sec=0.35,
        )

        # Sinh lệnh di chuyển từ ảnh Minimap
        cmd = adapter.compute_navigation_step(img)

        self.assertIsInstance(cmd, WasdCommand)
        self.assertIsInstance(cmd.direction, WasdDirection)
        self.assertGreaterEqual(cmd.coverage_ratio, 0.0)
        self.assertLessEqual(cmd.coverage_ratio, 1.0)

        # Nếu chưa xong map, phải có lệnh di chuyển hợp lệ
        if not cmd.is_fully_explored:
            self.assertNotEqual(cmd.direction, WasdDirection.NONE)
            self.assertGreater(len(cmd.keys), 0)
            self.assertGreaterEqual(cmd.dwell_sec, 0.30)
            self.assertGreater(cmd.screen_target[0], 0)
            self.assertGreater(cmd.screen_target[1], 0)

    def test_dispatch_to_controller_callback(self) -> None:
        """Kiểm chứng cơ chế dispatch lệnh WASD sang Character Controller."""
        adapter = MinimapNavigationAdapter()
        dispatched_payload = []

        def mock_wasd_sender(vx: float, vy: float, dwell: float) -> None:
            dispatched_payload.append((vx, vy, dwell))

        valid_cmd = WasdCommand(
            direction=WasdDirection.NORTH_EAST,
            keys=("W", "D"),
            vector=(0.707, -0.707),
            angle_deg=315.0,
            dwell_sec=0.35,
            screen_target=(1600, 100),
            minimap_target=(300, 50),
            coverage_ratio=0.50,
            is_fully_explored=False,
        )

        success = adapter.dispatch_to_controller(valid_cmd, sender_func=mock_wasd_sender)
        self.assertTrue(success)
        self.assertEqual(len(dispatched_payload), 1)
        self.assertAlmostEqual(dispatched_payload[0][0], 0.707, places=2)
        self.assertAlmostEqual(dispatched_payload[0][1], -0.707, places=2)
        self.assertEqual(dispatched_payload[0][2], 0.35)

        # Lệnh NONE không được gửi
        none_cmd = WasdCommand(
            direction=WasdDirection.NONE,
            keys=(),
            vector=(0.0, 0.0),
            angle_deg=0.0,
            dwell_sec=0.0,
            screen_target=(0, 0),
            minimap_target=(0, 0),
            coverage_ratio=0.98,
            is_fully_explored=True,
        )
        self.assertFalse(adapter.dispatch_to_controller(none_cmd, sender_func=mock_wasd_sender))
        self.assertEqual(len(dispatched_payload), 1)  # Không tăng thêm

    def test_apply_map_layout_intelligence(self) -> None:
        """Kiểm chứng đồng bộ đặc tính layout T15 cho Monk CI vào MinimapNavigationAdapter."""
        adapter = MinimapNavigationAdapter()

        # 1. Map mở rộng (Deserted) -> Dwell dài 0.45s, coverage 92%
        res_des = adapter.apply_map_layout_intelligence("Deserted Map")
        self.assertEqual(res_des["layout_type"], "WIDE_OPEN")
        self.assertEqual(res_des["dwell_sec"], 0.45)
        self.assertEqual(adapter.default_dwell_sec, 0.45)
        self.assertEqual(adapter.target_coverage, 0.92)

        # 2. Map pháo đài (Fortress) -> Dwell 0.35s
        res_fort = adapter.apply_map_layout_intelligence("Fortress Map")
        self.assertEqual(res_fort["layout_type"], "SEMI_OPEN_CORRIDOR")
        self.assertEqual(res_fort["dwell_sec"], 0.35)
        self.assertEqual(adapter.default_dwell_sec, 0.35)

        # 3. Map đầm lầy (Creek / Marsh) -> Dwell 0.38s
        res_crk = adapter.apply_map_layout_intelligence("Creek Map")
        self.assertEqual(res_crk["layout_type"], "WETLAND_MEANDER")
        self.assertEqual(res_crk["dwell_sec"], 0.38)
        self.assertEqual(adapter.default_dwell_sec, 0.38)


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

