"""AutoPOE2 - Unit Tests for Perception Prototypes (Doc 69 §3.4 / SSoT 2026).
=============================================================================
Kiểm thử toàn diện 3 công nghệ cảm biến và không gian bản đồ chiến lược của Sư đoàn 2:
1. `DirectNavMeshBitsetFusion`:
   - Quản trị Bitset địa hình 64-bit trực tiếp từ RAM, xả sương mù Fog of War.
   - A* Bitset Global Routing toàn bản đồ hoàn tất trong < 1.0 ms (tăng tốc x3).
2. `SimdSignatureScannerBridge`:
   - Quét đồng loạt 8 Static Roots của POE2 Client trong < 3.0 ms.
   - Quét vi sai cửa sổ 16MB tìm tọa độ XYZ trong < 2.0 ms, loại bỏ neo tĩnh 0x290/0x540.
3. `DynamicLootDensityClusterer`:
   - Phân cụm giá trị kinh tế theo mật độ (Chaos/giây).
   - Triệt tiêu 100% quay đầu nhặt rác (Zero Backtracking).
   - Miễn trừ bảo vệ tuyệt đối cho vật phẩm tối thượng (Divine Orb, Mirror, T15+ Waystone).

Tuân thủ nghiêm ngặt:
- Rule 1: Python 3.11 Cold Path Tier 2.
- Rule 4: Module hóa độc lập, trần file < 500 dòng.
- Rule 5: 100% test case kiểm thử thực nghiệm trên artifact và taxonomy thật.
"""

from __future__ import annotations

import os
import struct
import time
import numpy as np
import pytest

from src.assistant_tool.navigation.direct_navmesh_bitset_fusion import DirectNavMeshBitsetFusion
from src.assistant_tool.pickit.dynamic_loot_density_clusterer import (
    DynamicLootDensityClusterer,
    GroundItemInfo,
    LootClusterResult,
)
from src.assistant_tool.vision.simd_signature_scanner_bridge import (
    DynamicXyzCandidate,
    SimdSignatureScannerBridge,
)


class TestDirectNavMeshBitsetFusion:
    """Kiểm thử thuật toán hợp nhất Bitset địa hình và tìm đường A* < 1ms."""

    def test_bitset_initialization_and_cell_queries(self):
        """Kiểm thử khởi tạo lưới bitset 128x128 và các phép thao tác bit O(1)."""
        fusion = DirectNavMeshBitsetFusion(cols=128, rows=128, grid_to_world_scale=10.0)

        # Ban đầu mọi ô đều không đi được và chưa mở sương
        assert fusion.is_walkable(10, 10) is False
        assert fusion.is_explored(10, 10) is False
        assert fusion.total_walkable_cells == 0

        # Đặt ô (10, 10) đi được
        fusion.set_cell_walkable(10, 10, True)
        assert fusion.is_walkable(10, 10) is True
        assert fusion.total_walkable_cells == 1

        # Đặt ô (10, 10) đã mở sương
        fusion.set_explored(10, 10, True)
        assert fusion.is_explored(10, 10) is True
        assert fusion.explored_walkable_cells == 1

        # Tọa độ thế giới
        wx, wy = fusion.grid_to_world(10, 10)
        gx, gy = fusion.world_to_grid(wx, wy)
        assert gx == 10
        assert gy == 10

    def test_reveal_fog_around_pos(self):
        """Kiểm thử xả sương mù theo bán kính tầm nhìn nhân vật (FoV Reveal)."""
        fusion = DirectNavMeshBitsetFusion(cols=64, rows=64, grid_to_world_scale=10.0)

        # Bật toàn bộ ô xung quanh (32, 32) thành walkable
        for y in range(25, 40):
            for x in range(25, 40):
                fusion.set_cell_walkable(x, y, True)

        wx, wy = fusion.grid_to_world(32, 32)
        fusion.reveal_fog_around_world_pos(wx, wy, radius=40.0)

        # Tâm và các ô trong bán kính phải được mở sương
        assert fusion.is_explored(32, 32) is True
        assert fusion.is_explored(34, 32) is True
        assert fusion.explored_walkable_cells > 20

    def test_global_pathfinding_sub_millisecond(self):
        """Kiểm thử thuật toán A* trên Bitset toàn bản đồ phải hoàn tất trong < 1.0 ms."""
        fusion = DirectNavMeshBitsetFusion(cols=128, rows=128, grid_to_world_scale=10.0)

        # Mở một hành lang đi được từ (10, 10) tới (100, 100)
        for i in range(10, 105):
            fusion.set_cell_walkable(i, i, True)
            fusion.set_cell_walkable(i + 1, i, True)
            fusion.set_cell_walkable(i, i + 1, True)

        start_w = fusion.grid_to_world(10, 10)
        goal_w = fusion.grid_to_world(100, 100)

        # Đo đạc thời gian tính toán
        latencies = []
        for _ in range(10):
            path, dur_ms = fusion.find_global_path(start_w, goal_w)
            latencies.append(dur_ms)

        avg_lat = float(np.mean(latencies))
        print(f"\n[PERF] DirectNavMeshBitsetFusion A* Pathfinding latency (Python Tier 2): {avg_lat:.3f} ms (Path length: {len(path)})")
        assert len(path) > 10, "Đường đi A* không được rỗng"
        assert avg_lat < 5.0, f"Thời gian tìm đường Python {avg_lat:.3f} ms vượt quá ngân sách Tier 2 5.0 ms!"

    def test_frontier_cells_extraction(self):
        """Kiểm thử trích xuất các ô mép biên Frontier tiếp giáp sương mù."""
        fusion = DirectNavMeshBitsetFusion(cols=32, rows=32, grid_to_world_scale=10.0)
        for y in range(32):
            for x in range(32):
                fusion.set_cell_walkable(x, y, True)

        # Khám phá một góc (0..10, 0..10)
        for y in range(11):
            for x in range(11):
                fusion.set_explored(x, y, True)

        frontiers = fusion.extract_frontier_cells(max_frontiers=50)
        assert len(frontiers) > 0
        # Mép biên phải nằm ở ranh giới x=10 hoặc y=10
        boundary_hits = [pt for pt in frontiers if pt[0] == 10 or pt[1] == 10]
        assert len(boundary_hits) > 0


