"""AnswerDisplay — AI-powered stealth answer overlay.

Workflow:
  1. Press hotkey (default: Ctrl+F9)
  2. Screenshot taken of the current screen
  3. Vision AI identifies the correct answer (A/B/C/D)
  4. Answer letter displayed in bottom-right corner overlay
  5. Overlay is topmost (above taskbar) and invisible to screen sharing

Stealth:
  - Windows: SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE) — invisible to
    CSCA/SEB screen capture, OBS, Discord, Zoom, etc.
  - macOS: NSWindowLevel 1100 (above SEB kiosk).
  - Overlay auto-hides after a configurable timeout.

Platform support:
  - Windows: PyQt6 + ctypes stealth
  - macOS:   PyQt6 (stealth via NSPanel level)
"""

from __future__ import annotations

import logging
import sys
import threading
from typing import TYPE_CHECKING

from .vision_pipeline import (
    ANSWER_PROMPT,
    analyze_screenshot,
    beep_result,
    beep_start,
    capture_screenshot,
    extract_answer,
)

if TYPE_CHECKING:
    from ..config import Config

log = logging.getLogger(__name__)


class AnswerDisplay:
    """Screenshot -> Vision AI -> stealth overlay with correct answer letter."""

    def __init__(self, config: Config) -> None:
        self._config = config
        self._running = False
        self._lock = threading.Lock()
        self._overlay: AnswerOverlayWidget | None = None

    def set_overlay(self, overlay: AnswerOverlayWidget) -> None:
        """Set the overlay widget (must be created in the main/Qt thread)."""
        self._overlay = overlay

    # ------------------------------------------------------------------ #
    # Public entry point
    # ------------------------------------------------------------------ #

    def on_hotkey(self) -> None:
        """Called when hotkey is pressed."""
        # If overlay is visible — just hide it
        if self._overlay and self._overlay.is_visible():
            self._overlay.hide_signal.emit()
            log.info("[answer] Hiding overlay")
            return

        with self._lock:
            if self._running:
                log.info("[answer] AI request in progress — please wait")
                return
            self._running = True
        log.info("[answer] Starting answer display...")
        threading.Thread(target=self._run, daemon=True, name="answer-display").start()

    # ------------------------------------------------------------------ #
    # Main pipeline
    # ------------------------------------------------------------------ #

    def _run(self) -> None:
        try:
            # Step 1: screenshot
            img_b64, orig_w, orig_h, res_w, res_h = capture_screenshot(
                self._config, "answer_display",
            )
            if not img_b64:
                log.error("[answer] Screenshot failed — aborting")
                return

            # Step 2: Vision AI
            result = analyze_screenshot(
                self._config, img_b64, ANSWER_PROMPT,
                config_prefix="answer_display",
                retries=4,
            )
            if result is None:
                log.error("[answer] Vision AI failed — aborting")
                return

            answer = extract_answer(result)

            if not answer:
                log.info("[answer] No question found on screen")
                return

            log.info("[answer] Answer: %s", answer)

            # Step 3: show overlay via signal (thread-safe)
            timeout = int(self._config.get("answer_display.timeout_sec", 5))
            if self._overlay:
                self._overlay.show_signal.emit(answer, timeout)
            else:
                log.error("[answer] No overlay widget — cannot show answer")

        except Exception as exc:
            log.error("[answer] Unexpected error: %s", exc, exc_info=True)
        finally:
            with self._lock:
                self._running = False


# ===================================================================== #
# Overlay widget — MUST be created in main/Qt thread
# ===================================================================== #


