"""
Unit Tests cho Client Launcher và Dynamic Campaign Progression Resolver
"""

import os
import sys
import unittest

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

if hasattr(sys.stdout, "reconfigure"):
    try:
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    except Exception:
        pass

from auto_quester import AutoQuester
from client_launcher import Poe2ClientLauncher


class TestClientLauncherAndDynamicQuest(unittest.TestCase):
    def test_find_standalone_exe(self):
        """Kiểm tra tìm đường dẫn PathOfExile.exe trên máy."""
        exe = Poe2ClientLauncher.find_standalone_exe()
        print(f"[Test] Phát hiện đường dẫn POE2: {exe}")
        # Nếu đã cài trên máy thì đường dẫn phải tồn tại
        if exe:
            self.assertTrue(os.path.isfile(exe))
            self.assertTrue(exe.lower().endswith(".exe"))

    def test_is_client_running(self):
        """Kiểm tra hàm is_client_running không ném exception và trả về bool."""
        running = Poe2ClientLauncher.is_client_running()
        self.assertIsInstance(running, bool)

    def test_is_client_window_ready(self):
        """Kiểm tra hàm is_client_window_ready không ném exception và trả về bool."""
        ready = Poe2ClientLauncher.is_client_window_ready()
        self.assertIsInstance(ready, bool)

    def test_is_process_running(self):
        """Kiểm tra hàm is_process_running không ném exception và trả về bool."""
        proc_running = Poe2ClientLauncher.is_process_running()
        self.assertIsInstance(proc_running, bool)

    def test_find_client_hwnd(self):
        """Kiểm tra hàm find_client_hwnd trả về int hoặc None."""
        hwnd = Poe2ClientLauncher.find_client_hwnd()
        self.assertTrue(hwnd is None or isinstance(hwnd, int))

    def test_launch_when_already_running(self):
        """Nếu game đã đang chạy, launch_client phải trả về True ngay mà không mở instance mới."""
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=True):
            # Dù truyền đường dẫn file không tồn tại, nó vẫn trả về True ngay lập tức
            ok = Poe2ClientLauncher.launch_client("Z:\\Fake\\PathOfExile.exe", wait_timeout_sec=1)
            self.assertTrue(ok)

    def test_launch_invalid_exe(self):
        """Khởi chạy với file không tồn tại phải trả về False an toàn."""
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=False):
            ok = Poe2ClientLauncher.launch_client("Z:\\NonExistent\\PathOfExile.exe", wait_timeout_sec=1)
            self.assertFalse(ok)

    def test_launch_client_subprocess_first(self):
        """Khởi chạy ưu tiên subprocess.Popen và hoàn tất khi window ready."""
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", side_effect=[False, True]), \
             patch.object(Poe2ClientLauncher, "is_client_window_ready", return_value=True), \
             patch("os.path.isfile", return_value=True), \
             patch("subprocess.Popen") as mock_popen, \
             patch("time.sleep"):
            ok = Poe2ClientLauncher.launch_client("C:\\Games\\Path of Exile 2\\PathOfExile.exe", wait_timeout_sec=5)
            self.assertTrue(ok)
            self.assertEqual(mock_popen.call_count, 1)
            call_args, call_kwargs = mock_popen.call_args
            self.assertEqual(call_args[0], ["C:\\Games\\Path of Exile 2\\PathOfExile.exe"])
            self.assertEqual(call_kwargs.get("cwd"), "C:\\Games\\Path of Exile 2")
            self.assertEqual(call_kwargs.get("shell"), False)

    def test_launch_client_fallback_to_shellexecute(self):
        """Khởi chạy fallback sang ShellExecuteW khi subprocess.Popen thất bại."""
        import ctypes
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", side_effect=[False, True]), \
             patch.object(Poe2ClientLauncher, "is_client_window_ready", return_value=True), \
             patch("os.path.isfile", return_value=True), \
             patch("subprocess.Popen", side_effect=OSError("Popen failed")), \
             patch.object(ctypes.windll.shell32, "ShellExecuteW", return_value=42) as mock_shell, \
             patch("time.sleep"):
            ok = Poe2ClientLauncher.launch_client("C:\\Games\\Path of Exile 2\\PathOfExile.exe", wait_timeout_sec=5)
            self.assertTrue(ok)
            mock_shell.assert_called_once_with(
                None, "open", "C:\\Games\\Path of Exile 2\\PathOfExile.exe", None, "C:\\Games\\Path of Exile 2", 1
            )

    def test_core_controller_safety_failsafe_move_flag(self):
        """Kiểm tra CoreController: Nếu client chưa chạy -> KHÔNG có cờ --move; Khi client đã chạy -> CÓ cờ --move."""
        from unittest.mock import patch
        try:
            import core_controller as cc_mod
            from core_controller import CoreController
        except ImportError:
            import src.assistant_tool.core_controller as cc_mod
            from src.assistant_tool.core_controller import CoreController

        cc = CoreController()
        cfg = {
            "auto_dodge": True,
            "smart_flask": True,
            "move_mode": "mouse"
        }

        # 1. Khi game client CHƯA CHẠY: TUYỆT ĐỐI KHÔNG có cờ --move
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=False), \
             patch.object(cc_mod.Poe2ClientLauncher, "is_client_running", return_value=False):
            cmd = cc.build_cmd(cfg)
            self.assertNotIn("--move", cmd, "TUYỆT ĐỐI KHÔNG được thêm cờ --move khi game client chưa chạy!")

        # 2. Khi game client ĐÃ CHẠY: Cho phép truyền cờ --move
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=True), \
             patch.object(cc_mod.Poe2ClientLauncher, "is_client_running", return_value=True):
            cmd = cc.build_cmd(cfg)
            self.assertIn("--move", cmd, "Phải có cờ --move khi game client đã chạy thành công!")


    def test_dynamic_quest_act1(self):
        """Kiểm tra nhận diện mục tiêu Act 1 (Riverbank & Farmlands)."""
        quester = AutoQuester()
        obj1 = quester.resolve_active_objective("G1_1", "The Riverbank", 1)
        self.assertEqual(obj1.act, 1)
        self.assertEqual(obj1.target_name, "Clearfell Encampment")

        obj2 = quester.resolve_active_objective("G1_2", "The Ogham Farmlands", 12)
        self.assertEqual(obj2.act, 1)
        self.assertIn("Ogham", obj2.target_name)

    def test_dynamic_quest_act2_town(self):
        """Kiểm tra nhận diện khi nhân vật đang ở Thành Act 2 (Ardura Caravan)."""
        quester = AutoQuester()
        obj = quester.resolve_active_objective("G2_town", "Ardura Caravan", 32)
        self.assertEqual(obj.act, 2)
        self.assertEqual(obj.action_type, "NPC")
        banner = quester.get_status_banner("G2_town", "Ardura Caravan", 32)
        self.assertIn("Act 2", banner)
        self.assertIn("Ardura Caravan", banner)

    def test_dynamic_quest_act2_combat_zone(self):
        """Kiểm tra nhận diện khi nhân vật đang ở map chiến đấu Act 2 (Dreadnought)."""
        quester = AutoQuester()
        obj = quester.resolve_active_objective("G2_12", "Dreadnought", 31)
        self.assertEqual(obj.act, 2)
        self.assertIn("Jamanra", obj.target_name)
        banner = quester.get_status_banner("G2_12", "Dreadnought", 31)
        self.assertIn("Jamanra", banner)

    def test_dynamic_quest_unknown_leveling_zone(self):
        """Kiểm tra nhận diện linh hoạt với map lạ chưa có trong database."""
        quester = AutoQuester()
        obj = quester.resolve_active_objective("Custom_Map_99", "Ancient Ruins", 35)
        self.assertEqual(obj.act, 2) # Lv 35 -> Act 2
        self.assertEqual(obj.action_type, "EXPLORE")
        self.assertIn("Ancient Ruins", obj.area_name)

    def test_recover_client_when_healthy(self):
        """INV-CLIENT-CRASH-RECOVER: Khi game đang chạy bình thường, recover_client_if_crashed trả về True ngay."""
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=True), \
             patch.object(Poe2ClientLauncher, "is_client_window_ready", return_value=True), \
             patch.object(Poe2ClientLauncher, "launch_client") as mock_launch:
            ok = Poe2ClientLauncher.recover_client_if_crashed()
            self.assertTrue(ok)
            mock_launch.assert_not_called()

    def test_recover_client_when_crashed(self):
        """INV-CLIENT-CRASH-RECOVER: Khi game bị crash, recover_client_if_crashed tự động gọi launch_client."""
        from unittest.mock import patch
        with patch.object(Poe2ClientLauncher, "is_client_running", return_value=False), \
             patch.object(Poe2ClientLauncher, "launch_client", return_value=True) as mock_launch, \
             patch("time.sleep"):
            ok = Poe2ClientLauncher.recover_client_if_crashed()
            self.assertTrue(ok)
            mock_launch.assert_called_once()

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