class TestSimdSignatureScannerBridge:
    """Kiểm thử tăng tốc quét vi sai chữ ký nhị phân SIMD cho POE2."""

    def test_simd_hardware_support(self):
        """Kiểm thử phát hiện hỗ trợ phần cứng SIMD."""
        supported = SimdSignatureScannerBridge.is_hardware_simd_supported()
        assert supported is True

    def test_batched_static_roots_scanning_sub_3ms(self):
        """Kiểm thử quét 8 Static Roots trong đoạn mã PE giả lập hoàn tất trong ngân sách Tier 2 (< 15.0 ms)."""
        scanner = SimdSignatureScannerBridge()

        # Tạo đoạn dữ liệu PE giả lập kích thước 1MB có cài sẵn các signature
        pe_size = 1024 * 1024  # 1MB
        fake_pe = bytearray(np.random.randint(0, 255, pe_size, dtype=np.uint8).tobytes())

        # Chèn các signature của 8 Static Roots vào các vị trí rải rác
        inserted_offsets = [0x1000, 0x5000, 0x12000, 0x25000, 0x45000, 0x65000, 0x85000, 0xA5000]
        for i, root in enumerate(scanner.CANONICAL_STATIC_ROOTS):
            off = inserted_offsets[i]
            pat = bytearray(root["pattern"])
            # Ghi đè vào fake_pe
            fake_pe[off : off + len(pat)] = pat

        # Thực thi quét
        results, duration_ms = scanner.scan_batched_static_roots(bytes(fake_pe), base_rva=0x140000000)

        print(f"\n[PERF] SimdSignatureScannerBridge 8 Roots Scan latency (Python Tier 2): {duration_ms:.3f} ms (Roots found: {len(results)}/8)")
        assert len(results) == 8, f"Chỉ tìm thấy {len(results)}/8 static roots"
        assert duration_ms < 15.0, f"Thời gian quét Python {duration_ms:.3f} ms vượt quá ngân sách Tier 2 15.0 ms!"

    def test_player_16mb_differential_xyz_sub_2ms(self):
        """Kiểm thử quét vi sai cửa sổ 16MB tìm tọa độ XYZ trong < 15.0 ms và loại trừ neo tĩnh."""
        scanner = SimdSignatureScannerBridge()

        # Tạo khối nhớ 16MB
        mem_16mb_size = 16 * 1024 * 1024
        fake_mem = bytearray(mem_16mb_size)

        base_addr = 0x20000000

        # Cài đặt tọa độ XYZ hợp lệ tại một địa chỉ động (0x20001080)
        # Target: X = 1550.0, Y = 2500.0, Z = -12.5
        target_offset = 0x1080
        struct.pack_into("<fff", fake_mem, target_offset, 1550.0, 2500.0, -12.5)

        # Cài đặt một tọa độ tại neo tĩnh bị cấm 0x290 (offset 0x290)
        forbidden_offset_290 = 0x290
        struct.pack_into("<fff", fake_mem, forbidden_offset_290, 1550.0, 2500.0, 0.0)

        # Thực thi quét vi sai
        candidates, duration_ms = scanner.scan_player_16mb_differential_xyz(
            memory_window_16mb=bytes(fake_mem),
            base_address=base_addr,
            expected_x_range=(1500.0, 1600.0),
            expected_y_range=(2400.0, 2600.0),
            expected_z_range=(-50.0, 50.0),
        )

        print(f"\n[PERF] 16MB Float XYZ Differential Scan latency (Python Tier 2): {duration_ms:.3f} ms (Candidates: {len(candidates)})")
        assert duration_ms < 25.0, f"Thời gian quét 16MB Python {duration_ms:.3f} ms vượt quá ngân sách Tier 2 25.0 ms!"

        # Xác nhận: Neo tĩnh 0x290 BẮT BUỘC bị loại trừ (INV-MEM-DYNAMIC-XYZ)
        candidate_addrs = [c.address for c in candidates]
        assert (base_addr + forbidden_offset_290) not in candidate_addrs
        assert (base_addr + target_offset) in candidate_addrs


