"""Windows Graphics Capture engine (Windows 10+, WGC API)."""

import gc
import sys
import threading
from typing import Any, Dict, List, Optional


from wled_controller.core.capture_engines.base import (
    CaptureEngine,
    CaptureStream,
    DisplayInfo,
    ScreenCapture,
)
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class WGCCaptureStream(CaptureStream):
    """WGC capture stream for a specific display."""

    def __init__(self, display_index: int, config: Dict[str, Any]):
        super().__init__(display_index, config)
        self._wgc = None
        self._capture_instance = None
        self._capture_control = None
        self._latest_frame = None
        self._frame_event = threading.Event()
        self._closed_event = threading.Event()
        self._frame_lock = threading.Lock()

    def initialize(self) -> None:
        if self._wgc is None:
            try:
                import windows_capture
                self._wgc = windows_capture
            except ImportError:
                raise RuntimeError(
                    "windows-capture not installed. Install with: pip install windows-capture"
                )

        try:
            capture_cursor = self.config.get("capture_cursor", False)
            draw_border = self.config.get("draw_border", None)

            # WGC uses 1-based monitor indexing
            wgc_monitor_index = self.display_index + 1

            # draw_border toggling requires Windows 11 22H2+ (build 22621+).
            # On older builds, passing any value crashes the capture session,
            # so we only pass it when the platform supports it.
            wgc_kwargs = dict(
                cursor_capture=capture_cursor,
                monitor_index=wgc_monitor_index,
            )
            if draw_border is not None and self._supports_border_toggle():
                wgc_kwargs["draw_border"] = draw_border

            self._capture_instance = self._wgc.WindowsCapture(**wgc_kwargs)

            def on_frame_arrived(frame, capture_control):
                try:
                    frame_buffer = frame.frame_buffer
                    width = frame.width
                    height = frame.height

                    # WGC provides BGRA format, convert to RGB
                    # Fancy indexing creates a new contiguous array — no .copy() needed
                    frame_array = frame_buffer.reshape((height, width, 4))
                    frame_rgb = frame_array[:, :, [2, 1, 0]]

                    with self._frame_lock:
                        self._latest_frame = frame_rgb
                        self._frame_event.set()
                except Exception as e:
                    logger.error(f"Error processing WGC frame: {e}", exc_info=True)

            def on_closed():
                logger.debug(f"WGC capture session closed for display {self.display_index}")
                self._closed_event.set()

            self._capture_instance.frame_handler = on_frame_arrived
            self._capture_instance.closed_handler = on_closed

            logger.debug(f"Starting WGC capture for display {self.display_index} (free-threaded mode)...")
            self._capture_control = self._capture_instance.start_free_threaded()

            # Wait for first frame
            logger.debug(f"Waiting for first WGC frame from display {self.display_index}...")
            frame_received = self._frame_event.wait(timeout=5.0)

            if not frame_received or self._latest_frame is None:
                self._cleanup_internal()
                raise RuntimeError(
                    f"WGC capture started for display {self.display_index} but no frames received within 5 seconds."
                )

            self._initialized = True
            logger.info(
                f"WGC capture stream initialized (display={self.display_index}, "
                f"cursor={capture_cursor})"
            )

        except RuntimeError:
            raise
        except Exception as e:
            logger.error(f"Failed to initialize WGC for display {self.display_index}: {e}", exc_info=True)
            raise RuntimeError(f"Failed to initialize WGC for display {self.display_index}: {e}")

    @staticmethod
    def _supports_border_toggle() -> bool:
        """Check if the platform supports WGC border toggle (Windows 11 22H2+, build 22621+)."""
        try:
            import platform
            build = int(platform.version().split(".")[2])
            return build >= 22621
        except Exception as e:
            logger.debug("Failed to detect WGC border toggle support: %s", e)
            return False

    def _cleanup_internal(self) -> None:
        """Internal cleanup helper."""
        if self._capture_control:
            try:
                logger.debug(f"Stopping WGC capture thread for display {self.display_index}...")
                self._capture_control.stop()
                self._capture_control.wait()
                logger.debug(f"WGC capture thread finished for display {self.display_index}")
            except Exception as e:
                logger.error(f"Error during WGC capture control cleanup: {e}", exc_info=True)
            self._capture_control = None

        if self._capture_instance:
            try:
                del self._capture_instance
            except Exception as e:
                logger.debug("WGC capture instance cleanup: %s", e)
                pass
            self._capture_instance = None

        self._frame_event.clear()
        self._closed_event.clear()
        self._latest_frame = None

    def cleanup(self) -> None:
        self._cleanup_internal()
        self._initialized = False

        # Force garbage collection to release COM objects
        gc.collect()
        logger.info(f"WGC capture stream cleaned up (display={self.display_index})")

    def capture_frame(self) -> Optional[ScreenCapture]:
        if not self._initialized:
            self.initialize()

        try:
            # Only return a frame when the callback has delivered a new one
            if not self._frame_event.is_set():
                return None

            with self._frame_lock:
                if self._latest_frame is None:
                    return None
                frame = self._latest_frame
                self._frame_event.clear()

            logger.debug(
                f"WGC captured display {self.display_index}: "
                f"{frame.shape[1]}x{frame.shape[0]}"
            )

            return ScreenCapture(
                image=frame,
                width=frame.shape[1],
                height=frame.shape[0],
                display_index=self.display_index,
            )

        except RuntimeError:
            raise
        except Exception as e:
            logger.error(f"Failed to capture display {self.display_index} with WGC: {e}")
            raise RuntimeError(f"Screen capture failed: {e}")


