"""Module quản lý màn hình ảo phần cứng (Virtual Display Manager) cho AutoPOE2.

Hỗ trợ tự động nhận diện màn hình ảo IddCx phụ (Monitor 2), dời cửa sổ game
PathOfExile.exe sang không gian làm việc độc lập và chụp ảnh luồng thời gian thực
thông qua GDI BitBlt an toàn tài nguyên (Rule 5 & Rule 16).
"""

import ctypes
import io
import os
from ctypes import wintypes
from dataclasses import dataclass
from typing import List, Optional, Tuple

from PIL import Image

from src.common.screen_capture import capture_rect_bitblt
from src.common.win32_window import attach_to_default_desktop

# Win32 Constants
SWP_NOSIZE = 0x0001
SWP_NOMOVE = 0x0002
SWP_NOZORDER = 0x0004
SWP_FRAMECHANGED = 0x0020
SWP_SHOWWINDOW = 0x0040
SWP_NOACTIVATE = 0x0010
SW_RESTORE = 9
SW_SHOWNOACTIVATE = 4

GWL_STYLE = -16
WS_CAPTION = 0x00C00000
WS_THICKFRAME = 0x00040000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000
WS_POPUP = 0x80000000
WS_VISIBLE = 0x10000000


@dataclass(frozen=True)
class MonitorInfo:
    """Thông tin định tuyến và tọa độ hình học của một màn hình Win32."""
    device: str
    left: int
    top: int
    width: int
    height: int
    is_primary: bool

    @property
    def right(self) -> int:
        return self.left + self.width

    @property
    def bottom(self) -> int:
        return self.top + self.height

    def contains_point(self, x: int, y: int) -> bool:
        return self.left <= x < self.right and self.top <= y < self.bottom


class _RECT(ctypes.Structure):
    _fields_ = [
        ("left", wintypes.LONG),
        ("top", wintypes.LONG),
        ("right", wintypes.LONG),
        ("bottom", wintypes.LONG),
    ]


class _MONITORINFOEXW(ctypes.Structure):
    _fields_ = [
        ("cbSize", wintypes.DWORD),
        ("rcMonitor", _RECT),
        ("rcWork", _RECT),
        ("dwFlags", wintypes.DWORD),
        ("szDevice", wintypes.WCHAR * 32),
    ]


def get_all_monitors() -> List[MonitorInfo]:
    """Liệt kê toàn bộ các màn hình hiển thị đang kết nối với hệ điều hành.

    Returns:
        List[MonitorInfo]: Danh sách các màn hình kèm tọa độ và cờ Primary.
    """
    if os.name != "nt":
        return []

    try:
        attach_to_default_desktop()
    except Exception:
        pass

    user32 = ctypes.windll.user32
    monitors: List[MonitorInfo] = []

    def _enum_proc(h_monitor, _hdc, _lprc, _lparam):
        info = _MONITORINFOEXW()
        info.cbSize = ctypes.sizeof(_MONITORINFOEXW)
        if user32.GetMonitorInfoW(ctypes.c_void_p(h_monitor), ctypes.byref(info)):
            r = info.rcMonitor
            is_primary = bool(info.dwFlags & 1)
            monitors.append(
                MonitorInfo(
                    device=info.szDevice,
                    left=int(r.left),
                    top=int(r.top),
                    width=int(r.right - r.left),
                    height=int(r.bottom - r.top),
                    is_primary=is_primary,
                )
            )
        return True

    cb_type = ctypes.WINFUNCTYPE(
        ctypes.c_bool,
        ctypes.c_void_p,
        ctypes.c_void_p,
        ctypes.POINTER(_RECT),
        wintypes.LPARAM,
    )
    user32.EnumDisplayMonitors(None, None, cb_type(_enum_proc), 0)
    return monitors


def get_virtual_monitor() -> Optional[MonitorInfo]:
    """Tìm màn hình ảo phụ (non-primary monitor) để chạy game độc lập.

    Ưu tiên tìm màn hình không phải primary có chiều rộng >= 1280.
    Nếu có nhiều màn hình phụ, chọn màn hình có tọa độ left >= 1920.

    Returns:
        Optional[MonitorInfo]: Màn hình ảo nếu tồn tại, ngược lại None.
    """
    monitors = get_all_monitors()
    non_primaries = [m for m in monitors if not m.is_primary]
    if not non_primaries:
        return None

    # Ưu tiên màn hình đặt bên phải màn hình chính (left > 0)
    for m in non_primaries:
        if m.left > 0 and m.width >= 1280:
            return m

    return non_primaries[0]


def is_virtual_display_active() -> bool:
    """Kiểm tra xem hệ thống có màn hình ảo phụ đang hoạt động hay không."""
    return get_virtual_monitor() is not None


