"""
AutoPOE2 - Unit Tests for WaystoneLoaderGuard (Doc 48 / Doc 56)
=============================================================
Kiểm thử chuyên biệt cho module WaystoneLoaderGuard:
- crisp_ctrl_click (Zero Mouse Drag, phím Ctrl + LMB)
- is_item_stuck_on_cursor (quét quang học cảnh báo "You cannot drop that item here")
- recover_cursor_item_to_inventory (giải cứu con trỏ dính item và phục hồi trạng thái)
"""

from unittest.mock import MagicMock, patch
import pytest

from src.assistant_tool.waystone_loader_guard import WaystoneLoaderGuard


class TestWaystoneLoaderGuardSuite:
    """Bộ kiểm thử độc lập cho WaystoneLoaderGuard."""

    def test_crisp_ctrl_click_simulation(self):
        """Kiểm tra chế độ mô phỏng trả về True an toàn."""
        res = WaystoneLoaderGuard.crisp_ctrl_click(100, 200, is_simulated=True)
        assert res is True

    @patch("ctypes.windll.user32.SetCursorPos")
    @patch("ctypes.windll.user32.keybd_event")
    @patch("ctypes.windll.user32.mouse_event")
    def test_crisp_ctrl_click_mocked_sequence(self, mock_mouse_event, mock_keybd_event, mock_set_cursor):
        """Xác thực chuỗi gọi Win32 API dứt khoát, không drag chuột."""
        mock_set_cursor.return_value = 1
        res = WaystoneLoaderGuard.crisp_ctrl_click(
            screen_x=1731,
            screen_y=822,
            hold_ms=0.01,
            delay_after=0.01,
            is_simulated=False,
        )
        assert res is True
        # Phải đặt chuột tại (1731, 822) tối thiểu 2 lần (trước khi ấn Ctrl và trước khi Click)
        assert mock_set_cursor.call_count >= 2
        mock_set_cursor.assert_any_call(1731, 822)

        # Phải có sự kiện Ctrl Down và Ctrl Up
        assert mock_keybd_event.call_count >= 2
        # Phải có sự kiện LMB Down và LMB Up
        assert mock_mouse_event.call_count >= 2
        mock_mouse_event.assert_any_call(WaystoneLoaderGuard.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
        mock_mouse_event.assert_any_call(WaystoneLoaderGuard.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

    @patch("src.common.win32_input.post_message_ctrl_click", return_value=True)
    def test_crisp_ctrl_click_post_message_mode(self, mock_pm_ctrl):
        """Xác thực chế độ gửi qua PostMessageW ngầm không dịch chuyển chuột."""
        res = WaystoneLoaderGuard.crisp_ctrl_click(
            screen_x=1731,
            screen_y=822,
            hold_ms=0.04,
            delay_after=0.01,
            is_simulated=False,
            hwnd=123456,
            use_post_message=True,
        )
        assert res is True
        mock_pm_ctrl.assert_called_once_with(123456, 1731, 822, hold_ms=0.04, delay_after=0.01)

    def test_is_item_stuck_on_cursor_none_or_missing_file(self):
        """Kiểm tra xử lý file không tồn tại hoặc None -> False an toàn."""
        assert WaystoneLoaderGuard.is_item_stuck_on_cursor(None) is False
        assert WaystoneLoaderGuard.is_item_stuck_on_cursor("") is False
        assert WaystoneLoaderGuard.is_item_stuck_on_cursor("non_existent_image_12345.png") is False

    @patch("os.path.exists", return_value=True)
    @patch("PIL.Image.open")
    @patch("src.common.safe_recognize_pil_sync")
    def test_is_item_stuck_on_cursor_detected(self, mock_ocr, mock_img_open, mock_exists):
        """Phát hiện chính xác popup cảnh báo vật phẩm dính chuột."""
        mock_ocr.return_value = {
            "lines": [
                {"text": "You cannot drop that item here."},
                {"text": "Map Device"},
            ]
        }
        res = WaystoneLoaderGuard.is_item_stuck_on_cursor("mock_screenshot.png")
        assert res is True

    @patch("os.path.exists", return_value=True)
    @patch("PIL.Image.open")
    @patch("src.common.safe_recognize_pil_sync")
    def test_is_item_stuck_on_cursor_not_detected(self, mock_ocr, mock_img_open, mock_exists):
        """Màn hình bình thường không có popup cảnh báo."""
        mock_ocr.return_value = {
            "lines": [
                {"text": "Traverse"},
                {"text": "Tier 15 Deserted Waystone"},
            ]
        }
        res = WaystoneLoaderGuard.is_item_stuck_on_cursor("mock_normal_screen.png")
        assert res is False

    def test_recover_cursor_item_to_inventory_execution(self):
        """Xác thực quy trình hoàn trả vật phẩm về ô túi đồ và phát phím ESC."""
        clicked_coords = []
        pressed_keys = []

        def mock_click(x, y):
            clicked_coords.append((x, y))

        def mock_press_key(vk, sc):
            pressed_keys.append((vk, sc))

        recovered = WaystoneLoaderGuard.recover_cursor_item_to_inventory(
            click_func=mock_click,
            slot_x=1731,
            slot_y=822,
            press_key_func=mock_press_key,
            delay_after=0.01,
        )
        assert recovered is True
        assert (1731, 822) in clicked_coords
        assert (0x1B, 0x01) in pressed_keys  # VK_ESCAPE (0x1B), ScanCode ESC (0x01)
