import ctypes
import os
import time
from typing import Any, Callable, Optional, Tuple

import win32api
import win32con
import win32gui

from .lifecycle import hard_exit
from .resources import get_resource_path

# Constants for layered window
WS_EX_LAYERED = 0x00080000
LWA_ALPHA = 0x00000002
GWL_EXSTYLE = -20

_DEBUG_ENV = "WEBVIEW_FORM_DEBUG"
_CLOSE_GUARD = True
_RESIZE_CALLBACK: Optional[Callable[[int, int], None]] = None
_CORNER_RADIUS: int = 0  # Corner radius for rounded windows
_USE_DWM_CORNERS: bool = False  # Whether DWM smooth corners are being used
_APP_USER_MODEL_ID = "xbot-form"


def _debug_log(message: str) -> None:
    if os.environ.get(_DEBUG_ENV) == "1":
        print(message, flush=True)


def _load_app_icon(hinst) -> Tuple[Optional[Any], Optional[Any]]:
    icon_path = get_resource_path("app.ico")
    if not os.path.exists(icon_path):
        _debug_log(f"App icon not found at: {icon_path}")
        return None, None
    try:
        big_icon = win32gui.LoadImage(
            hinst,
            icon_path,
            win32con.IMAGE_ICON,
            0,
            0,
            win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE,
        )
        small_icon = win32gui.LoadImage(
            hinst,
            icon_path,
            win32con.IMAGE_ICON,
            16,
            16,
            win32con.LR_LOADFROMFILE,
        )
        return big_icon, small_icon
    except Exception as exc:
        _debug_log(f"Failed to load app icon: {exc!r}")
        return None, None


def _set_app_user_model_id(app_id: str) -> None:
    try:
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
        _debug_log(f"Set AppUserModelID: {app_id}")
    except Exception as exc:
        _debug_log(f"Failed to set AppUserModelID: {exc!r}")


def set_close_guard(enabled: bool) -> None:
    global _CLOSE_GUARD
    _CLOSE_GUARD = enabled


def set_resize_callback(callback: Optional[Callable[[int, int], None]]) -> None:
    """
    Set a callback function to be called when the window is resized.

    Args:
        callback: A function that takes (width, height) as arguments.
    """
    global _RESIZE_CALLBACK
    _RESIZE_CALLBACK = callback


def start_window_drag(hwnd: int) -> None:
    """
    Start dragging the window (for frameless windows).
    This simulates clicking on the title bar to allow window movement.

    Args:
        hwnd: Window handle.
    """
    try:
        # Release mouse capture and send WM_NCLBUTTONDOWN with HTCAPTION
        # This simulates clicking the title bar to start dragging
        ctypes.windll.user32.ReleaseCapture()
        # WM_NCLBUTTONDOWN = 0x00A1, HTCAPTION = 2
        ctypes.windll.user32.SendMessageW(hwnd, 0x00A1, 2, 0)
        _debug_log("Window drag started")
    except Exception as exc:
        _debug_log(f"Failed to start window drag: {exc!r}")


def _apply_dwm_rounded_corners(hwnd: int) -> bool:
    """
    Apply smooth rounded corners using Windows 11 DWM API.
    This provides anti-aliased corners without jagged edges.

    Args:
        hwnd: Window handle.

    Returns:
        True if DWM corners were successfully applied, False otherwise.
    """
    try:
        # DWM constants for Windows 11+
        DWMWA_WINDOW_CORNER_PREFERENCE = 33
        DWMWCP_ROUND = 2  # Rounded corners
        DWMWCP_ROUNDSMALL = 3  # Small rounded corners

        dwmapi = ctypes.windll.dwmapi

        # Choose corner style based on radius
        # Use small corners for radius <= 8, regular for larger
        corner_style = DWMWCP_ROUNDSMALL if _CORNER_RADIUS <= 8 else DWMWCP_ROUND
        value = ctypes.c_int(corner_style)

        result = dwmapi.DwmSetWindowAttribute(
            hwnd,
            DWMWA_WINDOW_CORNER_PREFERENCE,
            ctypes.byref(value),
            ctypes.sizeof(value),
        )

        if result == 0:  # S_OK
            _debug_log(f"Applied DWM smooth rounded corners (style={corner_style})")
            return True
        else:
            _debug_log(f"DwmSetWindowAttribute returned {result}")
            return False
    except Exception as exc:
        _debug_log(f"DWM rounded corners not available: {exc!r}")
        return False


