"""ScreenshotViewer — real-time screenshot window floating above ALL windows.

Workflow:
  1. Press hotkey (default: Ctrl+F4) — window appears with live screenshot
  2. Press hotkey again — window hides
  3. "🔄 Refresh" button — takes new screenshot manually
  4. Auto-refresh every N seconds (screenshot_viewer.refresh_interval_sec, default 3)

The window:
  - Floats ABOVE all windows including CSCA/SEB
  - Is HIDDEN from CSCA's own screenshot capture (WDA_EXCLUDEFROMCAPTURE)
  - Uses the same z-order techniques as AnswerWindow (Ctrl+F3)

Platform support:
  - Windows: PyQt6 + ctypes (HWND_TOPMOST + WDA_EXCLUDEFROMCAPTURE)
  - macOS:   PyQt6 + NSWindowLevel 1100 (above SEB kiosk)
"""

from __future__ import annotations

import logging
import sys
import threading
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..config import Config

log = logging.getLogger(__name__)


class ScreenshotViewer:
    """Controller: hotkey toggle → capture screenshot → show/hide widget."""

    def __init__(self, config: Config) -> None:
        self._config = config
        self._widget: ScreenshotViewerWidget | None = None

    def set_widget(self, widget: ScreenshotViewerWidget) -> None:
        """Set the window widget (must be created in the main/Qt thread)."""
        self._widget = widget

    def on_hotkey(self) -> None:
        """Called when hotkey is pressed — toggle show/hide.

        BUG FIX: on_hotkey() is invoked from the hotkey monitor thread, NOT
        the Qt main thread.  Calling self._widget.is_visible() (which calls
        QWidget.isVisible()) from a non-Qt thread is undefined behaviour and
        can cause crashes.  We use a thread-safe visibility flag maintained
        by the widget itself instead of querying Qt directly.
        """
        if self._widget and self._widget.is_visible_flag:
            self._widget.hide_signal.emit()
            log.info("[screenshot-viewer] Hiding")
            return

        log.info("[screenshot-viewer] Showing")
        if self._widget:
            self._widget.show_signal.emit()


# ===================================================================== #
# Widget — MUST be created in main/Qt thread
# ===================================================================== #