def move_window_to_virtual_display(
    hwnd: int,
    monitor: Optional[MonitorInfo] = None,
    borderless: bool = False,
    keep_size: bool = False,
) -> bool:
    """Di chuyển và căn chỉnh cửa sổ Win32 sang màn hình ảo phụ.

    Args:
        hwnd: Handle cửa sổ cần di chuyển.
        monitor: Thông tin màn hình đích (nếu None sẽ tự động tìm).
        borderless: Nếu True, loại bỏ viền và thanh tiêu đề thành Borderless Fullscreen. Mặc định False để bảo vệ DirectX 12 swapchain.
        keep_size: Nếu True, giữ nguyên kích thước hiện tại và chỉ dịch chuyển tọa độ (SWP_NOSIZE), tránh kích hoạt ResizeBuffers trong DirectX.

    Returns:
        bool: True nếu di chuyển thành công, ngược lại False.
    """
    if os.name != "nt" or not hwnd:
        return False

    target_monitor = monitor or get_virtual_monitor()
    if not target_monitor:
        return False

    user32 = ctypes.windll.user32
    if not user32.IsWindow(hwnd):
        return False

    # 1. Khôi phục kích thước bình thường nếu cửa sổ đang bị thu nhỏ (Iconic)
    if user32.IsIconic(hwnd):
        user32.ShowWindow(hwnd, SW_RESTORE)

    # 2. Xử lý borderless nếu yêu cầu
    if borderless:
        style = user32.GetWindowLongW(hwnd, GWL_STYLE)
        style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
        style |= WS_POPUP | WS_VISIBLE
        user32.SetWindowLongW(hwnd, GWL_STYLE, style)
        flags = SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE
        target_w = target_monitor.width
        target_h = target_monitor.height
    elif keep_size:
        flags = SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE
        target_w = 0
        target_h = 0
    else:
        flags = SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOACTIVATE
        target_w = target_monitor.width
        target_h = target_monitor.height

    # 3. Đặt vị trí cửa sổ vừa khít với màn hình ảo mà không cướp focus của người dùng (SWP_NOACTIVATE)
    success = bool(
        user32.SetWindowPos(
            hwnd,
            0,
            target_monitor.left,
            target_monitor.top,
            target_w,
            target_h,
            flags,
        )
    )
    return success


def get_window_bounds(hwnd: int) -> Optional[Tuple[int, int, int, int]]:
    """Lấy tọa độ tuyệt đối của cửa sổ (left, top, right, bottom).

    Args:
        hwnd: Handle cửa sổ Win32.

    Returns:
        Optional[Tuple[int, int, int, int]]: Bộ tứ (left, top, right, bottom) hoặc None.
    """
    if os.name != "nt" or not hwnd:
        return None

    user32 = ctypes.windll.user32
    if not user32.IsWindow(hwnd):
        return None

    rect = _RECT()
    if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
        return None

    return (int(rect.left), int(rect.top), int(rect.right), int(rect.bottom))


def is_window_on_virtual_display(
    hwnd: int,
    monitor: Optional[MonitorInfo] = None,
) -> bool:
    """Xác thực xem cửa sổ Win32 có đang nằm trong phạm vi màn hình ảo hay không.

    Args:
        hwnd: Handle cửa sổ cần kiểm tra.
        monitor: Thông tin màn hình đích (nếu None sẽ tự động tìm).

    Returns:
        bool: True nếu tâm của cửa sổ nằm trong màn hình ảo, ngược lại False.
    """
    if os.name != "nt" or not hwnd:
        return False

    target_monitor = monitor or get_virtual_monitor()
    if not target_monitor:
        return False

    bounds = get_window_bounds(hwnd)
    if not bounds:
        return False

    center_x = (bounds[0] + bounds[2]) // 2
    center_y = (bounds[1] + bounds[3]) // 2
    return target_monitor.contains_point(center_x, center_y)


def capture_virtual_display(
    monitor: Optional[MonitorInfo] = None,
) -> Optional[Image.Image]:
    """Chụp khung hình toàn màn hình ảo thông qua GDI BitBlt / PrintWindow an toàn tài nguyên.

    Ưu tiên chụp trực tiếp buffer DirectX của cửa sổ game nếu game đang hiển thị
    trên màn hình ảo (tránh hiện tượng BitBlt desktop bị DirectFlip bypass).
    Fallback sang chụp toàn bộ rect màn hình ảo nếu không có game.

    Args:
        monitor: Màn hình ảo cần chụp (nếu None sẽ tự động tìm).

    Returns:
        Optional[Image.Image]: Ảnh chụp PIL RGB nếu thành công, ngược lại None.
    """
    target_monitor = monitor or get_virtual_monitor()
    if not target_monitor:
        return None

    # 1. Nếu cửa sổ game đang hiển thị trên màn hình ảo, ưu tiên chụp trực tiếp từ DWM swapchain
    try:
        from src.common.win32_window import find_poe2_window
        from src.common.screen_capture import capture_window_bitblt
        game_hwnd = find_poe2_window()
        if game_hwnd and is_window_on_virtual_display(game_hwnd, target_monitor):
            img_game = capture_window_bitblt(game_hwnd, client_only=False)
            if img_game is not None:
                return img_game
    except Exception:
        pass

    # 2. Fallback sang chụp toàn bộ rect của màn hình ảo
    return capture_rect_bitblt(
        target_monitor.left,
        target_monitor.top,
        target_monitor.width,
        target_monitor.height,
    )


def capture_virtual_display_jpeg(
    monitor: Optional[MonitorInfo] = None,
    quality: int = 75,
) -> Optional[bytes]:
    """Chụp ảnh màn hình ảo và nén sang định dạng JPEG phục vụ live web stream.

    Args:
        monitor: Màn hình ảo cần chụp (nếu None sẽ tự động tìm).
        quality: Chất lượng nén JPEG (1-100), mặc định 75 tối ưu băng thông.

    Returns:
        Optional[bytes]: Mảng bytes JPEG nếu chụp và nén thành công, ngược lại None.
    """
    img = capture_virtual_display(monitor)
    if not img:
        return None

    buf = io.BytesIO()
    img.save(buf, format="JPEG", quality=quality)
    return buf.getvalue()