def _apply_region_rounded_corners(hwnd: int, width: int, height: int) -> None:
    """
    Apply rounded corners using GDI region (fallback method, has jagged edges).

    Args:
        hwnd: Window handle.
        width: Window width.
        height: Window height.
    """
    try:
        # Create a rounded rectangle region
        hrgn = win32gui.CreateRoundRectRgn(
            0, 0, width + 1, height + 1, _CORNER_RADIUS, _CORNER_RADIUS
        )
        # Set the window region (window takes ownership of the region)
        win32gui.SetWindowRgn(hwnd, hrgn, True)
        _debug_log(f"Applied region rounded corners: radius={_CORNER_RADIUS}")
    except Exception as exc:
        _debug_log(f"Failed to apply region rounded corners: {exc!r}")


def _wnd_proc(hwnd, msg, wparam, lparam):
    """
    Window procedure to handle messages.
    """
    if msg == win32con.WM_CLOSE:
        _debug_log("WndProc received WM_CLOSE")
        if _CLOSE_GUARD:
            _debug_log("Close guard active; ignoring WM_CLOSE")
            return 0
        # Requirement 4: On WM_CLOSE, directly call hard_exit().
        # - No hiding
        # - No returning (process terminates immediately)
        # - No confirmation dialogs
        hard_exit()
        return 0

    if msg == win32con.WM_SIZE:
        # Handle window resize - update WebView bounds
        width = lparam & 0xFFFF
        height = (lparam >> 16) & 0xFFFF
        _debug_log(f"WndProc received WM_SIZE: {width}x{height}")

        # Update rounded corners on resize (only needed for region method, not DWM)
        if _CORNER_RADIUS > 0 and not _USE_DWM_CORNERS:
            _apply_region_rounded_corners(hwnd, width, height)

        if _RESIZE_CALLBACK is not None:
            try:
                _RESIZE_CALLBACK(width, height)
            except Exception as exc:
                _debug_log(f"Resize callback failed: {exc!r}")
        return 0

    return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)


def show_window(hwnd: int, animate: bool = True, duration_ms: int = 150) -> None:
    """
    Show the window after it has been created with optional fade-in animation.
    This is used to delay window display until the WebView content is ready.

    Args:
        hwnd: Window handle.
        animate: Whether to use fade-in animation (default True).
        duration_ms: Animation duration in milliseconds (default 150ms).
    """
    try:
        if animate:
            _show_window_with_fade(hwnd, duration_ms)
        else:
            win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
            win32gui.UpdateWindow(hwnd)
            win32gui.SetForegroundWindow(hwnd)
        _debug_log("Window shown")
    except Exception as exc:
        _debug_log(f"Failed to show window: {exc!r}")
        # Fallback: show window without animation
        try:
            win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
            win32gui.SetForegroundWindow(hwnd)
        except Exception:
            pass


