"""
Windows native window management for xbot-form.

This module provides classes for creating and managing Win32 windows
with support for frameless windows, rounded corners, and DPI awareness.
"""

import ctypes
import os
import time
import warnings
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional, Tuple

import pywintypes
import win32api
import win32con
import win32gui

from ._internal.logging import debug_log
from .exceptions import WindowError, _InternalCancelSignal
from .resources import get_resource_path

_MODULE = "window"

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


@dataclass
class WindowConfig:
    """Configuration for window creation."""

    title: str = "XBot Form"
    width: int = 1000
    height: int = 800
    frameless: bool = True
    corner_radius: int = 32


class NativeWindow:
    """
    Native Win32 window wrapper.

    This class encapsulates all window state and provides methods for
    window management without relying on global variables.

    Attributes:
        hwnd: The window handle (HWND).
        config: The window configuration.
    """

    # Class-level registry to map HWND to window instances
    _instances: Dict[int, "NativeWindow"] = {}
    _APP_USER_MODEL_ID = "xbot-form"

    def __init__(self, config: Optional[WindowConfig] = None):
        """
        Initialize a new NativeWindow.

        Args:
            config: Window configuration. Uses defaults if not provided.
        """
        self.config = config or WindowConfig()
        self.hwnd: Optional[int] = None

        # Instance state (no longer global)
        self._close_guard: bool = True
        self._resize_callback: Optional[Callable[[int, int], None]] = None
        self._use_dwm_corners: bool = False
        self._big_icon: Optional[Any] = None
        self._small_icon: Optional[Any] = None

    @classmethod
    def _get_instance(cls, hwnd: int) -> Optional["NativeWindow"]:
        """Get the NativeWindow instance for a given HWND."""
        return cls._instances.get(hwnd)

    @classmethod
    def _register_instance(cls, hwnd: int, instance: "NativeWindow") -> None:
        """Register a NativeWindow instance for a given HWND."""
        cls._instances[hwnd] = instance

    @classmethod
    def _unregister_instance(cls, hwnd: int) -> None:
        """Unregister a NativeWindow instance."""
        cls._instances.pop(hwnd, None)

    def set_close_guard(self, enabled: bool) -> None:
        """
        Enable or disable the close guard.

        When enabled, WM_CLOSE messages are ignored. This prevents
        accidental window closure before the WebView is ready.

        Args:
            enabled: True to enable the guard, False to disable.
        """
        self._close_guard = enabled

    def set_resize_callback(
        self, callback: Optional[Callable[[int, int], None]]
    ) -> None:
        """
        Set a callback function for window resize events.

        Args:
            callback: Function that takes (width, height) as arguments,
                     or None to remove the callback.
        """
        self._resize_callback = callback

    def _load_app_icon(self, hinst) -> Tuple[Optional[Any], Optional[Any]]:
        """Load the application icon."""
        icon_path = get_resource_path("app.ico")
        if not os.path.exists(icon_path):
            debug_log(f"App icon not found at: {icon_path}", _MODULE)
            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}", _MODULE)
            return None, None

    def _set_app_user_model_id(self) -> None:
        """Set the application user model ID for taskbar grouping."""
        try:
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                self._APP_USER_MODEL_ID
            )
            debug_log(f"Set AppUserModelID: {self._APP_USER_MODEL_ID}", _MODULE)
        except Exception as exc:
            debug_log(f"Failed to set AppUserModelID: {exc!r}", _MODULE)

    def _apply_dwm_rounded_corners(self) -> bool:
        """
        Apply smooth rounded corners using Windows 11 DWM API.

        Returns:
            True if DWM corners were successfully applied, False otherwise.
        """
        if not self.hwnd or self.config.corner_radius <= 0:
            return False

        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
            corner_style = (
                DWMWCP_ROUNDSMALL if self.config.corner_radius <= 8 else DWMWCP_ROUND
            )
            value = ctypes.c_int(corner_style)

            result = dwmapi.DwmSetWindowAttribute(
                self.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})",
                    _MODULE,
                )
                return True
            else:
                debug_log(f"DwmSetWindowAttribute returned {result}", _MODULE)
                return False
        except Exception as exc:
            debug_log(f"DWM rounded corners not available: {exc!r}", _MODULE)
            return False

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

        Args:
            width: Window width.
            height: Window height.
        """
        if not self.hwnd or self.config.corner_radius <= 0:
            return

        try:
            hrgn = win32gui.CreateRoundRectRgn(
                0,
                0,
                width + 1,
                height + 1,
                self.config.corner_radius,
                self.config.corner_radius,
            )
            win32gui.SetWindowRgn(self.hwnd, hrgn, True)
            debug_log(
                f"Applied region rounded corners: radius={self.config.corner_radius}",
                _MODULE,
            )
        except Exception as exc:
            debug_log(f"Failed to apply region rounded corners: {exc!r}", _MODULE)

    def _wnd_proc(self, hwnd: int, msg: int, wparam: int, lparam: int) -> int:
        """
        Window procedure to handle messages.

        This is the instance method that handles window messages.
        """
        if msg == win32con.WM_CLOSE:
            debug_log("WndProc received WM_CLOSE", _MODULE)
            if self._close_guard:
                debug_log("Close guard active; ignoring WM_CLOSE", _MODULE)
                return 0
            # Signal cancellation instead of hard_exit
            raise _InternalCancelSignal("Window closed by user")

        if msg == win32con.WM_SIZE:
            width = lparam & 0xFFFF
            height = (lparam >> 16) & 0xFFFF
            debug_log(f"WndProc received WM_SIZE: {width}x{height}", _MODULE)

            # Update rounded corners on resize (only for region method)
            if self.config.corner_radius > 0 and not self._use_dwm_corners:
                self._apply_region_rounded_corners(width, height)

            if self._resize_callback is not None:
                try:
                    self._resize_callback(width, height)
                except Exception as exc:
                    debug_log(f"Resize callback failed: {exc!r}", _MODULE)
            return 0

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

    def create(self) -> int:
        """
        Create the native Win32 window.

        Returns:
            The window handle (HWND).

        Raises:
            WindowError: If window creation fails.
        """
        self._set_app_user_model_id()
        class_name = f"WebViewFormWindow_{id(self)}"
        hinst = win32api.GetModuleHandle(None)

        # Define window class
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = self._create_wnd_proc_wrapper()
        wc.lpszClassName = class_name
        wc.hInstance = hinst
        wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
        wc.hbrBackground = win32con.COLOR_WINDOW + 1

        self._big_icon, self._small_icon = self._load_app_icon(hinst)
        if self._big_icon:
            setattr(wc, "hIcon", self._big_icon)

        # Register window class
        try:
            win32gui.RegisterClass(wc)
        except pywintypes.error as e:
            # Error code 1410 = ERROR_CLASS_ALREADY_EXISTS, which is acceptable
            if e.winerror != 1410:
                debug_log(f"RegisterClass failed: {e}", _MODULE)
                raise WindowError(f"Failed to register window class: {e}")

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

        # Determine window style
        if self.config.frameless:
            style = win32con.WS_POPUP
        else:
            style = win32con.WS_OVERLAPPEDWINDOW
            style &= ~win32con.WS_MAXIMIZEBOX
            style &= ~win32con.WS_MINIMIZEBOX
            style &= ~win32con.WS_SYSMENU

        # Create the window (hidden initially)
        self.hwnd = win32gui.CreateWindow(
            class_name,
            self.config.title,
            style,
            x,
            y,
            self.config.width,
            self.config.height,
            0,
            0,
            hinst,
            None,
        )

        if not self.hwnd:
            raise WindowError("Failed to create window")

        # Register this instance
        NativeWindow._register_instance(self.hwnd, self)

        # Set icons
        if self._big_icon:
            win32gui.SendMessage(
                self.hwnd, win32con.WM_SETICON, win32con.ICON_BIG, self._big_icon
            )
            try:
                win32gui.SetClassLong(self.hwnd, win32con.GCL_HICON, self._big_icon)
            except Exception:
                pass
        if self._small_icon:
            win32gui.SendMessage(
                self.hwnd, win32con.WM_SETICON, win32con.ICON_SMALL, self._small_icon
            )
            try:
                win32gui.SetClassLong(self.hwnd, win32con.GCL_HICONSM, self._small_icon)
            except Exception:
                pass

        # Apply rounded corners for frameless windows
        if self.config.frameless and self.config.corner_radius > 0:
            self._use_dwm_corners = self._apply_dwm_rounded_corners()
            if not self._use_dwm_corners:
                self._apply_region_rounded_corners(
                    self.config.width, self.config.height
                )

        debug_log(f"Window created (hidden): hwnd={self.hwnd}", _MODULE)
        return self.hwnd

    def _create_wnd_proc_wrapper(self):
        """Create a window procedure wrapper that routes to this instance."""
        instance = self

        def wnd_proc_wrapper(hwnd, msg, wparam, lparam):
            try:
                return instance._wnd_proc(hwnd, msg, wparam, lparam)
            except _InternalCancelSignal:
                # Re-raise for proper handling
                raise
            except Exception as exc:
                debug_log(f"WndProc exception: {exc!r}", _MODULE)
                return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)

        return wnd_proc_wrapper

    def show(self, animate: bool = True, duration_ms: int = 150) -> None:
        """
        Show the window with optional fade-in animation.

        Args:
            animate: Whether to use fade-in animation (default True).
            duration_ms: Animation duration in milliseconds (default 150ms).
        """
        if not self.hwnd:
            return

        try:
            if animate:
                self._show_with_fade(duration_ms)
            else:
                win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
                win32gui.UpdateWindow(self.hwnd)
                win32gui.SetForegroundWindow(self.hwnd)
            debug_log("Window shown", _MODULE)
        except Exception as exc:
            debug_log(f"Failed to show window: {exc!r}", _MODULE)
            # Fallback: show window without animation
            try:
                win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
                win32gui.SetForegroundWindow(self.hwnd)
            except Exception:
                pass

    def _show_with_fade(self, duration_ms: int) -> None:
        """Show window with a smooth fade-in animation."""
        user32 = ctypes.windll.user32

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

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

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

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

        for i in range(1, steps + 1):
            # Use ease-out cubic for smoother animation
            t = i / steps
            eased = 1 - (1 - t) ** 3
            alpha = int(255 * eased)
            user32.SetLayeredWindowAttributes(self.hwnd, 0, alpha, LWA_ALPHA)
            time.sleep(step_delay)

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

    def destroy(self) -> None:
        """Destroy the window and clean up resources."""
        if self.hwnd:
            NativeWindow._unregister_instance(self.hwnd)
            try:
                win32gui.DestroyWindow(self.hwnd)
            except Exception:
                pass
            self.hwnd = None

    def start_drag(self) -> None:
        """
        Start dragging the window (for frameless windows).

        This simulates clicking on the title bar to allow window movement.
        """
        if not self.hwnd:
            return
        try:
            ctypes.windll.user32.ReleaseCapture()
            # WM_NCLBUTTONDOWN = 0x00A1, HTCAPTION = 2
            ctypes.windll.user32.SendMessageW(self.hwnd, 0x00A1, 2, 0)
            debug_log("Window drag started", _MODULE)
        except Exception as exc:
            debug_log(f"Failed to start window drag: {exc!r}", _MODULE)


# Legacy compatibility functions (deprecated, will be removed in future versions)
# These functions maintain backward compatibility with the old global-state API

_legacy_window: Optional[NativeWindow] = None


def set_close_guard(enabled: bool) -> None:
    """
    [DEPRECATED] Set the close guard for the current window.

    Use NativeWindow.set_close_guard() instead.

    .. deprecated:: 0.2.0
        Use :meth:`NativeWindow.set_close_guard` instead.
    """
    warnings.warn(
        "set_close_guard() is deprecated, use NativeWindow.set_close_guard() instead",
        DeprecationWarning,
        stacklevel=2,
    )
    global _legacy_window
    if _legacy_window:
        _legacy_window.set_close_guard(enabled)


def set_resize_callback(callback: Optional[Callable[[int, int], None]]) -> None:
    """
    [DEPRECATED] Set a resize callback for the current window.

    Use NativeWindow.set_resize_callback() instead.

    .. deprecated:: 0.2.0
        Use :meth:`NativeWindow.set_resize_callback` instead.
    """
    warnings.warn(
        "set_resize_callback() is deprecated, use NativeWindow.set_resize_callback() instead",
        DeprecationWarning,
        stacklevel=2,
    )
    global _legacy_window
    if _legacy_window:
        _legacy_window.set_resize_callback(callback)


def start_window_drag(hwnd: int) -> None:
    """
    [DEPRECATED] Start dragging a window.

    Use NativeWindow.start_drag() instead.

    .. deprecated:: 0.2.0
        Use :meth:`NativeWindow.start_drag` instead.
    """
    warnings.warn(
        "start_window_drag() is deprecated, use NativeWindow.start_drag() instead",
        DeprecationWarning,
        stacklevel=2,
    )
    window = NativeWindow._get_instance(hwnd)
    if window:
        window.start_drag()
    else:
        # Fallback for direct hwnd usage
        try:
            ctypes.windll.user32.ReleaseCapture()
            ctypes.windll.user32.SendMessageW(hwnd, 0x00A1, 2, 0)
        except Exception:
            pass


def show_window(hwnd: int, animate: bool = True, duration_ms: int = 150) -> None:
    """
    [DEPRECATED] Show a window.

    Use NativeWindow.show() instead.

    .. deprecated:: 0.2.0
        Use :meth:`NativeWindow.show` instead.
    """
    warnings.warn(
        "show_window() is deprecated, use NativeWindow.show() instead",
        DeprecationWarning,
        stacklevel=2,
    )
    window = NativeWindow._get_instance(hwnd)
    if window:
        window.show(animate, duration_ms)


def create_window(
    title: str,
    width: int,
    height: int,
    frameless: bool = False,
    corner_radius: int = 0,
) -> int:
    """
    [DEPRECATED] Create a native Win32 window.

    Use NativeWindow class instead.

    .. deprecated:: 0.2.0
        Use :class:`NativeWindow` instead.
    """
    warnings.warn(
        "create_window() is deprecated, use NativeWindow class instead",
        DeprecationWarning,
        stacklevel=2,
    )
    global _legacy_window
    config = WindowConfig(
        title=title,
        width=width,
        height=height,
        frameless=frameless,
        corner_radius=corner_radius,
    )
    _legacy_window = NativeWindow(config)
    return _legacy_window.create()
