#!/usr/bin/env python3
"""
Hotkey Manager for neo-pod-desktop
Provides configurable keyboard shortcuts.
"""

import logging
from typing import Callable, Dict, Optional

from PyQt6.QtCore import Qt, QObject
from PyQt6.QtGui import QKeySequence, QShortcut, QAction
from PyQt6.QtWidgets import QApplication, QWidget, QDialog, QVBoxLayout, QLabel, QPushButton

logger = logging.getLogger(__name__)

QSeq = QKeySequence

# Key names that QKeySequence doesn't expose as enum values
_KEY_MAP = {
    "Space": QSeq(Qt.Key.Key_Space),
    "Delete": QSeq(Qt.Key.Key_Delete),
    "Up": QSeq(Qt.Key.Key_Up),
    "Down": QSeq(Qt.Key.Key_Down),
    "Left": QSeq(Qt.Key.Key_Left),
    "Right": QSeq(Qt.Key.Key_Right),
    "Enter": QSeq(Qt.Key.Key_Enter),
    "Return": QSeq(Qt.Key.Key_Return),
    "Esc": QSeq(Qt.Key.Key_Escape),
    "Escape": QSeq(Qt.Key.Key_Escape),
    "Tab": QSeq(Qt.Key.Key_Tab),
    "Backspace": QSeq(Qt.Key.Key_Backspace),
    "Plus": QSeq(Qt.Key.Key_Plus),
    "Minus": QSeq(Qt.Key.Key_Minus),
    "F1": QSeq(Qt.Key.Key_F1),
    "F2": QSeq(Qt.Key.Key_F2),
    "F3": QSeq(Qt.Key.Key_F3),
    "F4": QSeq(Qt.Key.Key_F4),
    "F5": QSeq(Qt.Key.Key_F5),
    "F6": QSeq(Qt.Key.Key_F6),
    "F7": QSeq(Qt.Key.Key_F7),
    "F8": QSeq(Qt.Key.Key_F8),
    "F9": QSeq(Qt.Key.Key_F9),
    "F10": QSeq(Qt.Key.Key_F10),
    "F11": QSeq(Qt.Key.Key_F11),
    "F12": QSeq(Qt.Key.Key_F12),
}


def _build_seq(text: str) -> QKeySequence:
    """Build a QKeySequence from a human-readable shortcut string."""
    text = text.strip()
    if not text:
        return QSeq()
    known = _KEY_MAP.get(text)
    if known is not None:
        return known
    return QSeq(text)


def _seq_to_string(seq: QKeySequence) -> str:
    """Convert a QKeySequence to a human-readable string."""
    return seq.toString()


def _is_editable_focused() -> bool:
    """Return True if an editable widget currently has keyboard focus."""
    from PyQt6.QtWidgets import QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox
    w = QApplication.focusWidget()
    if w is None:
        return False
    return isinstance(w, (QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox))


# ---------------------------------------------------------------------------
# Default shortcut definitions
# (action_name, default_key_string, display_label)
# ---------------------------------------------------------------------------
DEFAULTS = [
    ("play_pause",        "Space",            "Play / Pause"),
    ("prev_track",        "Ctrl+Left",        "Previous Track"),
    ("next_track",        "Ctrl+Right",       "Next Track"),
    ("volume_up",         "Ctrl+Up",          "Volume Up"),
    ("volume_down",       "Ctrl+Down",        "Volume Down"),
    ("seek_forward",      "Right",            "Seek Forward 5s"),
    ("seek_backward",     "Left",             "Seek Backward 5s"),
    ("tab_library",       "Ctrl+1",           "Switch to Library"),
    ("tab_ipod",          "Ctrl+2",           "Switch to iPod"),
    ("tab_settings",      "Ctrl+3",           "Switch to Settings"),
    ("search_focus",      "Ctrl+F",           "Focus Search"),
    ("select_all",        "Ctrl+A",           "Select All"),
    ("library_refresh",   "F5",               "Refresh Library"),
    ("ipod_refresh",      "Ctrl+R",           "Refresh Devices"),
    ("delete_selected",   "Delete",           "Delete Selected"),
    ("edit_metadata",     "F2",               "Edit Metadata"),
    ("toggle_sidebar",    "Ctrl+B",           "Toggle Sidebar"),
    ("library_add_files", "Ctrl+O",           "Library: Add Files"),
]


class KeyCaptureDialog(QDialog):
    """Modal dialog that captures a single key combination."""

    def __init__(self, parent=None, current: str = ""):
        super().__init__(parent)
        self.setWindowTitle("Press New Shortcut")
        self.setMinimumWidth(320)
        self.setModal(True)

        self._captured_seq: QKeySequence = QSeq()
        self._modifiers = Qt.KeyboardModifier.NoModifier
        self._key = Qt.Key.Key_unknown

        layout = QVBoxLayout(self)
        self._label = QLabel(f"Press new shortcut for this action...\n(current: {current or 'none'})")
        self._label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self._label.setWordWrap(True)
        layout.addWidget(self._label)

        clear_btn = QPushButton("Clear (Disable)")
        clear_btn.clicked.connect(self._on_clear)
        layout.addWidget(clear_btn)

        layout.addWidget(QPushButton("Cancel", clicked=self.reject))

        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.setFocus()

    def keyPressEvent(self, event):
        key = event.key()
        modifiers = event.modifiers()
        mod_mask = modifiers & (
            Qt.KeyboardModifier.ControlModifier
            | Qt.KeyboardModifier.ShiftModifier
            | Qt.KeyboardModifier.AltModifier
            | Qt.KeyboardModifier.MetaModifier
        )

        # Ignore pure modifier presses
        ignore = {
            Qt.Key.Key_Control, Qt.Key.Key_Shift, Qt.Key.Key_Alt, Qt.Key.Key_Meta,
            Qt.Key.Key_Super_L, Qt.Key.Key_Super_R,
        }
        if key in ignore:
            return

        # Build the sequence — handle both old (int) and new (enum) PyQt6 types
        def _to_int(val):
            return val if isinstance(val, int) else val.value

        keys = _to_int(mod_mask) | _to_int(key)
        self._captured_seq = QSeq(keys)
        self._label.setText(f"Captured: {self._captured_seq.toString()}\nPress Close to accept")
        self.accept()

    def _on_clear(self):
        self._captured_seq = QSeq()
        self._label.setText("Shortcut cleared (disabled)")
        self.accept()

    def captured_sequence(self) -> QKeySequence:
        return self._captured_seq