class ScreenshotViewerWidget:
    """Floating window showing a live screenshot above all windows.

    - Takes screenshot via mss on show + manual refresh + auto-refresh timer
    - Hidden from CSCA capture (WDA_EXCLUDEFROMCAPTURE)
    - Stays above CSCA via HWND_TOPMOST + 50ms reassert + foreground hook
    - Thread-safe: all UI operations via Qt signals
    """

    def __init__(self, config: Config) -> None:
        from PyQt6.QtCore import QObject, QTimer, Qt, pyqtSignal
        from PyQt6.QtWidgets import (
            QApplication, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget,
        )
        from PyQt6.QtGui import QFont

        self._config = config

        # ── Signals ────────────────────────────────────────────────────
        class _Signals(QObject):
            show = pyqtSignal()
            hide = pyqtSignal()
            update = pyqtSignal(object, str)   # (PIL Image | None, info_text)

        self._signals = _Signals()
        self.show_signal = self._signals.show
        self.hide_signal = self._signals.hide
        self._update_signal = self._signals.update

        # ── Window ─────────────────────────────────────────────────────
        self._widget = QWidget()
        self._widget.setObjectName("screenshot_viewer")
        self._widget.setWindowTitle("")
        self._widget.setWindowFlags(
            Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
            | Qt.WindowType.Tool
            | Qt.WindowType.WindowDoesNotAcceptFocus
            | Qt.WindowType.BypassWindowManagerHint
        )
        self._widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        self._widget.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
        self._widget.setStyleSheet(_STYLE)
        self._widget.resize(720, 520)

        # ── Layout ─────────────────────────────────────────────────────
        root = QVBoxLayout(self._widget)
        root.setContentsMargins(8, 8, 8, 8)
        root.setSpacing(4)

        # Header row
        hdr = QHBoxLayout()
        title = QLabel("📸 Screenshot Viewer")
        title.setObjectName("sv_title")
        hdr.addWidget(title)
        hdr.addStretch()
        self._info = QLabel("Capturing...")
        self._info.setObjectName("sv_info")
        self._info.setWordWrap(False)
        hdr.addWidget(self._info)
        close_btn = QPushButton("✕")
        close_btn.setObjectName("sv_close")
        close_btn.setFixedWidth(28)
        close_btn.clicked.connect(self._do_hide)
        hdr.addWidget(close_btn)
        root.addLayout(hdr)

        # Screenshot area
        self._shot = QLabel("Taking screenshot...")
        self._shot.setObjectName("sv_shot")
        self._shot.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self._shot.setMinimumSize(680, 420)
        root.addWidget(self._shot)

        # Button row
        btn_row = QHBoxLayout()
        refresh_btn = QPushButton("🔄 Refresh")
        refresh_btn.setObjectName("sv_refresh")
        refresh_btn.clicked.connect(self._capture_async)
        btn_row.addWidget(refresh_btn)
        btn_row.addStretch()
        root.addLayout(btn_row)

        # ── Timers ─────────────────────────────────────────────────────
        # Re-assert topmost every 50ms
        self._topmost_timer = QTimer(self._widget)
        self._topmost_timer.setInterval(50)
        self._topmost_timer.timeout.connect(self._assert_topmost)

        # Auto-refresh timer
        interval_sec = int(config.get("screenshot_viewer.refresh_interval_sec", 3))
        self._auto_refresh_timer = QTimer(self._widget)
        self._auto_refresh_timer.setInterval(interval_sec * 1000)
        self._auto_refresh_timer.timeout.connect(self._capture_async)

        # Foreground hook state
        self._foreground_hook_installed = False
        self._foreground_callback = None  # prevent GC
        self._foreground_hook_handle = None  # Win32 HWINEVENTHOOK handle

        # BUG FIX: thread-safe visibility flag so on_hotkey() (called from a
        # non-Qt thread) can check visibility without calling Qt APIs.
        # Written only from Qt thread (inside _do_show / _do_hide signals).
        self.is_visible_flag: bool = False

        # ── Connect signals ─────────────────────────────────────────────
        self.show_signal.connect(self._do_show)
        self.hide_signal.connect(self._do_hide)
        self._update_signal.connect(self._apply_frame)

    def is_visible(self) -> bool:
        """Qt-thread-safe check via flag (not via QWidget.isVisible())."""
        return self.is_visible_flag

    # ── Windows constants ───────────────────────────────────────────────
    _HWND_TOPMOST = -1
    _SWP_NOMOVE = 0x0002
    _SWP_NOSIZE = 0x0001
    _SWP_NOACTIVATE = 0x0010
    _SWP_SHOWWINDOW = 0x0040
    _GWL_EXSTYLE = -20
    _WS_EX_TOOLWINDOW = 0x00000080
    _WS_EX_NOACTIVATE = 0x08000000
    _WS_EX_TOPMOST = 0x00000008
    _WS_EX_LAYERED = 0x00080000
    _WDA_EXCLUDEFROMCAPTURE = 0x00000011

    # ── Show / Hide ────────────────────────────────────────────────────

    def _do_show(self) -> None:
        from PyQt6.QtWidgets import QApplication

        screen = QApplication.primaryScreen()
        if screen:
            geom = screen.geometry()
            self._widget.resize(720, 520)
            x = geom.center().x() - self._widget.width() // 2
            y = geom.center().y() - self._widget.height() // 2
            self._widget.move(x, y)

        self._widget.show()
        self._widget.raise_()
        self.is_visible_flag = True  # update flag before any signal that checks it
        self._assert_topmost()
        self._topmost_timer.start()
        self._install_foreground_hook()
        self._register_cbt_hook()
        self._auto_refresh_timer.start()

        # First capture
        self._capture_async()
        log.info("[screenshot-viewer] Window shown")

    def _do_hide(self) -> None:
        self._topmost_timer.stop()
        self._auto_refresh_timer.stop()
        self._widget.hide()
        self.is_visible_flag = False
        # BUG FIX: unhook the WinEvent foreground hook so the handle is not
        # leaked and the callback is not called after the window is hidden.
        # Also reset the installed flag so the hook is reinstalled on next show.
        self._uninstall_foreground_hook()
        log.info("[screenshot-viewer] Window hidden")

    # ── Capture ────────────────────────────────────────────────────────

    def _capture_async(self) -> None:
        threading.Thread(target=self._do_capture, daemon=True, name="sv-capture").start()

    def _do_capture(self) -> None:
        from datetime import datetime
        ts = datetime.now().strftime("%H:%M:%S")
        try:
            import mss
            from PIL import Image

            with mss.mss() as sct:
                monitors = sct.monitors
                mon = monitors[1] if len(monitors) > 1 else monitors[0]
                raw = sct.grab(mon)
                img = Image.frombytes("RGBA", raw.size, bytes(raw.bgra), "raw", "BGRA")
                img = img.convert("RGB")

            # Crop 15% from top and 15% from left
            w, h = img.size
            crop_left = int(w * 0.15)
            crop_top = int(h * 0.15)
            img = img.crop((crop_left, crop_top, w, h))

            # Resize to optimal for models (max 1024px)
            w, h = img.size
            max_size = 1024
            if max(w, h) > max_size:
                ratio = max_size / max(w, h)
                img = img.resize((int(w * ratio), int(h * ratio)), Image.LANCZOS)

            info = f"{ts}  {img.width}×{img.height} (cropped 15%)"
            self._update_signal.emit(img, info)

        except Exception as exc:
            log.error("[screenshot-viewer] Capture error: %s", exc)
            self._update_signal.emit(None, f"{ts}  ERROR: {exc}")

    def _apply_frame(self, img, info: str) -> None:
        """Qt thread: display captured image."""
        from PyQt6.QtCore import Qt
        from PyQt6.QtGui import QImage, QPixmap

        self._info.setText(info)

        if img is None:
            self._shot.setText("❌ Capture failed")
            return

        data = img.tobytes("raw", "RGB")
        qimg = QImage(data, img.width, img.height, img.width * 3, QImage.Format.Format_RGB888)
        pm = QPixmap.fromImage(qimg).scaled(
            self._shot.width(), self._shot.height(),
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation,
        )
        self._shot.setPixmap(pm)

    # ── Z-order ────────────────────────────────────────────────────────

    def _assert_topmost(self) -> None:
        if not self._widget.isVisible():
            return
        if sys.platform == "win32":
            self._assert_topmost_win32()
        elif sys.platform == "darwin":
            self._assert_topmost_macos()

    def _assert_topmost_win32(self) -> None:
        try:
            import ctypes
            hwnd = int(self._widget.winId())
            u = ctypes.windll.user32
            u.SetWindowPos(
                hwnd, self._HWND_TOPMOST, 0, 0, 0, 0,
                self._SWP_NOMOVE | self._SWP_NOSIZE
                | self._SWP_NOACTIVATE | self._SWP_SHOWWINDOW,
            )
            # Hide from CSCA's own screen capture
            u.SetWindowDisplayAffinity(hwnd, self._WDA_EXCLUDEFROMCAPTURE)
            ex = (
                self._WS_EX_TOOLWINDOW | self._WS_EX_NOACTIVATE
                | self._WS_EX_TOPMOST | self._WS_EX_LAYERED
            )
            u.SetWindowLongW(hwnd, self._GWL_EXSTYLE, ex)
            u.SetWindowTextW(hwnd, "")
        except (OSError, TypeError, ValueError, AttributeError):
            pass

    def _assert_topmost_macos(self) -> None:
        try:
            import ctypes
            import ctypes.util
            objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc"))
            objc.objc_getClass.restype = ctypes.c_void_p
            objc.sel_registerName.restype = ctypes.c_void_p
            objc.objc_msgSend.restype = ctypes.c_void_p
            objc.objc_msgSend.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
            win_id = int(self._widget.winId())
            nsview = ctypes.c_void_p(win_id)
            nswindow = objc.objc_msgSend(nsview, objc.sel_registerName(b"window"))
            if nswindow:
                objc.objc_msgSend.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long]
                objc.objc_msgSend(nswindow, objc.sel_registerName(b"setLevel:"), 1100)
        except Exception:
            pass

    def _register_cbt_hook(self) -> None:
        if sys.platform != "win32":
            return
        try:
            from ..seb.window_cloak import register_protected_hwnd
            register_protected_hwnd(int(self._widget.winId()))
        except Exception:
            pass

    def _install_foreground_hook(self) -> None:
        if self._foreground_hook_installed or sys.platform != "win32":
            return
        try:
            import ctypes
            from ctypes import wintypes

            WINEVENTPROC = ctypes.WINFUNCTYPE(
                None, wintypes.HANDLE, wintypes.DWORD, wintypes.HWND,
                ctypes.c_long, ctypes.c_long, wintypes.DWORD, wintypes.DWORD,
            )
            widget_ref = self._widget

            def _on_fg(hook, event, hwnd, idObj, idChild, idThread, time):
                if widget_ref.isVisible():
                    try:
                        ctypes.windll.user32.SetWindowPos(
                            int(widget_ref.winId()), -1, 0, 0, 0, 0,
                            0x0002 | 0x0001 | 0x0010,
                        )
                    except Exception:
                        pass

            self._foreground_callback = WINEVENTPROC(_on_fg)
            hook = ctypes.windll.user32.SetWinEventHook(
                0x0003, 0x0003, None, self._foreground_callback,
                0, 0, 0x0000 | 0x0002,
            )
            if hook:
                # BUG FIX: store the hook handle so we can call UnhookWinEvent
                # when the window is hidden.  Previously the handle was
                # discarded and the hook was never unregistered (handle leak).
                self._foreground_hook_handle = hook
                self._foreground_hook_installed = True
        except Exception:
            pass

    def _uninstall_foreground_hook(self) -> None:
        """BUG FIX: release the WinEvent hook and reset the installed flag.

        Called from _do_hide() so the hook is properly cleaned up every time
        the window is hidden, and will be reinstalled on the next show.
        """
        if not self._foreground_hook_installed or sys.platform != "win32":
            return
        try:
            import ctypes
            if self._foreground_hook_handle:
                ctypes.windll.user32.UnhookWinEvent(self._foreground_hook_handle)
                self._foreground_hook_handle = None
        except Exception:
            pass
        finally:
            self._foreground_hook_installed = False
            self._foreground_callback = None  # release the WINFUNCTYPE object


_STYLE = """
QWidget#screenshot_viewer {
    background-color: rgba(12, 12, 12, 248);
    border: 2px solid #2266cc;
    border-radius: 8px;
}
QLabel#sv_title {
    color: #4488ff;
    font-size: 13px;
    font-weight: bold;
    padding: 2px 6px;
}
QLabel#sv_info {
    color: #888888;
    font-size: 10px;
    padding: 2px 4px;
}
QLabel#sv_shot {
    background-color: #0a0a0a;
    border: 1px solid #222;
}
QPushButton {
    background-color: #252525;
    color: #cccccc;
    border: 1px solid #444;
    border-radius: 4px;
    padding: 3px 10px;
    font-size: 11px;
}
QPushButton:hover { background-color: #333; }
QPushButton#sv_refresh {
    background-color: #0d2e0d;
    color: #66dd66;
    border-color: #1a5c1a;
}
QPushButton#sv_refresh:hover { background-color: #143a14; }
QPushButton#sv_close {
    background-color: #2e0d0d;
    color: #dd6666;
    border-color: #5c1a1a;
}
"""
