"""AutoPOE2 - Unit & Integration Tests cho Focus Interlock Guard.
===================================================================
Domain: QA & Governance Sentinel (Sư đoàn 4) & Common Foundation
Mục tiêu:
- Bảo vệ Desktop người dùng: Cấm tuyệt đối rò rỉ phím WASD / click chuột khi game POE2 bị unfocused.
- Tuân thủ nghiêm ngặt Focus Interlock và Rule 4C & Rule 5.
"""

from __future__ import annotations

import os
from unittest.mock import MagicMock, call, patch

import pytest

from src.common.win32_input import (
    VK_A,
    VK_D,
    VK_S,
    VK_W,
    click,
    ctrl_click,
    drag,
    emergency_release_wasd_keys,
    press_key,
    send_wasd_direction,
    set_input_simulation_mode,
)
from src.common.win32_window import is_poe2_window_focused


@pytest.fixture(autouse=True)
def reset_simulation_mode():
    """Tự động đặt lại simulation mode sau mỗi bài test."""
    set_input_simulation_mode(None)
    yield
    set_input_simulation_mode(None)


class TestPoe2WindowFocusDetection:
    """Kiểm thử hàm dò tìm Focus của cửa sổ game POE2."""

    def test_focus_returns_true_when_foreground_is_poe_class(self):
        """Khi Foreground Window có ClassName là 'POEWindowClass' -> is_poe2_window_focused = True."""
        with patch("ctypes.windll.user32.GetForegroundWindow", return_value=12345):
            def mock_get_class(hwnd, buf, max_len):
                buf.value = "POEWindowClass"
                return len(buf.value)

            with patch("ctypes.windll.user32.GetClassNameW", side_effect=mock_get_class):
                assert is_poe2_window_focused() is True

    def test_focus_returns_true_when_foreground_is_poec2_class(self):
        """Khi Foreground Window có ClassName là 'POEC2_MainWindow' -> is_poe2_window_focused = True."""
        with patch("ctypes.windll.user32.GetForegroundWindow", return_value=12345):
            def mock_get_class(hwnd, buf, max_len):
                buf.value = "POEC2_MainWindow"
                return len(buf.value)

            with patch("ctypes.windll.user32.GetClassNameW", side_effect=mock_get_class):
                assert is_poe2_window_focused() is True

    def test_focus_returns_false_when_foreground_is_desktop_or_other_app(self):
        """Khi Foreground Window là ứng dụng khác (Chrome, Notepad, Desktop) -> is_poe2_window_focused = False."""
        with patch("ctypes.windll.user32.GetForegroundWindow", return_value=99999):
            def mock_get_class(hwnd, buf, max_len):
                buf.value = "Chrome_WidgetWin_1"
                return len(buf.value)

            def mock_get_text(hwnd, buf, max_len):
                buf.value = "Google Chrome - Work Tab"
                return len(buf.value)

            with patch("ctypes.windll.user32.GetClassNameW", side_effect=mock_get_class):
                with patch("ctypes.windll.user32.GetWindowTextW", side_effect=mock_get_text):
                    with patch("src.common.win32_window.find_poe2_window", return_value=12345):
                        assert is_poe2_window_focused() is False

    def test_focus_returns_false_when_chrome_has_poe2_tab_title(self):
        """Bảo vệ bất biến INV-FOCUS-INTERLOCK: Trình duyệt mở tab 'Path of Exile 2' phải bị từ chối 100%."""
        with patch("ctypes.windll.user32.GetForegroundWindow", return_value=88888):
            def mock_get_class(hwnd, buf, max_len):
                buf.value = "Chrome_WidgetWin_1"
                return len(buf.value)

            def mock_get_text(hwnd, buf, max_len):
                buf.value = "Path of Exile 2 - Atlas Tree Guide - Google Chrome"
                return len(buf.value)

            with patch("ctypes.windll.user32.GetClassNameW", side_effect=mock_get_class):
                with patch("ctypes.windll.user32.GetWindowTextW", side_effect=mock_get_text):
                    with patch("src.common.win32_window.find_poe2_window", return_value=0):
                        with patch("src.common.win32_window.is_poe2_pid", return_value=False):
                            assert is_poe2_window_focused() is False

    def test_focus_with_explicit_hwnd(self):
        """Khi chỉ định HWND cụ thể, chỉ True nếu Foreground khớp chính xác HWND đó."""
        with patch("ctypes.windll.user32.GetForegroundWindow", return_value=12345):
            assert is_poe2_window_focused(hwnd=12345) is True
            assert is_poe2_window_focused(hwnd=54321) is False