def _show_window_with_fade(hwnd: int, duration_ms: int) -> None:
    """
    Show window with a smooth fade-in animation using layered window.

    Args:
        hwnd: Window handle.
        duration_ms: Animation duration in milliseconds.
    """
    user32 = ctypes.windll.user32

    # Get current extended style and add WS_EX_LAYERED
    ex_style = user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
    user32.SetWindowLongW(hwnd, GWL_EXSTYLE, ex_style | WS_EX_LAYERED)

    # Set initial transparency to 0 (fully transparent)
    user32.SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA)

    # Show the window (still invisible due to 0 alpha)
    win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
    win32gui.SetForegroundWindow(hwnd)

    # Animate fade-in with easing
    steps = 20  # Number of animation steps
    step_delay = duration_ms / 1000.0 / steps

    for i in range(1, steps + 1):
        # Use ease-out cubic for smoother animation: 1 - (1-t)^3
        t = i / steps
        eased = 1 - (1 - t) ** 3
        alpha = int(255 * eased)

        user32.SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA)
        time.sleep(step_delay)

    # Remove WS_EX_LAYERED to restore normal rendering performance
    user32.SetWindowLongW(hwnd, GWL_EXSTYLE, ex_style)
    win32gui.UpdateWindow(hwnd)


def create_window(
    title: str, width: int, height: int, frameless: bool = False, corner_radius: int = 0
) -> int:
    """
    Creates a native Win32 window and registers its WndProc.
    The window is created hidden and should be shown later using show_window()
    after the WebView content is ready.

    Args:
        title (str): The window title.
        width (int): Window width.
        height (int): Window height.
        frameless (bool): If True, creates a window without title bar and border.
        corner_radius (int): Radius for rounded corners (0 for sharp corners).

    Returns:
        int: The handle of the created window (HWND).
    """
    global _CORNER_RADIUS
    _CORNER_RADIUS = (
        corner_radius if frameless else 0
    )  # Only apply to frameless windows

    _set_app_user_model_id(_APP_USER_MODEL_ID)
    class_name = "WebViewFormWindow"
    hinst = win32api.GetModuleHandle(None)

    # Define window class
    wc = win32gui.WNDCLASS()
    # Requirement 3: Must register WndProc
    wc.lpfnWndProc = _wnd_proc
    wc.lpszClassName = class_name
    wc.hInstance = hinst
    wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
    wc.hbrBackground = win32con.COLOR_WINDOW + 1
    big_icon, small_icon = _load_app_icon(hinst)
    if big_icon:
        setattr(wc, "hIcon", big_icon)

    # Register window class
    try:
        win32gui.RegisterClass(wc)
    except Exception:
        # Class might already be registered
        pass

    # Calculate centered position
    screen_width = win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
    screen_height = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
    x = (screen_width - width) // 2
    y = (screen_height - height) // 2

    # Requirement 1: Create a native Win32 window (hidden initially)
    if frameless:
        # Frameless window: no title bar, no border (without WS_VISIBLE)
        style = win32con.WS_POPUP
    else:
        style = win32con.WS_OVERLAPPEDWINDOW
        style &= ~win32con.WS_MAXIMIZEBOX
        style &= ~win32con.WS_MINIMIZEBOX
        style &= ~win32con.WS_SYSMENU
    hwnd = win32gui.CreateWindow(
        class_name,
        title,
        style,
        x,
        y,
        width,
        height,
        0,
        0,
        hinst,
        None,
    )

    if big_icon:
        win32gui.SendMessage(hwnd, win32con.WM_SETICON, win32con.ICON_BIG, big_icon)
        try:
            win32gui.SetClassLong(hwnd, win32con.GCL_HICON, big_icon)
        except Exception:
            pass
    if small_icon:
        win32gui.SendMessage(hwnd, win32con.WM_SETICON, win32con.ICON_SMALL, small_icon)
        try:
            win32gui.SetClassLong(hwnd, win32con.GCL_HICONSM, small_icon)
        except Exception:
            pass

    # Apply rounded corners for frameless windows
    if frameless and corner_radius > 0:
        global _USE_DWM_CORNERS
        # Try DWM first (Windows 11+, smooth anti-aliased corners)
        _USE_DWM_CORNERS = _apply_dwm_rounded_corners(hwnd)
        # Fallback to region method if DWM not available
        if not _USE_DWM_CORNERS:
            _apply_region_rounded_corners(hwnd, width, height)

    # Window is created hidden - call show_window() after content is ready
    _debug_log(f"Window created (hidden): hwnd={hwnd}")

    return hwnd
