"""
Session Editor — CRUD-only session tree for nterm desktop.

Manages sessions and folders in the shared sessions.db without
connect/launch functionality. Follows the same integration pattern
as CredentialManagerWidget: standalone widget wrapped in a QDialog,
launched from the desktop shell menu.

Port from ntermqt's tree.py + editor.py, with connect actions removed.
"""

from __future__ import annotations
from typing import Optional, List
from enum import Enum, auto

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
    QTreeWidget, QTreeWidgetItem, QLineEdit, QSpinBox,
    QComboBox, QPushButton, QMenu, QInputDialog,
    QMessageBox, QAbstractItemView, QDialog,
    QDialogButtonBox, QGroupBox, QLabel, QSizePolicy,
)
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
from PyQt6.QtGui import QAction


# Lives in nterm/manager/session_editor.py — uses relative imports.
from . import SessionStore, SavedSession, SessionFolder


# ═════════════════════════════════════════════════════════════
#  Constants
# ═════════════════════════════════════════════════════════════

ROLE_ITEM_TYPE = Qt.ItemDataRole.UserRole
ROLE_ITEM_ID   = Qt.ItemDataRole.UserRole + 1


class ItemType(Enum):
    FOLDER  = auto()
    SESSION = auto()


# ═════════════════════════════════════════════════════════════
#  Drag-drop QTreeWidget subclass
# ═════════════════════════════════════════════════════════════

class _DragDropTree(QTreeWidget):
    """QTreeWidget that emits a signal after an internal drag-drop."""

    items_moved = pyqtSignal()

    def dropEvent(self, event):
        super().dropEvent(event)
        self.items_moved.emit()


# ═════════════════════════════════════════════════════════════
#  Session Form Dialog  (create / edit a single session)
# ═════════════════════════════════════════════════════════════