class WGCEngine(CaptureEngine):
    """Windows Graphics Capture engine.

    Uses the windows-capture library which leverages Windows.Graphics.Capture API.
    This is Microsoft's recommended modern screen capture API for Windows 10+.

    Features:
    - Cross-GPU support
    - Hardware cursor exclusion
    - GPU-accelerated with direct texture sharing

    Requires: Windows 10 1803+
    """

    ENGINE_TYPE = "wgc"
    ENGINE_PRIORITY = 2

    @classmethod
    def is_available(cls) -> bool:
        if sys.platform != "win32":
            return False

        try:
            import platform
            version = platform.version()
            parts = version.split(".")
            if len(parts) >= 3:
                major = int(parts[0])
                minor = int(parts[1])
                build = int(parts[2])
                if major < 10 or (major == 10 and minor == 0 and build < 17134):
                    return False
        except Exception as e:
            logger.debug("Failed to check Windows version for WGC availability: %s", e)
            pass

        try:
            import importlib.util
            return importlib.util.find_spec("windows_capture") is not None
        except (ImportError, ModuleNotFoundError, ValueError):
            return False

    @classmethod
    def get_default_config(cls) -> Dict[str, Any]:
        return {
            "capture_cursor": False,
            "draw_border": None,  # None = OS default; False = hide border (Win 11 22H2+ only)
        }

    @classmethod
    def get_available_displays(cls) -> List[DisplayInfo]:
        try:
            import mss

            with mss.mss() as sct:
                displays = []
                for i, monitor in enumerate(sct.monitors[1:], start=0):
                    displays.append(DisplayInfo(
                        index=i,
                        name=f"Monitor {i+1}",
                        width=monitor["width"],
                        height=monitor["height"],
                        x=monitor["left"],
                        y=monitor["top"],
                        is_primary=(i == 0),
                        refresh_rate=60,
                    ))

            logger.debug(f"WGC detected {len(displays)} display(s)")
            return displays

        except Exception as e:
            logger.error(f"Failed to detect displays: {e}")
            raise RuntimeError(f"Failed to detect displays: {e}")

    @classmethod
    def create_stream(cls, display_index: int, config: Dict[str, Any]) -> WGCCaptureStream:
        return WGCCaptureStream(display_index, config)