class AnswerOverlayWidget:
    """Stealth overlay — shows single answer letter in bottom-right corner.

    - Black text, 50% transparent, large font (~10rem / 160px)
    - On top of all windows including taskbar
    - Invisible in screen sharing (SetWindowDisplayAffinity)
    - Thread-safe: show/hide via Qt signals
    """

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

        # Signals for thread-safe communication
        class _Signals(QObject):
            show = pyqtSignal(str, int)  # answer_letter, timeout_sec
            hide = pyqtSignal()

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

        # Create widget
        self._widget = QWidget()
        self._widget.setObjectName("answer_overlay")
        self._widget.setWindowFlags(
            Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
            | Qt.WindowType.Tool
            | Qt.WindowType.WindowDoesNotAcceptFocus
            | Qt.WindowType.BypassWindowManagerHint
        )
        self._widget.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
        # Use solid dark background instead of WA_TranslucentBackground
        # (TranslucentBackground conflicts with SetWindowDisplayAffinity
        # on Windows — makes window fully invisible to user too)
        self._widget.setStyleSheet(
            "QWidget#answer_overlay { background-color: rgba(30, 30, 30, 200); "
            "border-radius: 12px; }"
        )

        # Layout
        layout = QVBoxLayout(self._widget)
        layout.setContentsMargins(0, 0, 0, 0)

        # Answer label — large grey semi-transparent letter, no background
        self._label = QLabel()
        self._label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        font = QFont("Arial", 120, QFont.Weight.Bold)
        self._label.setFont(font)
        # Grey color, 50% opacity
        self._label.setStyleSheet("color: rgba(128, 128, 128, 128); background: transparent;")
        layout.addWidget(self._label)

        # Auto-hide timer
        self._hide_timer = QTimer(self._widget)
        self._hide_timer.setSingleShot(True)
        self._hide_timer.timeout.connect(self._do_hide)

        # Z-order maintenance timer (every 50ms — matches NormalizeAllTopMosts speed)
        self._topmost_timer = QTimer(self._widget)
        self._topmost_timer.setInterval(50)
        self._topmost_timer.timeout.connect(self._assert_topmost)

        # Foreground hook state (Win32 — instant z-order re-assertion)
        self._foreground_hook_installed = False
        self._foreground_callback = None  # prevent GC of ctypes callback

        # Connect signals to slots
        self.show_signal.connect(self._do_show)
        self.hide_signal.connect(self._do_hide)

    def is_visible(self) -> bool:
        """Check if overlay is currently visible (thread-safe read)."""
        return self._widget.isVisible()

    # Win32 stealth constants
    _WDA_EXCLUDEFROMCAPTURE = 0x00000011
    _HWND_TOPMOST = -1
    _SWP_NOMOVE = 0x0002
    _SWP_NOSIZE = 0x0001
    _SWP_NOACTIVATE = 0x0010
    _GWL_EXSTYLE = -20
    _WS_EX_TOOLWINDOW = 0x00000080
    _WS_EX_NOACTIVATE = 0x08000000
    _WS_EX_LAYERED = 0x00080000

    def _do_show(self, answer: str, timeout_sec: int) -> None:
        """Show the answer letter overlay."""
        self._label.setText(answer)
        self._widget.adjustSize()

        # Position: bottom-right corner of FULL screen (not available — covers taskbar)
        self._position_bottom_right()

        self._widget.show()
        self._widget.raise_()

        # Re-assert topmost (stealth applied inside)
        self._assert_topmost()

        # Start timers
        self._topmost_timer.start()
        if timeout_sec > 0:
            self._hide_timer.start(timeout_sec * 1000)

        # Install foreground hook for instant z-order re-assertion
        self._install_foreground_hook()
        self._register_cbt_hook()

        log.info("[answer] Overlay shown: %s", answer)

    def _do_hide(self) -> None:
        """Hide the overlay."""
        self._hide_timer.stop()
        self._topmost_timer.stop()
        self._widget.hide()
        log.info("[answer] Overlay hidden")

    def _position_bottom_right(self) -> None:
        """Place widget in bottom-right corner — over taskbar."""
        from PyQt6.QtWidgets import QApplication

        screen = QApplication.primaryScreen()
        if not screen:
            return
        # Use geometry() (full screen) not availableGeometry() (excludes taskbar)
        geom = screen.geometry()
        margin = 20
        x = geom.right() - self._widget.width() - margin
        y = geom.bottom() - self._widget.height() - margin
        self._widget.move(x, y)

    def _apply_stealth(self) -> None:
        """Apply screen-capture exclusion (Windows only)."""
        if sys.platform != "win32":
            return
        try:
            import ctypes
            hwnd = int(self._widget.winId())
            user32 = ctypes.windll.user32  # type: ignore[attr-defined]
            # Exclude from screen capture
            user32.SetWindowDisplayAffinity(hwnd, self._WDA_EXCLUDEFROMCAPTURE)
            # Hide from Alt+Tab + taskbar + no focus steal
            ex_style = (self._WS_EX_TOOLWINDOW | self._WS_EX_NOACTIVATE
                        | self._WS_EX_LAYERED)
            user32.SetWindowLongW(hwnd, self._GWL_EXSTYLE, ex_style)
        except (OSError, TypeError, ValueError, AttributeError):
            pass

    def _register_cbt_hook(self) -> None:
        """Register this window with the CBT hook for CSCA Z-order protection."""
        if sys.platform != "win32":
            return
        try:
            from ..seb.window_cloak import register_protected_hwnd

            hwnd = int(self._widget.winId())
            register_protected_hwnd(hwnd)
        except Exception:
            pass

    def _install_foreground_hook(self) -> None:
        """Install EVENT_SYSTEM_FOREGROUND hook for instant z-order re-assertion."""
        if self._foreground_hook_installed or sys.platform != "win32":
            return
        try:
            import ctypes
            from ctypes import wintypes

            # WinEventProc callback type
            WINEVENTPROC = ctypes.WINFUNCTYPE(
                None,             # return void
                wintypes.HANDLE,  # hWinEventHook
                wintypes.DWORD,   # event
                wintypes.HWND,    # hwnd
                ctypes.c_long,    # idObject
                ctypes.c_long,    # idChild
                wintypes.DWORD,   # idEventThread
                wintypes.DWORD,   # dwmsEventTime
            )

            widget_ref = self._widget  # prevent GC

            def _on_foreground(
                hWinEventHook, event, hwnd, idObject, idChild,
                idEventThread, dwmsEventTime,
            ):
                if widget_ref.isVisible():
                    try:
                        my_hwnd = int(widget_ref.winId())
                        ctypes.windll.user32.SetWindowPos(
                            my_hwnd, -1, 0, 0, 0, 0,
                            0x0002 | 0x0001 | 0x0010,  # NOMOVE|NOSIZE|NOACTIVATE
                        )
                    except Exception:
                        pass

            self._foreground_callback = WINEVENTPROC(_on_foreground)

            EVENT_SYSTEM_FOREGROUND = 0x0003
            WINEVENT_OUTOFCONTEXT = 0x0000
            WINEVENT_SKIPOWNPROCESS = 0x0002

            hook = ctypes.windll.user32.SetWinEventHook(
                EVENT_SYSTEM_FOREGROUND,  # eventMin
                EVENT_SYSTEM_FOREGROUND,  # eventMax
                None,                      # hmodWinEventProc
                self._foreground_callback,
                0,                         # idProcess (all processes)
                0,                         # idThread (all threads)
                WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
            )
            if hook:
                self._foreground_hook_installed = True
                log.debug("[answer] Foreground hook installed")
        except Exception:
            pass

    def _assert_topmost(self) -> None:
        """Re-assert topmost z-order and stealth."""
        if not self._widget.isVisible():
            return
        if sys.platform == "win32":
            try:
                import ctypes
                hwnd = int(self._widget.winId())
                user32 = ctypes.windll.user32  # type: ignore[attr-defined]
                user32.SetWindowPos(
                    hwnd, self._HWND_TOPMOST, 0, 0, 0, 0,
                    self._SWP_NOMOVE | self._SWP_NOSIZE | self._SWP_NOACTIVATE,
                )
                # Exclude from CSCA screen capture (WDA_EXCLUDEFROMCAPTURE)
                user32.SetWindowDisplayAffinity(hwnd, self._WDA_EXCLUDEFROMCAPTURE)
                # Hide from Alt+Tab + taskbar
                ex_style = (self._WS_EX_TOOLWINDOW | self._WS_EX_NOACTIVATE
                            | self._WS_EX_LAYERED)
                user32.SetWindowLongW(hwnd, self._GWL_EXSTYLE, ex_style)
                # Clear window title — harder to find via FindWindow/EnumWindows
                user32.SetWindowTextW(hwnd, "")
            except (OSError, TypeError, ValueError, AttributeError):
                pass
        elif sys.platform == "darwin":
            self._assert_topmost_macos()

    def _assert_topmost_macos(self) -> None:
        """Set NSWindowLevel to 1100 — above SEB kiosk (~1000)."""
        try:
            import ctypes
            import ctypes.util

            lib_path = ctypes.util.find_library("objc")
            if not lib_path:
                return
            objc = ctypes.cdll.LoadLibrary(lib_path)
            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)
            sel_window = objc.sel_registerName(b"window")
            nswindow = objc.objc_msgSend(nsview, sel_window)
            if nswindow:
                sel_set_level = objc.sel_registerName(b"setLevel:")
                objc.objc_msgSend.argtypes = [
                    ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long,
                ]
                objc.objc_msgSend(nswindow, sel_set_level, 1100)
                objc.objc_msgSend.argtypes = [
                    ctypes.c_void_p, ctypes.c_void_p,
                ]
        except Exception:
            pass
