"""
Unit Tests cho Town & World Map Quest Engine (Phím U)
Kiểm thử:
1. Cấu trúc QuestObjective mở rộng zone_index
2. Phân loại khu vực Làng (Town Hub)
3. Tra cứu nhiệm vụ theo Act
4. Đóng gói & giải mã gói tin IPC TRIGGER_WORLD_MAP_TRAVEL (OpCode 96)
"""

import os
import struct
import sys
import unittest

# Thêm thư mục gốc vào sys.path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from src.assistant_tool.auto_quester import CAMPAIGN_KNOWLEDGE_GRAPH, AutoQuester


class TestTownQuestEngine(unittest.TestCase):
    def setUp(self):
        self.quester = AutoQuester()

    def test_quest_objective_zone_index(self):
        """Kiểm tra thuộc tính zone_index trong đồ thị tri thức nhiệm vụ."""
        for step in CAMPAIGN_KNOWLEDGE_GRAPH:
            self.assertIsInstance(step.zone_index, int)
            self.assertGreaterEqual(step.zone_index, 0)
            self.assertLessEqual(step.zone_index, 10)

    def test_is_town_area_detection(self):
        """Kiểm tra nhận diện chính xác các khu vực Làng / Hideout."""
        # Các trường hợp là Làng / Hideout
        self.assertTrue(self.quester.is_town_area("G1_town", "Clearfell Encampment"))
        self.assertTrue(self.quester.is_town_area("G2_town", "Ardura Caravan"))
        self.assertTrue(self.quester.is_town_area("Hideout_Desert", "Desert Hideout"))
        self.assertTrue(self.quester.is_town_area("", "The Haven"))
        # Nhận diện Làng / Hub Act 3
        self.assertTrue(self.quester.is_town_area("G3_town", "The Sandswept Marsh / Phrey Jungle Encampment"))
        self.assertTrue(self.quester.is_town_area("g3_town", ""))
        self.assertTrue(self.quester.is_town_area("g3", ""))
        self.assertTrue(self.quester.is_town_area("", "Act 3 Encampment"))

        # Các trường hợp là Map chiến đấu (Sandswept Marsh G3_1 Lv 33 KHÔNG PHẢI là Làng)
        self.assertFalse(self.quester.is_town_area("G1_1", "The Riverbank"))
        self.assertFalse(self.quester.is_town_area("G1_2", "The Ogham Farmlands"))
        self.assertFalse(self.quester.is_town_area("G2_12", "Dreadnought"))
        self.assertFalse(self.quester.is_town_area("G3_1", "Sandswept Marsh"))
        self.assertFalse(self.quester.is_town_area("", "Sandswept Marsh"))
        self.assertFalse(self.quester.is_town_area("", "The Sandswept Marsh"))
        self.assertFalse(self.quester.is_town_area("G3_2", "Phrey Jungle"))
        self.assertFalse(self.quester.is_town_area("G3_5", "The Ziggurat / Aggorat"))

        # Các UI scene overlay tuyệt đối KHÔNG PHẢI là Làng
        for scene in ["Act 1", "Act 2", "Act 3", "Act 4", "Atlas", "(null)", "(unknown)"]:
            self.assertFalse(self.quester.is_town_area("", scene))

    def test_invalid_map_names(self):
        """Kiểm tra: Act 1..4, Atlas, null, unknown không bị coi là tên map hợp lệ."""
        for invalid in ["Act 1", "Act 2", "Act 3", "Act 4", "Atlas", "(null)", "(unknown)"]:
            self.assertFalse(self.quester.is_valid_map_name(invalid), f"{invalid} không được coi là map hợp lệ")

        # Map thật phải hợp lệ
        for valid in ["The Riverbank", "Clearfell Encampment", "Sandswept Marsh", "Phrey Jungle"]:
            self.assertTrue(self.quester.is_valid_map_name(valid), f"{valid} phải là map hợp lệ")

        # resolve_active_objective khi truyền UI scene không được lấy UI scene làm tên map
        obj = self.quester.resolve_active_objective("", "Act 1", 1)
        self.assertNotEqual(obj.area_name, "Act 1")
        self.assertEqual(obj.area_name, "Zone Lv 1")

    def test_get_objectives_for_act(self):
        """Kiểm tra lọc danh sách nhiệm vụ theo Act 1, 2, 3."""
        act1_steps = self.quester.get_objectives_for_act(1)
        self.assertGreater(len(act1_steps), 0)
        for s in act1_steps:
            self.assertEqual(s.act, 1)

        act2_steps = self.quester.get_objectives_for_act(2)
        self.assertGreater(len(act2_steps), 0)
        for s in act2_steps:
            self.assertEqual(s.act, 2)

        # Kiểm tra sự hiện diện của Boss Dreadnought Act 2
        dreadnought_found = any("Dreadnought" in s.area_name for s in act2_steps)
        self.assertTrue(dreadnought_found, "Phải có nhiệm vụ Dreadnought trong Act 2")

        # Kiểm tra lọc danh sách nhiệm vụ Act 3
        act3_steps = self.quester.get_objectives_for_act(3)
        self.assertGreater(len(act3_steps), 0)
        for s in act3_steps:
            self.assertEqual(s.act, 3)

        sandswept_found = any("Sandswept Marsh" in s.area_name for s in act3_steps)
        self.assertTrue(sandswept_found, "Phải có nhiệm vụ Sandswept Marsh trong Act 3")
        phrey_found = any("Phrey Jungle" in s.area_name for s in act3_steps)
        self.assertTrue(phrey_found, "Phải có nhiệm vụ Phrey Jungle trong Act 3")

    def test_resolve_active_objective_act3_level30_not_act1(self):
        """Kiểm tra: Nhân vật Level 30 đang ở Act 3 KHÔNG BAO GIỜ bị phán đoán nhầm về Act 1."""
        # 1. Map chiến đấu có code g3 (Sandswept Marsh - mở Waypoint)
        obj1 = self.quester.resolve_active_objective("G3_1", "Sandswept Marsh", 30)
        self.assertEqual(obj1.act, 3)
        self.assertNotEqual(obj1.act, 1, "Level 30 tại G3_1 không được là Act 1")
        self.assertEqual(obj1.action_type, "WAYPOINT")
        self.assertIn("Sandswept Marsh", obj1.area_name)

        # 2. Map chiến đấu Phrey Jungle
        obj2 = self.quester.resolve_active_objective("G3_2", "Phrey Jungle", 30)
        self.assertEqual(obj2.act, 3)
        self.assertIn("Phrey Jungle", obj2.area_name)
        self.assertNotEqual(obj2.act, 1)

        # 3. Làng Act 3 (G3_town / Encampment)
        obj3 = self.quester.resolve_active_objective("G3_town", "Phrey Jungle Encampment", 30)
        self.assertEqual(obj3.act, 3)
        self.assertEqual(obj3.action_type, "NPC")
        self.assertNotEqual(obj3.act, 1)

        # 4. Khi chỉ có tên map chứa sandswept và không có code
        obj4 = self.quester.resolve_active_objective("", "The Sandswept Marsh", 30)
        self.assertEqual(obj4.act, 3)
        self.assertNotEqual(obj4.act, 1)
        self.assertEqual(obj4.action_type, "WAYPOINT")

        # 5. Khi chỉ có tên map chứa act 3
        obj5 = self.quester.resolve_active_objective("", "Phrey Jungle (Act 3)", 30)
        self.assertEqual(obj5.act, 3)
        self.assertNotEqual(obj5.act, 1)

        # 6. Status banner phải hiển thị chuẩn Act 3
        banner = self.quester.get_status_banner("G3_1", "Sandswept Marsh", 30)
        self.assertIn("[Act 3]", banner)
        self.assertNotIn("[Act 1]", banner)

    def test_town_world_map_ipc_packet_serialization(self):
        """Kiểm tra định dạng nhị phân 64 bytes của lệnh TRIGGER_WORLD_MAP_TRAVEL (OpCode 96)."""
        act = 2
        zone_index = 3 # Dreadnought
        cmd_id = 996
        opcode = 96 # TRIGGER_WORLD_MAP_TRAVEL
        target_x = float(act)
        target_y = float(zone_index)
        target_z = 0.0
        target_entity_id = 0
        priority = 1
        timeout_ms = 5000

        # Đóng gói struct chuẩn C++: <QIfffIII (64 bytes)
        raw = struct.pack("<QIfffIII", cmd_id, opcode, target_x, target_y, target_z, target_entity_id, priority, timeout_ms)
        raw_padded = raw.ljust(64, b"\x00")
        self.assertEqual(len(raw_padded), 64)

        # Giải mã kiểm chứng
        unpacked_id, unpacked_op, u_act, u_zone, u_z, u_ent, u_prio, u_timeout = struct.unpack("<QIfffIII", raw_padded[:36])
        self.assertEqual(unpacked_id, 996)
        self.assertEqual(unpacked_op, 96)
        self.assertEqual(int(u_act), 2)
        self.assertEqual(int(u_zone), 3)


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