class HotkeyManager:
    """Manages all keyboard shortcuts for the application.

    Loads key bindings from config, creates QShortcut instances,
    and supports live reassignment at runtime.
    """

    def __init__(self, parent: QWidget, config_loader=None):
        self._parent = parent
        self._config_loader = config_loader

        # action_name → (QShortcut, callback, default_seq)
        self._shortcuts: Dict[str, QShortcut] = {}
        self._callbacks: Dict[str, Callable[[], None]] = {}
        self._defaults: Dict[str, QKeySequence] = {}
        self._labels: Dict[str, str] = {}

        for name, key_str, label in DEFAULTS:
            self._defaults[name] = _build_seq(key_str)
            self._labels[name] = label

    # ------------------------------------------------------------------
    # Registration
    # ------------------------------------------------------------------

    def register(self, action_name: str, callback: Callable[[], None]) -> None:
        """Register a shortcut with its callback.

        If a saved key exists in config it is used; otherwise the default.
        """
        saved_key = None
        if self._config_loader:
            saved_key = self._config_loader.get("Hotkeys", action_name, fallback=None)

        seq = _build_seq(saved_key) if saved_key else self._defaults.get(action_name, QSeq())
        self._callbacks[action_name] = callback

        context = Qt.ShortcutContext.WindowShortcut

        sc = QShortcut(seq, self._parent, context=context)

        def _make_handler(name):
            def handler():
                self._dispatch(name)
            return handler

        sc.activated.connect(_make_handler(action_name))
        self._shortcuts[action_name] = sc

    def register_all(self, action_map: Dict[str, Callable[[], None]]) -> None:
        """Register all known shortcuts from an action_name→callback mapping."""
        for name, callback in action_map.items():
            self.register(name, callback)

    # ------------------------------------------------------------------
    # Dispatch
    # ------------------------------------------------------------------

    def _dispatch(self, action_name: str):
        """Check if the shortcut should fire (not blocked by editable focus),
        then invoke the callback.
        """
        # For seek arrows and space, don't fire when text editing
        editable_blocked = {"seek_forward", "seek_backward", "play_pause"}
        if action_name in editable_blocked and _is_editable_focused():
            return

        cb = self._callbacks.get(action_name)
        if cb:
            cb()

    # ------------------------------------------------------------------
    # Configuration persistence
    # ------------------------------------------------------------------

    def save_to_config(self) -> None:
        """Write all current key bindings to the config loader."""
        if self._config_loader is None:
            return
        for name, sc in self._shortcuts.items():
            key_str = sc.key().toString()
            self._config_loader.set("Hotkeys", name, key_str if key_str else "")
        self._config_loader.save()

    def load_from_config(self) -> None:
        """Re-read key bindings from config and update all shortcuts."""
        if self._config_loader is None:
            return
        for name, sc in self._shortcuts.items():
            saved = self._config_loader.get("Hotkeys", name, fallback=None)
            if saved is not None:
                seq = _build_seq(saved) if saved != "" else QSeq()
            else:
                seq = self._defaults.get(name, QSeq())
            sc.setKey(seq)

    def reset_to_defaults(self) -> None:
        """Reset all shortcuts to their default keys and save."""
        for name, sc in self._shortcuts.items():
            default_seq = self._defaults.get(name, QSeq())
            sc.setKey(default_seq)
        self.save_to_config()

    # ------------------------------------------------------------------
    # Query API (for Settings UI)
    # ------------------------------------------------------------------

    def get_label(self, action_name: str) -> str:
        return self._labels.get(action_name, action_name)

    def get_current_key_string(self, action_name: str) -> str:
        sc = self._shortcuts.get(action_name)
        if sc is None:
            return ""
        return sc.key().toString()

    def get_default_key_string(self, action_name: str) -> str:
        return self._defaults.get(action_name, QSeq()).toString()

    def all_action_names(self):
        return list(self._shortcuts.keys())

    # ------------------------------------------------------------------
    # Live reassignment (called from Settings UI)
    # ------------------------------------------------------------------

    def set_shortcut(self, action_name: str, key_string: str) -> None:
        """Change the key binding for an action at runtime and persist."""
        sc = self._shortcuts.get(action_name)
        if sc is None:
            return
        seq = _build_seq(key_string)
        sc.setKey(seq)
        if self._config_loader:
            self._config_loader.set("Hotkeys", action_name, key_string)
            self._config_loader.save()

    def set_shortcut_seq(self, action_name: str, seq: QKeySequence) -> None:
        """Set a shortcut directly from a QKeySequence (from key capture dialog)."""
        sc = self._shortcuts.get(action_name)
        if sc is not None:
            sc.setKey(seq)
        if self._config_loader:
            self._config_loader.set("Hotkeys", action_name, seq.toString())
            self._config_loader.save()
