"""Thư viện dùng chung chụp màn hình an toàn tài nguyên GDI cho AutoPOE2.

Module này cung cấp các engine chụp màn hình hiệu năng cao dựa trên Win32 GDI BitBlt
và PrintWindow API. Tuân thủ nghiêm ngặt nguyên tắc Zero Resource Leak (Rule 5 & Rule 16):
Mọi đối tượng đồ họa Windows (DC, HBITMAP) đều BẮT BUỘC phải được giải phóng qua
DeleteDC, DeleteObject và ReleaseDC trong các khối finally.
"""

import ctypes
import ctypes.wintypes
import os
from typing import Optional, Tuple

from PIL import Image

SRCCOPY = 0x00CC0020
PW_RENDERFULLCONTENT = 0x00000002
BI_RGB = 0


class _BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ("biSize", ctypes.wintypes.DWORD),
        ("biWidth", ctypes.wintypes.LONG),
        ("biHeight", ctypes.wintypes.LONG),
        ("biPlanes", ctypes.wintypes.WORD),
        ("biBitCount", ctypes.wintypes.WORD),
        ("biCompression", ctypes.wintypes.DWORD),
        ("biSizeImage", ctypes.wintypes.DWORD),
        ("biXPelsPerMeter", ctypes.wintypes.LONG),
        ("biYPelsPerMeter", ctypes.wintypes.LONG),
        ("biClrUsed", ctypes.wintypes.DWORD),
        ("biClrImportant", ctypes.wintypes.DWORD),
    ]


def capture_rect_bitblt(
    left: int, top: int, width: int, height: int
) -> Optional[Image.Image]:
    """Chụp vùng chữ nhật xác định trên màn hình thông qua GDI BitBlt siêu tốc.

    Đảm bảo 100% tài nguyên GDI (Device Context, Compatible Bitmap) được dọn dẹp
    triệt để trong khối finally ngay cả khi xảy ra ngoại lệ không mong muốn.

    Args:
        left: Tọa độ X góc trên bên trái theo pixel màn hình.
        top: Tọa độ Y góc trên bên trái theo pixel màn hình.
        width: Chiều rộng vùng chụp (pixel). Phải lớn hơn 0.
        height: Chiều cao vùng chụp (pixel). Phải lớn hơn 0.

    Returns:
        Optional[Image.Image]: Đối tượng PIL Image RGB nếu thành công, hoặc None nếu thất bại.

    Thread-safety:
        Thread-safe.

    Example:
        >>> img = capture_rect_bitblt(0, 0, 1920, 1080)
        >>> if img:
        ...     img.save("fullscreen.png")
    """
    if os.name != "nt" or width <= 0 or height <= 0:
        return None

    try:
        from src.common.win32_window import attach_to_default_desktop
        attach_to_default_desktop()
    except Exception:
        pass

    user32 = ctypes.windll.user32
    gdi32 = ctypes.windll.gdi32

    desk_dc = None
    mem_dc = None
    bmp = None
    old_bmp = None

    try:
        desk_dc = user32.GetDC(0)
        if not desk_dc:
            return None

        mem_dc = gdi32.CreateCompatibleDC(desk_dc)
        if not mem_dc:
            return None

        bmp = gdi32.CreateCompatibleBitmap(desk_dc, width, height)
        if not bmp:
            return None

        old_bmp = gdi32.SelectObject(mem_dc, bmp)
        if not old_bmp:
            return None

        # BitBlt từ Desktop DC sang In-Memory DC
        if not gdi32.BitBlt(mem_dc, 0, 0, width, height, desk_dc, left, top, SRCCOPY):
            return None

        bmi = _BITMAPINFOHEADER()
        bmi.biSize = ctypes.sizeof(_BITMAPINFOHEADER)
        bmi.biWidth = width
        bmi.biHeight = -height  # Top-down bitmap
        bmi.biPlanes = 1
        bmi.biBitCount = 32
        bmi.biCompression = BI_RGB

        buf = ctypes.create_string_buffer(width * height * 4)
        lines = gdi32.GetDIBits(mem_dc, bmp, 0, height, buf, ctypes.byref(bmi), 0)
        if lines <= 0:
            return None

        raw_img = Image.frombuffer("RGBA", (width, height), bytes(buf), "raw", "BGRA", 0, 1)
        return raw_img.convert("RGB")
    except Exception:
        return None
    finally:
        # Dọn dẹp tài nguyên GDI tuyệt đối an toàn
        if mem_dc and old_bmp:
            gdi32.SelectObject(mem_dc, old_bmp)
        if bmp:
            gdi32.DeleteObject(bmp)
        if mem_dc:
            gdi32.DeleteDC(mem_dc)
        if desk_dc:
            user32.ReleaseDC(0, desk_dc)