class TestInputFocusInterlockGuards:
    """Kiểm thử chốt chặn Focus Interlock trong các hàm win32_input."""

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=False)
    @patch("src.common.win32_input._user32.keybd_event")
    def test_send_wasd_direction_blocked_when_unfocused(self, mock_keybd, mock_focus):
        """Khi game unfocused, send_wasd_direction TỪ CHỐI gửi phím và cưỡng chế nhả phím."""
        res = send_wasd_direction(1.0, 1.0, dwell_ms=0.35)
        assert res is False
        # mock_keybd chỉ được gọi cho việc nhả phím (KEYEVENTF_KEYUP = 2), không được bấm xuống (flag 0)
        # Kiểm tra không có bất kỳ lệnh nhấn xuống nào (flags == 0)
        down_calls = [c for c in mock_keybd.call_args_list if len(c[0]) >= 3 and c[0][2] == 0]
        assert len(down_calls) == 0, f"Phát hiện gọi keybd_event nhấn xuống khi unfocused: {down_calls}"

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=True)
    @patch("src.common.win32_input._user32.keybd_event")
    @patch("src.common.win32_input._user32.SendInput")
    def test_send_wasd_direction_allowed_when_focused(self, mock_send_input, mock_keybd, mock_focus):
        """Khi game focused, send_wasd_direction hoạt động bình thường."""
        with patch("time.sleep"):
            res = send_wasd_direction(0.0, 1.0, dwell_ms=0.35)
            assert res is True
            # Phải có lệnh nhấn xuống
            down_calls = [c for c in mock_keybd.call_args_list if len(c[0]) >= 3 and c[0][2] == 0]
            assert len(down_calls) > 0

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=False)
    @patch("src.common.win32_input._user32.keybd_event")
    def test_press_key_blocked_when_unfocused(self, mock_keybd, mock_focus):
        """Khi game unfocused, press_key từ chối gọi keybd_event."""
        res = press_key(0x20)  # VK_SPACE
        assert res is False
        mock_keybd.assert_not_called()

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=True)
    @patch("src.common.win32_input._user32.keybd_event")
    @patch("src.common.win32_input._user32.SendInput")
    def test_press_key_allowed_when_focused(self, mock_send_input, mock_keybd, mock_focus):
        """Khi game focused, press_key gọi keybd_event bình thường."""
        with patch("time.sleep"):
            res = press_key(0x20)
            assert res is True
            assert mock_keybd.call_count == 2  # Down & Up

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=False)
    @patch("src.common.win32_input._user32.mouse_event")
    def test_click_blocked_when_unfocused(self, mock_mouse, mock_focus):
        """Khi game unfocused, click từ chối gọi mouse_event."""
        res = click(500, 500)
        assert res is False
        mock_mouse.assert_not_called()

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=True)
    @patch("src.common.win32_input.set_cursor_pos", return_value=True)
    @patch("src.common.win32_input._user32.mouse_event")
    def test_click_allowed_when_focused(self, mock_mouse, mock_cursor, mock_focus):
        """Khi game focused, click hoạt động bình thường."""
        with patch("time.sleep"):
            res = click(500, 500)
            assert res is True
            assert mock_mouse.call_count == 2  # Down & Up

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=False)
    @patch("src.common.win32_input._user32.mouse_event")
    def test_drag_blocked_when_unfocused(self, mock_mouse, mock_focus):
        """Khi game unfocused, drag từ chối thực hiện."""
        res = drag(100, 100, 200, 200)
        assert res is False
        mock_mouse.assert_not_called()

    @patch("src.common.win32_input.is_poe2_window_focused", return_value=False)
    @patch("src.common.win32_input._user32.keybd_event")
    def test_ctrl_click_blocked_when_unfocused(self, mock_keybd, mock_focus):
        """Khi game unfocused, ctrl_click không nhấn phím Ctrl và không click."""
        res = ctrl_click(100, 100)
        assert res is False
        mock_keybd.assert_not_called()

    def test_simulation_mode_override(self):
        """Kiểm tra set_input_simulation_mode cho phép override focus trong headless tests,
        nhưng TUYỆT ĐỐI KHÔNG GỬI PHÍM VẬT LÝ RA OS (INV-FOCUS-SAFETY-SEPARATION)."""
        set_input_simulation_mode(True)
        with patch("src.common.win32_input._user32.keybd_event") as mock_keybd:
            with patch("src.common.win32_input._user32.SendInput") as mock_send_input:
                with patch("time.sleep"):
                    res = press_key(0x20)
                    assert res is True
                    # BẤT BIẾN SINH TỬ: Không bao giờ gọi keybd_event hay SendInput thật khi simulation mode!
                    assert mock_keybd.call_count == 0
                    assert mock_send_input.call_count == 0

        set_input_simulation_mode(False)
        with patch("src.common.win32_input._user32.keybd_event") as mock_keybd:
            with patch("src.common.win32_input._user32.SendInput") as mock_send_input:
                res = press_key(0x20)
                assert res is False
                assert mock_keybd.call_count == 0
                assert mock_send_input.call_count == 0

    def test_send_wasd_direction_in_simulation_mode_zero_hardware_leak(self):
        """Khi ở chế độ mô phỏng, send_wasd_direction trả về True nhưng không gọi SendInput hay keybd_event."""
        set_input_simulation_mode(True)
        with patch("src.common.win32_input._user32.keybd_event") as mock_keybd:
            with patch("src.common.win32_input._user32.SendInput") as mock_send_input:
                with patch("time.sleep"):
                    res = send_wasd_direction(0.0, 1.0, dwell_ms=0.35)
                    assert res is True
                    assert mock_keybd.call_count == 0
                    assert mock_send_input.call_count == 0

    def test_send_wasd_direction_aborts_mid_dwell_on_focus_loss(self):
        """Nếu game mất focus giữa chừng (user alt-tab), send_wasd_direction ngắt ngay và xả phím."""
        focus_states = [True, True, False]
        def mock_focus_generator(*args, **kwargs):
            if focus_states:
                return focus_states.pop(0)
            return False

        with patch("src.common.win32_input.is_poe2_window_focused", side_effect=mock_focus_generator):
            with patch("src.common.win32_input._user32.keybd_event"):
                with patch("src.common.win32_input._user32.SendInput"):
                    with patch("src.common.win32_input.emergency_release_wasd_keys") as mock_release:
                        with patch("time.sleep"):
                            res = send_wasd_direction(1.0, 1.0, dwell_ms=0.5)
                            assert res is True
                            assert mock_release.call_count >= 1


class TestCoordinatorFocusInterlock:
    """Kiểm thử Focus Interlock trên AutonomousMappingCoordinator."""

    @patch("src.common.is_poe2_window_focused", return_value=False)
    @patch("src.common.emergency_release_wasd_keys")
    @patch("src.common.send_wasd_direction")
    def test_coordinator_send_wasd_blocked_when_unfocused(self, mock_wasd, mock_release, mock_focus):
        """Coordinator từ chối gửi WASD khi mất focus."""
        from tools.autonomous_mapping_coordinator import AutonomousMappingCoordinator

        coordinator = AutonomousMappingCoordinator()
        res = coordinator.send_wasd_direction(1.0, 0.0)

        assert res is False
        mock_wasd.assert_not_called()
        mock_release.assert_called_once()