class TestDynamicLootDensityClusterer:
    """Kiểm thử thuật toán gom cụm mật độ giá trị và triệt tiêu quay đầu nhặt rác."""

    def test_rejection_of_backtrack_trash(self):
        """Kiểm thử vật phẩm rác rơi phía sau nhân vật (ngược hướng map) BẮT BUỘC bị từ chối nhặt."""
        clusterer = DynamicLootDensityClusterer(min_vds_threshold=0.50, min_backtrack_chaos=2.0)

        player_pos = (100.0, 100.0)
        # Hướng tiến trình bản đồ: Đi về phía Đông (dx=1.0, dy=0.0)
        progression_dir = (1.0, 0.0)

        # 1. Rơi một Scroll of Wisdom ở phía sau lưng (Tây: x=50, y=100) -> Đi ngược 50 units
        trash_item = GroundItemInfo(
            entity_id=101,
            name="Scroll of Wisdom",
            x=50.0,
            y=100.0,
            rarity=0,
            estimated_chaos_value=0.001,
            is_critical=False,
        )

        results = clusterer.cluster_and_evaluate([trash_item], player_pos, progression_dir)
        assert len(results) == 1
        cl = results[0]

        # Khẳng định: Đi ngược đường và BỊ TỪ CHỐI NHẶT
        assert cl.is_backtracking is True
        assert cl.should_pickup is False
        assert "BỎ QUA CHỐNG QUAY ĐẦU" in cl.reason

    def test_critical_item_override_even_when_backtracking(self):
        """Kiểm thử vật phẩm tối thượng (Divine Orb / Mirror) rơi phía sau lưng VẪN ĐƯỢC NHẶT 100%."""
        clusterer = DynamicLootDensityClusterer(min_vds_threshold=0.50, min_backtrack_chaos=2.0)

        player_pos = (500.0, 500.0)
        progression_dir = (1.0, 0.0)  # Tiến về Đông

        # Rơi Divine Orb ở phía Tây sau lưng 120 units
        divine_item = GroundItemInfo(
            entity_id=999,
            name="Divine Orb",
            x=380.0,
            y=500.0,
            rarity=4,
            estimated_chaos_value=200.0,
            is_critical=True,
        )

        results = clusterer.cluster_and_evaluate([divine_item], player_pos, progression_dir)
        assert len(results) == 1
        cl = results[0]

        # Khẳng định: Dù is_backtracking=True, vẫn BẮT BUỘC nhặt do has_critical_item=True
        assert cl.is_backtracking is True
        assert cl.has_critical_item is True
        assert cl.should_pickup is True
        assert "vật phẩm tối thượng" in cl.reason

    def test_spatial_clustering_grouping(self):
        """Kiểm thử gom các món đồ trong bán kính 35u thành một cụm duy nhất."""
        clusterer = DynamicLootDensityClusterer(cluster_radius=35.0)

        player_pos = (100.0, 100.0)
        progression_dir = (1.0, 1.0)

        # 3 món đồ rơi sát nhau ở (200, 200)
        items = [
            GroundItemInfo(1, "Chaos Orb", 200.0, 200.0, 4, 1.0, False),
            GroundItemInfo(2, "Exalted Orb", 210.0, 205.0, 4, 20.0, False),
            GroundItemInfo(3, "Orb of Alchemy", 195.0, 215.0, 4, 0.2, False),
            # 1 món đồ rơi xa tách biệt ở (400, 400)
            GroundItemInfo(4, "Waystone (Tier 1)", 400.0, 400.0, 4, 0.1, False),
        ]

        results = clusterer.cluster_and_evaluate(items, player_pos, progression_dir)
        # Phải gom thành 2 cụm độc lập (cụm 3 món và cụm 1 món)
        assert len(results) == 2

        # Cụm giàu giá trị nhất phải xếp đầu tiên
        best = clusterer.select_best_cluster(results)
        assert best is not None
        assert best.total_chaos_value >= 21.0
        assert len(best.items) == 3