class SessionFormDialog(QDialog):
    """
    Form dialog for creating or editing a SavedSession.

    Credential names are passed in so the combo can offer vault
    entries without the form needing a direct vault dependency.
    """

    def __init__(
        self,
        session: SavedSession = None,
        credential_names: List[str] = None,
        parent: QWidget = None,
    ):
        super().__init__(parent)
        self._session = session or SavedSession()
        self._credential_names = credential_names or []
        self._setup_ui()
        self._load_session()

    # ── UI ───────────────────────────────────────────────────

    def _setup_ui(self) -> None:
        self.setWindowTitle(
            f"Edit Session" if self._session.id else "New Session"
        )
        self.setMinimumWidth(420)

        layout = QVBoxLayout(self)

        # ── Connection group ─────────────────────────────────
        conn_group = QGroupBox("Connection")
        conn_form = QFormLayout(conn_group)

        self._name_input = QLineEdit()
        self._name_input.setPlaceholderText("My Server")
        conn_form.addRow("Name:", self._name_input)

        self._desc_input = QLineEdit()
        self._desc_input.setPlaceholderText("Optional description")
        conn_form.addRow("Description:", self._desc_input)

        host_row = QHBoxLayout()
        self._host_input = QLineEdit()
        self._host_input.setPlaceholderText("hostname or IP")
        host_row.addWidget(self._host_input, 1)
        host_row.addWidget(QLabel(":"))
        self._port_input = QSpinBox()
        self._port_input.setRange(1, 65535)
        self._port_input.setValue(22)
        self._port_input.setFixedWidth(80)
        host_row.addWidget(self._port_input)
        conn_form.addRow("Host:", host_row)

        layout.addWidget(conn_group)

        # ── Authentication group ─────────────────────────────
        auth_group = QGroupBox("Authentication")
        auth_form = QFormLayout(auth_group)

        self._cred_combo = QComboBox()
        self._cred_combo.addItem("(SSH Agent)", None)
        self._cred_combo.addItem("(Ask on connect)", "__ask__")
        for name in self._credential_names:
            self._cred_combo.addItem(name, name)
        auth_form.addRow("Credential:", self._cred_combo)

        layout.addWidget(auth_group)

        # ── Extras / vendor (optional) ───────────────────────
        extras_group = QGroupBox("Platform")
        extras_form = QFormLayout(extras_group)

        self._vendor_combo = QComboBox()
        self._vendor_combo.addItem("(none)", "")
        self._vendor_combo.addItem("Arista EOS", "arista_eos")
        self._vendor_combo.addItem("Cisco IOS", "cisco_ios")
        self._vendor_combo.addItem("Cisco IOS-XE", "cisco_ios_xe")
        self._vendor_combo.addItem("Cisco NX-OS", "cisco_nxos")
        self._vendor_combo.addItem("Juniper Junos", "juniper_junos")
        extras_form.addRow("Vendor:", self._vendor_combo)

        hint = QLabel("Used for telemetry and command parsing.")
        hint.setStyleSheet("color: #6a7585; font-size: 10px;")
        extras_form.addRow("", hint)

        layout.addWidget(extras_group)

        # ── Buttons ──────────────────────────────────────────
        buttons = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel
        )
        buttons.accepted.connect(self._on_accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

        self._name_input.setFocus()

    # ── Data ─────────────────────────────────────────────────

    def _load_session(self) -> None:
        self._name_input.setText(self._session.name)
        self._desc_input.setText(self._session.description)
        self._host_input.setText(self._session.hostname)
        self._port_input.setValue(self._session.port)

        # Credential
        if self._session.credential_name:
            idx = self._cred_combo.findData(self._session.credential_name)
            if idx >= 0:
                self._cred_combo.setCurrentIndex(idx)

        # Vendor
        vendor = self._session.extras.get("vendor", "")
        if vendor:
            idx = self._vendor_combo.findData(vendor)
            if idx >= 0:
                self._vendor_combo.setCurrentIndex(idx)

    def _on_accept(self) -> None:
        if not self._name_input.text().strip():
            self._name_input.setFocus()
            return
        if not self._host_input.text().strip():
            self._host_input.setFocus()
            return
        self.accept()

    def get_session(self) -> SavedSession:
        """Return the edited session."""
        cred_data = self._cred_combo.currentData()
        cred_name = cred_data if cred_data and cred_data != "__ask__" else None

        extras = dict(self._session.extras)
        vendor = self._vendor_combo.currentData()
        if vendor:
            extras["vendor"] = vendor
        else:
            extras.pop("vendor", None)

        return SavedSession(
            id=self._session.id,
            name=self._name_input.text().strip(),
            description=self._desc_input.text().strip(),
            hostname=self._host_input.text().strip(),
            port=self._port_input.value(),
            credential_name=cred_name,
            folder_id=self._session.folder_id,
            position=self._session.position,
            extras=extras,
        )


# ═════════════════════════════════════════════════════════════
#  Session Editor Widget  (the main CRUD tree panel)
# ═════════════════════════════════════════════════════════════

class SessionEditorWidget(QWidget):
    """
    Tree-based session browser for CRUD operations.

    No connect/launch functionality — this is purely for
    organizing sessions and folders in sessions.db.

    Signals:
        sessions_changed: Emitted after any CRUD operation so the
            host can refresh dependent views (e.g. the web UI tree).
    """

    sessions_changed = pyqtSignal()

    def __init__(
        self,
        store: SessionStore,
        credential_names: List[str] = None,
        parent: QWidget = None,
    ):
        super().__init__(parent)
        self.store = store
        self._credential_names = credential_names or []

        self._filter_timer = QTimer()
        self._filter_timer.setSingleShot(True)
        self._filter_timer.timeout.connect(self._apply_filter)

        self._setup_ui()
        self.refresh()

    def set_credential_names(self, names: List[str]) -> None:
        """Update credential names (call after vault unlock)."""
        self._credential_names = list(names)

    # ─────────────────────────────────────────────────────────
    #  UI
    # ─────────────────────────────────────────────────────────

    def _setup_ui(self) -> None:
        layout = QVBoxLayout(self)
        layout.setContentsMargins(8, 8, 8, 8)
        layout.setSpacing(6)

        # ── Toolbar ──────────────────────────────────────────
        toolbar = QHBoxLayout()
        toolbar.setSpacing(4)

        self._filter_input = QLineEdit()
        self._filter_input.setPlaceholderText("Filter sessions…")
        self._filter_input.setClearButtonEnabled(True)
        self._filter_input.textChanged.connect(self._on_filter_changed)
        toolbar.addWidget(self._filter_input, 1)

        layout.addLayout(toolbar)

        # ── Tree ─────────────────────────────────────────────
        self._tree = _DragDropTree()
        self._tree.setHeaderHidden(True)
        self._tree.setRootIsDecorated(True)
        self._tree.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection
        )
        self._tree.setContextMenuPolicy(
            Qt.ContextMenuPolicy.CustomContextMenu
        )
        self._tree.setDragDropMode(
            QAbstractItemView.DragDropMode.InternalMove
        )
        self._tree.setAnimated(True)

        self._tree.itemDoubleClicked.connect(self._on_double_click)
        self._tree.customContextMenuRequested.connect(self._show_context_menu)
        self._tree.itemExpanded.connect(self._on_item_expanded)
        self._tree.itemCollapsed.connect(self._on_item_collapsed)
        self._tree.items_moved.connect(self._persist_tree_state)

        layout.addWidget(self._tree)

        # ── Bottom button row ────────────────────────────────
        btn_row = QHBoxLayout()
        btn_row.setSpacing(4)

        add_session_btn = QPushButton("+ Session")
        add_session_btn.setToolTip("Add a new session")
        add_session_btn.clicked.connect(lambda: self._add_session(None))
        btn_row.addWidget(add_session_btn)

        add_folder_btn = QPushButton("+ Folder")
        add_folder_btn.setToolTip("Add a new folder")
        add_folder_btn.clicked.connect(lambda: self._add_folder(None))
        btn_row.addWidget(add_folder_btn)

        # Import/Export menu button
        io_btn = QPushButton("⋮")
        io_btn.setFixedWidth(32)
        io_btn.setToolTip("Import / Export")
        io_btn.clicked.connect(lambda: self._show_io_menu(io_btn))
        btn_row.addWidget(io_btn)

        btn_row.addStretch()

        # Summary label
        self._summary_label = QLabel()
        self._summary_label.setStyleSheet("color: #6a7585; font-size: 11px;")
        btn_row.addWidget(self._summary_label)

        layout.addLayout(btn_row)

    # ─────────────────────────────────────────────────────────
    #  Public API
    # ─────────────────────────────────────────────────────────

    def refresh(self) -> None:
        """Reload tree from the store."""
        self._tree.clear()
        tree_data = self.store.get_tree()

        # Build folder lookup
        folder_items: dict[int, QTreeWidgetItem] = {}

        # First pass: create folder items
        for folder in tree_data["folders"]:
            item = self._create_folder_item(folder)
            folder_items[folder.id] = item

        # Second pass: parent folders
        for folder in tree_data["folders"]:
            item = folder_items[folder.id]
            if folder.parent_id and folder.parent_id in folder_items:
                folder_items[folder.parent_id].addChild(item)
            else:
                self._tree.addTopLevelItem(item)
            item.setExpanded(folder.expanded)

        # Add sessions
        session_count = 0
        for session in tree_data["sessions"]:
            item = self._create_session_item(session)
            if session.folder_id and session.folder_id in folder_items:
                folder_items[session.folder_id].addChild(item)
            else:
                self._tree.addTopLevelItem(item)
            session_count += 1

        folder_count = len(tree_data["folders"])
        self._summary_label.setText(
            f"{session_count} sessions · {folder_count} folders"
        )

        self._apply_filter()

    # ─────────────────────────────────────────────────────────
    #  Item creation
    # ─────────────────────────────────────────────────────────

    def _create_folder_item(self, folder: SessionFolder) -> QTreeWidgetItem:
        item = QTreeWidgetItem()
        item.setText(0, f"📁 {folder.name}")
        item.setData(0, ROLE_ITEM_TYPE, ItemType.FOLDER)
        item.setData(0, ROLE_ITEM_ID, folder.id)
        item.setFlags(
            item.flags()
            | Qt.ItemFlag.ItemIsDragEnabled
            | Qt.ItemFlag.ItemIsDropEnabled
        )
        return item

    def _create_session_item(self, session: SavedSession) -> QTreeWidgetItem:
        item = QTreeWidgetItem()
        display = f"🖥 {session.name}"
        if session.description:
            display += f"  ({session.description})"
        item.setText(0, display)
        item.setToolTip(0, f"{session.hostname}:{session.port}")
        item.setData(0, ROLE_ITEM_TYPE, ItemType.SESSION)
        item.setData(0, ROLE_ITEM_ID, session.id)
        item.setFlags(
            item.flags()
            | Qt.ItemFlag.ItemIsDragEnabled
            | Qt.ItemFlag.ItemNeverHasChildren
        )
        return item

    def _find_session_item(self, session_id: int) -> Optional[QTreeWidgetItem]:
        for item in self._tree_iterator():
            if (
                item.data(0, ROLE_ITEM_TYPE) == ItemType.SESSION
                and item.data(0, ROLE_ITEM_ID) == session_id
            ):
                return item
        return None

    def _tree_iterator(self):
        def recurse(parent):
            for i in range(parent.childCount()):
                child = parent.child(i)
                yield child
                yield from recurse(child)

        for i in range(self._tree.topLevelItemCount()):
            item = self._tree.topLevelItem(i)
            yield item
            yield from recurse(item)

    # ─────────────────────────────────────────────────────────
    #  Drag-drop persistence
    # ─────────────────────────────────────────────────────────

    def _persist_tree_state(self) -> None:
        """Walk the visual tree and persist positions after drag-drop."""

        def process_children(parent_item, parent_folder_id):
            if parent_item is None:
                count = self._tree.topLevelItemCount()
                for pos in range(count):
                    process_item(self._tree.topLevelItem(pos), None, pos)
            else:
                for pos in range(parent_item.childCount()):
                    process_item(parent_item.child(pos), parent_folder_id, pos)

        def process_item(item, parent_folder_id, position):
            item_type = item.data(0, ROLE_ITEM_TYPE)
            item_id = item.data(0, ROLE_ITEM_ID)

            if item_type == ItemType.SESSION:
                session = self.store.get_session(item_id)
                if session and (
                    session.folder_id != parent_folder_id
                    or session.position != position
                ):
                    session.folder_id = parent_folder_id
                    session.position = position
                    self.store.update_session(session)

            elif item_type == ItemType.FOLDER:
                folder = self.store.get_folder(item_id)
                if folder and (
                    folder.parent_id != parent_folder_id
                    or folder.position != position
                ):
                    folder.parent_id = parent_folder_id
                    folder.position = position
                    self.store.update_folder(folder)
                process_children(item, item_id)

        process_children(None, None)
        self.sessions_changed.emit()

    # ─────────────────────────────────────────────────────────
    #  Filtering
    # ─────────────────────────────────────────────────────────

    def _on_filter_changed(self, text: str) -> None:
        self._filter_timer.start(150)

    def _apply_filter(self) -> None:
        query = self._filter_input.text().strip().lower()

        if not query:
            for item in self._tree_iterator():
                item.setHidden(False)
            return

        def process_item(item) -> bool:
            item_type = item.data(0, ROLE_ITEM_TYPE)

            if item_type == ItemType.SESSION:
                session_id = item.data(0, ROLE_ITEM_ID)
                session = self.store.get_session(session_id)
                if session:
                    matches = (
                        query in session.name.lower()
                        or query in session.description.lower()
                        or query in session.hostname.lower()
                    )
                    item.setHidden(not matches)
                    return matches
                return False

            elif item_type == ItemType.FOLDER:
                any_child_visible = False
                for i in range(item.childCount()):
                    if process_item(item.child(i)):
                        any_child_visible = True
                item.setHidden(not any_child_visible)
                if any_child_visible:
                    item.setExpanded(True)
                return any_child_visible

            return False

        for i in range(self._tree.topLevelItemCount()):
            process_item(self._tree.topLevelItem(i))

    # ─────────────────────────────────────────────────────────
    #  Events
    # ─────────────────────────────────────────────────────────

    def _on_double_click(self, item: QTreeWidgetItem, column: int) -> None:
        """Double-click opens the editor (not connect)."""
        if item.data(0, ROLE_ITEM_TYPE) == ItemType.SESSION:
            session_id = item.data(0, ROLE_ITEM_ID)
            session = self.store.get_session(session_id)
            if session:
                self._edit_session(session)

    def _on_item_expanded(self, item: QTreeWidgetItem) -> None:
        if item.data(0, ROLE_ITEM_TYPE) == ItemType.FOLDER:
            folder_id = item.data(0, ROLE_ITEM_ID)
            folder = self.store.get_folder(folder_id)
            if folder:
                folder.expanded = True
                self.store.update_folder(folder)

    def _on_item_collapsed(self, item: QTreeWidgetItem) -> None:
        if item.data(0, ROLE_ITEM_TYPE) == ItemType.FOLDER:
            folder_id = item.data(0, ROLE_ITEM_ID)
            folder = self.store.get_folder(folder_id)
            if folder:
                folder.expanded = False
                self.store.update_folder(folder)

    # ─────────────────────────────────────────────────────────
    #  Context menu
    # ─────────────────────────────────────────────────────────

    def _show_context_menu(self, pos) -> None:
        menu = QMenu(self)
        item = self._tree.itemAt(pos)

        if item:
            item_type = item.data(0, ROLE_ITEM_TYPE)

            if item_type == ItemType.SESSION:
                session_id = item.data(0, ROLE_ITEM_ID)
                session = self.store.get_session(session_id)
                if session:
                    menu.addAction(
                        "Edit…", lambda: self._edit_session(session)
                    )
                    menu.addAction(
                        "Duplicate", lambda: self._duplicate_session(session)
                    )
                    menu.addSeparator()
                    menu.addAction(
                        "Delete", lambda: self._delete_session(session)
                    )

            elif item_type == ItemType.FOLDER:
                folder_id = item.data(0, ROLE_ITEM_ID)
                folder = self.store.get_folder(folder_id)
                if folder:
                    menu.addAction(
                        "New Session Here…",
                        lambda: self._add_session(folder_id),
                    )
                    menu.addAction(
                        "New Subfolder…",
                        lambda: self._add_folder(folder_id),
                    )
                    menu.addSeparator()
                    menu.addAction(
                        "Rename…", lambda: self._rename_folder(folder)
                    )
                    menu.addSeparator()
                    menu.addAction(
                        "Delete Folder", lambda: self._delete_folder(folder)
                    )
        else:
            # Empty space
            menu.addAction("New Session…", lambda: self._add_session(None))
            menu.addAction("New Folder…", lambda: self._add_folder(None))

        if menu.actions():
            menu.exec(self._tree.viewport().mapToGlobal(pos))

    # ─────────────────────────────────────────────────────────
    #  CRUD actions
    # ─────────────────────────────────────────────────────────

    def _add_session(self, folder_id: Optional[int] = None) -> None:
        session = SavedSession(folder_id=folder_id)
        dialog = SessionFormDialog(
            session, self._credential_names, parent=self
        )
        if dialog.exec():
            new_session = dialog.get_session()
            self.store.add_session(new_session)
            self.refresh()
            self.sessions_changed.emit()

    def _edit_session(self, session: SavedSession) -> None:
        dialog = SessionFormDialog(
            session, self._credential_names, parent=self
        )
        if dialog.exec():
            updated = dialog.get_session()
            updated.id = session.id
            self.store.update_session(updated)
            self.refresh()
            # Re-select the edited item
            item = self._find_session_item(session.id)
            if item:
                self._tree.setCurrentItem(item)
            self.sessions_changed.emit()

    def _duplicate_session(self, session: SavedSession) -> None:
        new_session = SavedSession(
            name=f"{session.name} (copy)",
            description=session.description,
            hostname=session.hostname,
            port=session.port,
            credential_name=session.credential_name,
            folder_id=session.folder_id,
            extras=session.extras.copy() if session.extras else {},
        )
        new_id = self.store.add_session(new_session)
        self.refresh()
        item = self._find_session_item(new_id)
        if item:
            self._tree.setCurrentItem(item)
        self.sessions_changed.emit()

    def _delete_session(self, session: SavedSession) -> None:
        reply = QMessageBox.question(
            self,
            "Delete Session",
            f"Delete session '{session.name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if reply == QMessageBox.StandardButton.Yes:
            self.store.delete_session(session.id)
            self.refresh()
            self.sessions_changed.emit()

    def _add_folder(self, parent_id: Optional[int] = None) -> None:
        name, ok = QInputDialog.getText(self, "New Folder", "Folder name:")
        if ok and name.strip():
            self.store.add_folder(name.strip(), parent_id)
            self.refresh()
            self.sessions_changed.emit()

    def _rename_folder(self, folder: SessionFolder) -> None:
        name, ok = QInputDialog.getText(
            self, "Rename Folder", "Folder name:", text=folder.name
        )
        if ok and name.strip():
            folder.name = name.strip()
            self.store.update_folder(folder)
            self.refresh()
            self.sessions_changed.emit()

    def _delete_folder(self, folder: SessionFolder) -> None:
        reply = QMessageBox.question(
            self,
            "Delete Folder",
            f"Delete folder '{folder.name}'?\n\n"
            "Sessions inside will be moved to root level.",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if reply == QMessageBox.StandardButton.Yes:
            self.store.delete_folder(folder.id)
            self.refresh()
            self.sessions_changed.emit()

    # ─────────────────────────────────────────────────────────
    #  Import / Export
    # ─────────────────────────────────────────────────────────

    def _show_io_menu(self, anchor: QPushButton) -> None:
        menu = QMenu(self)
        menu.addAction("Import Sessions…", self._import_sessions)
        menu.addAction("Import from TerminalTelemetry…", self._import_terminal_telemetry)
        menu.addSeparator()
        menu.addAction("Export Sessions…", self._export_sessions)
        menu.exec(anchor.mapToGlobal(anchor.rect().topRight()))

    def _import_sessions(self) -> None:
        from .io import ImportDialog
        dialog = ImportDialog(self.store, parent=self)
        if dialog.exec():
            self.refresh()
            self.sessions_changed.emit()

    def _import_terminal_telemetry(self) -> None:
        from .io import ImportTerminalTelemetryDialog
        dialog = ImportTerminalTelemetryDialog(self.store, parent=self)
        if dialog.exec():
            self.refresh()
            self.sessions_changed.emit()

    def _export_sessions(self) -> None:
        from .io import ExportDialog
        dialog = ExportDialog(self.store, parent=self)
        dialog.exec()