def capture_window_bitblt(hwnd: int, client_only: bool = True) -> Optional[Image.Image]:
    """Chụp cửa sổ game bằng phương pháp GDI BitBlt.

    Tự động xác định tọa độ vùng vẽ thực tế (Client Rect) hoặc toàn bộ khung cửa sổ
    (Window Rect) và thực hiện chụp nhanh. Đảm bảo giải phóng toàn bộ GDI handle trong finally.

    Args:
        hwnd: Handle cửa sổ Win32 cần chụp.
        client_only: Nếu True, chỉ chụp vùng hiển thị nội dung (loại trừ title bar và viền).
            Nếu False, chụp toàn bộ bounding box của cửa sổ.

    Returns:
        Optional[Image.Image]: Ảnh chụp dưới dạng PIL Image RGB, hoặc None nếu cửa sổ không hợp lệ.

    Thread-safety:
        Thread-safe.

    Example:
        >>> img = capture_window_bitblt(poe_hwnd, client_only=True)
    """
    if os.name != "nt" or not hwnd:
        return None

    try:
        from src.common.win32_window import attach_to_window_desktop
        attach_to_window_desktop(hwnd)
    except Exception:
        pass

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

    if client_only:
        rect = ctypes.wintypes.RECT()
        if not user32.GetClientRect(hwnd, ctypes.byref(rect)):
            return None
        w = rect.right - rect.left
        h = rect.bottom - rect.top
        pt = ctypes.wintypes.POINT(0, 0)
        if not user32.ClientToScreen(hwnd, ctypes.byref(pt)):
            return None
        left, top = pt.x, pt.y
    else:
        rect = ctypes.wintypes.RECT()
        if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
            return None
        w = rect.right - rect.left
        h = rect.bottom - rect.top
        left, top = rect.left, rect.top

    if w <= 0 or h <= 0:
        return None

    # 1. Ưu tiên capture_window_print để lấy trực tiếp buffer DirectX từ DWM, chống bị che khuất bởi các cửa sổ khác
    try:
        img_print = capture_window_print(hwnd, render_full_content=True)
        if img_print is not None:
            pw, ph = img_print.size
            if client_only and (pw > w or ph > h):
                bx = max(0, (pw - w) // 2)
                by = max(0, ph - h - bx)
                return img_print.crop((bx, by, bx + w, by + h))
            return img_print
    except Exception:
        pass

    # 2. Fallback sang capture_rect_bitblt từ Desktop DC
    return capture_rect_bitblt(left, top, w, h)


def capture_window_print(
    hwnd: int, render_full_content: bool = True
) -> Optional[Image.Image]:
    """Chụp cửa sổ game thông qua Win32 PrintWindow API.

    Phương pháp này hỗ trợ lấy ảnh ngay cả khi cửa sổ bị che khuất bởi các cửa sổ khác
    nhờ cờ PW_RENDERFULLCONTENT (0x02). Toàn bộ DC và Bitmap đều được giải phóng an toàn
    trong khối finally.

    Args:
        hwnd: Handle cửa sổ Win32 cần chụp.
        render_full_content: Nếu True, sử dụng cờ PW_RENDERFULLCONTENT để DWM render đầy đủ nội dung.

    Returns:
        Optional[Image.Image]: Đối tượng PIL Image RGB, hoặc None nếu thất bại.

    Thread-safety:
        Thread-safe.

    Example:
        >>> img = capture_window_print(poe_hwnd)
    """
    if os.name != "nt" or not hwnd:
        return None

    try:
        from src.common.win32_window import attach_to_window_desktop
        attach_to_window_desktop(hwnd)
    except Exception:
        pass

    user32 = ctypes.windll.user32
    gdi32 = ctypes.windll.gdi32

    if not user32.IsWindow(hwnd):
        return None

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

    w = rect.right - rect.left
    h = rect.bottom - rect.top
    if w <= 0 or h <= 0:
        return None

    desk_dc = None
    mem_dc = None
    bmp = None
    old_bmp = None

    try:
        desk_dc = user32.GetDC(0)
        if not desk_dc:
            return None

        mem_dc = gdi32.CreateCompatibleDC(desk_dc)
        if not mem_dc:
            return None

        bmp = gdi32.CreateCompatibleBitmap(desk_dc, w, h)
        if not bmp:
            return None

        old_bmp = gdi32.SelectObject(mem_dc, bmp)
        if not old_bmp:
            return None

        flags = PW_RENDERFULLCONTENT if render_full_content else 0
        success = user32.PrintWindow(hwnd, mem_dc, flags)
        if not success and render_full_content:
            # Thử lại với cờ mặc định nếu render_full_content không được hỗ trợ
            success = user32.PrintWindow(hwnd, mem_dc, 0)

        if not success:
            return None

        bmi = _BITMAPINFOHEADER()
        bmi.biSize = ctypes.sizeof(_BITMAPINFOHEADER)
        bmi.biWidth = w
        bmi.biHeight = -h
        bmi.biPlanes = 1
        bmi.biBitCount = 32
        bmi.biCompression = BI_RGB

        buf = ctypes.create_string_buffer(w * h * 4)
        lines = gdi32.GetDIBits(mem_dc, bmp, 0, h, buf, ctypes.byref(bmi), 0)
        if lines <= 0:
            return None

        raw_img = Image.frombuffer("RGBA", (w, h), bytes(buf), "raw", "BGRA", 0, 1)
        return raw_img.convert("RGB")
    except Exception:
        return None
    finally:
        if mem_dc and old_bmp:
            gdi32.SelectObject(mem_dc, old_bmp)
        if bmp:
            gdi32.DeleteObject(bmp)
        if mem_dc:
            gdi32.DeleteDC(mem_dc)
        if desk_dc:
            user32.ReleaseDC(0, desk_dc)
