"""Per-meeting context selector — repo chips + knowledge tree with checkboxes."""

from __future__ import annotations

import json
import threading
import urllib.request
from pathlib import Path

from PySide6.QtCore import QRect, QSize, Qt, Q_ARG, QMetaObject, QPoint
from PySide6.QtWidgets import (
    QFrame,
    QHBoxLayout,
    QLabel,
    QLayout,
    QLayoutItem,
    QPushButton,
    QSizePolicy,
    QStyle,
    QTreeWidget,
    QTreeWidgetItem,
    QVBoxLayout,
    QWidget,
    QWidgetItem,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def sanitize_name(raw: str) -> str:
    """'crypto_implementation_plan' → 'Crypto Implementation Plan'."""
    return raw.replace("_", " ").replace("-", " ").title()


# ---------------------------------------------------------------------------
# FlowLayout — standard Qt flow layout ported to PySide6
# ---------------------------------------------------------------------------


class FlowLayout(QLayout):
    """Layout that arranges widgets horizontally, wrapping to the next line."""

    def __init__(self, parent=None, margin: int = 0, spacing: int = 6):
        super().__init__(parent)
        self._items: list[QLayoutItem] = []
        self._spacing = spacing
        if margin >= 0:
            self.setContentsMargins(margin, margin, margin, margin)

    def addItem(self, item: QLayoutItem) -> None:
        self._items.append(item)

    def count(self) -> int:
        return len(self._items)

    def itemAt(self, index: int) -> QLayoutItem | None:
        if 0 <= index < len(self._items):
            return self._items[index]
        return None

    def takeAt(self, index: int) -> QLayoutItem | None:
        if 0 <= index < len(self._items):
            return self._items.pop(index)
        return None

    def expandingDirections(self):
        return Qt.Orientation(0)

    def hasHeightForWidth(self) -> bool:
        return True

    def heightForWidth(self, width: int) -> int:
        return self._do_layout(QRect(0, 0, width, 0), test_only=True)

    def setGeometry(self, rect: QRect) -> None:
        super().setGeometry(rect)
        self._do_layout(rect, test_only=False)

    def sizeHint(self) -> QSize:
        return self.minimumSize()

    def minimumSize(self) -> QSize:
        size = QSize()
        for item in self._items:
            size = size.expandedTo(item.minimumSize())
        m = self.contentsMargins()
        size += QSize(m.left() + m.right(), m.top() + m.bottom())
        return size

    def _do_layout(self, rect: QRect, test_only: bool) -> int:
        m = self.contentsMargins()
        effective = rect.adjusted(m.left(), m.top(), -m.right(), -m.bottom())
        x = effective.x()
        y = effective.y()
        line_height = 0

        for item in self._items:
            wid = item.widget()
            if wid is None:
                continue
            space_x = self._spacing
            item_size = item.sizeHint()
            next_x = x + item_size.width() + space_x
            if next_x - space_x > effective.right() + 1 and line_height > 0:
                x = effective.x()
                y = y + line_height + self._spacing
                next_x = x + item_size.width() + space_x
                line_height = 0
            if not test_only:
                item.setGeometry(QRect(QPoint(x, y), item_size))
            x = next_x
            line_height = max(line_height, item_size.height())

        return y + line_height - rect.y() + m.bottom()


# ---------------------------------------------------------------------------
# RepoChip — toggleable chip for a repo
# ---------------------------------------------------------------------------


class RepoChip(QPushButton):
    """Small rounded toggle chip representing a repository."""

    def __init__(self, repo_path: str, parent=None):
        self.repo_path = repo_path
        display_name = sanitize_name(Path(repo_path).name)
        super().__init__(display_name, parent)
        self.setCheckable(True)
        self.setCursor(Qt.PointingHandCursor)
        self.setStyleSheet("""
            QPushButton {
                background: #1a1a1a; color: #808080; border: 1px solid #2a2a2a;
                border-radius: 12px; padding: 4px 14px; font-size: 12px;
            }
            QPushButton:checked {
                background: #238636; color: #ffffff; border: 1px solid #2ea043;
            }
            QPushButton:hover { border-color: #58a6ff; }
            QPushButton:disabled { color: #3a3a3a; border-color: #1a1a1a; }
        """)


# ---------------------------------------------------------------------------
# ContextSelector — composite widget
# ---------------------------------------------------------------------------


class ContextSelector(QFrame):
    """Dashboard widget for selecting repos + knowledge folders per meeting."""

    def __init__(self, parent=None):
        super().__init__(parent)
        self._port = 7891
        self._repos: list[str] = []
        self._knowledge_path = ""
        self._session_active = False
        self._setup_ui()

    # -- UI setup ----------------------------------------------------------

    def _setup_ui(self):
        self.setStyleSheet(
            "QFrame#contextSelector { background: #0d0d0d; border: 1px solid #2a2a2a; "
            "border-radius: 8px; padding: 20px; }"
        )
        self.setObjectName("contextSelector")
        layout = QVBoxLayout(self)
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)

        # Header row
        header_row = QHBoxLayout()
        header = QLabel("Context Selector")
        header.setStyleSheet("font-size: 14px; font-weight: 600; color: #808080;")
        header_row.addWidget(header)
        self._loaded_label = QLabel("")
        self._loaded_label.setStyleSheet("font-size: 11px; color: #4ec9b0;")
        header_row.addWidget(self._loaded_label)
        header_row.addStretch()
        layout.addLayout(header_row)

        # Disabled overlay label
        self._disabled_label = QLabel("Start a meeting to select context")
        self._disabled_label.setStyleSheet(
            "font-size: 12px; color: #5a5a5a; font-style: italic; padding: 8px 0;"
        )
        self._disabled_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self._disabled_label)

        # -- Repos section --
        self._repos_label = QLabel("Repositories")
        self._repos_label.setStyleSheet("font-size: 12px; color: #808080; margin-top: 4px;")
        layout.addWidget(self._repos_label)

        self._chips_container = QWidget()
        self._chips_layout = FlowLayout(self._chips_container, spacing=6)
        layout.addWidget(self._chips_container)

        self._no_repos_label = QLabel("No repos configured")
        self._no_repos_label.setStyleSheet("font-size: 11px; color: #5a5a5a;")
        layout.addWidget(self._no_repos_label)

        # -- Knowledge section --
        self._knowledge_label = QLabel("Knowledge Base")
        self._knowledge_label.setStyleSheet("font-size: 12px; color: #808080; margin-top: 8px;")
        layout.addWidget(self._knowledge_label)

        self._tree = QTreeWidget()
        self._tree.setHeaderHidden(True)
        self._tree.setMinimumHeight(80)
        self._tree.setMaximumHeight(200)
        self._tree.setStyleSheet("""
            QTreeWidget {
                background: #111; border: 1px solid #2a2a2a; border-radius: 6px;
                color: #d4d4d4; font-size: 12px;
            }
            QTreeWidget::item { padding: 3px 0; }
            QTreeWidget::item:selected { background: #264f78; }
        """)
        self._tree.itemChanged.connect(self._on_tree_item_changed)
        layout.addWidget(self._tree)

        self._no_knowledge_label = QLabel("No knowledge folder configured")
        self._no_knowledge_label.setStyleSheet("font-size: 11px; color: #5a5a5a;")
        layout.addWidget(self._no_knowledge_label)

        # -- Load button --
        btn_row = QHBoxLayout()
        self._load_btn = QPushButton("Load Context")
        self._load_btn.setCursor(Qt.PointingHandCursor)
        self._load_btn.setFixedWidth(130)
        self._load_btn.setStyleSheet("""
            QPushButton {
                background: #238636; color: white; border: none; border-radius: 6px;
                padding: 8px 16px; font-size: 13px; font-weight: 500;
            }
            QPushButton:hover { background: #2ea043; }
            QPushButton:disabled { background: #1a3a1a; color: #5a5a5a; }
        """)
        self._load_btn.clicked.connect(self._load_context)
        btn_row.addWidget(self._load_btn)
        btn_row.addStretch()
        layout.addLayout(btn_row)

        # Start disabled
        self.set_session_active(False)

    # -- Public API --------------------------------------------------------

    def set_port(self, port: int):
        self._port = port

    def set_config(self, repos: list[str], knowledge_path: str, port: int):
        """Populate chips and tree from config."""
        self._repos = repos
        self._knowledge_path = knowledge_path
        self._port = port
        self._populate_chips()
        self._populate_tree()

    def set_session_active(self, active: bool):
        """Enable/disable the selector based on meeting state."""
        self._session_active = active
        self._load_btn.setEnabled(active)
        self._tree.setEnabled(active)
        self._chips_container.setEnabled(active)
        self._disabled_label.setVisible(not active)

        # Show/hide content sections
        for w in (self._repos_label, self._chips_container, self._no_repos_label,
                  self._knowledge_label, self._tree, self._no_knowledge_label,
                  self._load_btn):
            w.setVisible(active)

        # Re-show the correct visibility for repos/knowledge sections
        if active:
            self._update_section_visibility()

        if not active:
            self._loaded_label.setText("")

    # -- Internals ---------------------------------------------------------

    def _update_section_visibility(self):
        has_repos = len(self._repos) > 0
        self._chips_container.setVisible(has_repos)
        self._no_repos_label.setVisible(not has_repos)
        self._repos_label.setVisible(True)

        has_knowledge = self._tree.topLevelItemCount() > 0
        self._tree.setVisible(has_knowledge)
        self._no_knowledge_label.setVisible(not has_knowledge)
        self._knowledge_label.setVisible(True)
        self._load_btn.setVisible(True)

    def _populate_chips(self):
        """Create RepoChip widgets for each configured repo."""
        # Clear existing chips
        while self._chips_layout.count():
            item = self._chips_layout.takeAt(0)
            if item and item.widget():
                item.widget().deleteLater()

        for repo_path in self._repos:
            chip = RepoChip(repo_path)
            self._chips_layout.addWidget(chip)

    def _populate_tree(self):
        """Build the knowledge tree from the folder structure."""
        self._tree.clear()
        if not self._knowledge_path:
            self._update_section_visibility()
            return

        from meeting_helper.context_loader import get_knowledge_tree

        tree_data = get_knowledge_tree(self._knowledge_path)
        if not tree_data:
            self._update_section_visibility()
            return

        self._tree.blockSignals(True)
        self._add_tree_nodes(self._tree, tree_data)
        self._tree.blockSignals(False)
        self._tree.expandAll()
        self._update_section_visibility()

    def _add_tree_nodes(self, parent, nodes: list[dict]):
        for node in nodes:
            if isinstance(parent, QTreeWidget):
                item = QTreeWidgetItem(parent)
            else:
                item = QTreeWidgetItem(parent)
            display = f"{sanitize_name(node['name'])}  ({node['doc_count']})"
            item.setText(0, display)
            item.setData(0, Qt.UserRole, node["path"])
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(0, Qt.CheckState.Unchecked)
            if node.get("children"):
                self._add_tree_nodes(item, node["children"])

    def _on_tree_item_changed(self, item: QTreeWidgetItem, column: int):
        """Propagate check state: parent → children, child → parent."""
        self._tree.blockSignals(True)
        try:
            check_state = item.checkState(0)
            # Propagate down to children
            for i in range(item.childCount()):
                item.child(i).setCheckState(0, check_state)
                self._propagate_down(item.child(i), check_state)
            # Propagate up to parent
            parent = item.parent()
            if parent is not None:
                self._update_parent_check(parent)
        finally:
            self._tree.blockSignals(False)

    def _propagate_down(self, item: QTreeWidgetItem, state):
        for i in range(item.childCount()):
            item.child(i).setCheckState(0, state)
            self._propagate_down(item.child(i), state)

    def _update_parent_check(self, parent: QTreeWidgetItem):
        checked = 0
        total = parent.childCount()
        for i in range(total):
            cs = parent.child(i).checkState(0)
            if cs == Qt.CheckState.Checked:
                checked += 1
            elif cs == Qt.CheckState.PartiallyChecked:
                checked += 0.5

        if checked == 0:
            parent.setCheckState(0, Qt.CheckState.Unchecked)
        elif checked == total:
            parent.setCheckState(0, Qt.CheckState.Checked)
        else:
            parent.setCheckState(0, Qt.CheckState.PartiallyChecked)

        grandparent = parent.parent()
        if grandparent is not None:
            self._update_parent_check(grandparent)

    def _get_selected_repos(self) -> list[str]:
        result = []
        for i in range(self._chips_layout.count()):
            item = self._chips_layout.itemAt(i)
            if item and item.widget() and isinstance(item.widget(), RepoChip):
                chip = item.widget()
                if chip.isChecked():
                    result.append(chip.repo_path)
        return result

    def _get_selected_folders(self) -> list[str]:
        """Return relative paths of checked leaf-level knowledge folders."""
        result: list[str] = []
        self._collect_checked(self._tree.invisibleRootItem(), result)
        return result

    def _collect_checked(self, parent_item, result: list[str]):
        for i in range(parent_item.childCount()):
            child = parent_item.child(i)
            if child.checkState(0) == Qt.CheckState.Checked and child.childCount() == 0:
                path = child.data(0, Qt.UserRole)
                if path:
                    result.append(path)
            elif child.checkState(0) in (Qt.CheckState.Checked, Qt.CheckState.PartiallyChecked):
                if child.childCount() > 0:
                    # If fully checked parent with children, add the parent path
                    if child.checkState(0) == Qt.CheckState.Checked:
                        path = child.data(0, Qt.UserRole)
                        if path:
                            result.append(path)
                    else:
                        # Partially checked — recurse into children
                        self._collect_checked(child, result)

    def _load_context(self):
        selected_repos = self._get_selected_repos()
        selected_folders = self._get_selected_folders()

        if not selected_repos and not selected_folders:
            self._loaded_label.setText("Select at least one item")
            self._loaded_label.setStyleSheet("font-size: 11px; color: #f14c4c;")
            return

        self._load_btn.setEnabled(False)
        self._loaded_label.setText("Loading...")
        self._loaded_label.setStyleSheet("font-size: 11px; color: #808080;")

        threading.Thread(
            target=self._do_load,
            args=(selected_repos, selected_folders),
            daemon=True,
        ).start()

    def _do_load(self, repos: list[str], folders: list[str]):
        try:
            data = json.dumps({
                "repos": repos,
                "knowledge_path": self._knowledge_path,
                "knowledge_folders": folders,
            }).encode()
            req = urllib.request.Request(
                f"http://127.0.0.1:{self._port}/config",
                data=data,
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            with urllib.request.urlopen(req, timeout=15) as resp:
                json.loads(resp.read().decode())

            msg = f"Loaded {len(repos)} repo(s), {len(folders)} folder(s)"
            QMetaObject.invokeMethod(
                self._loaded_label, "setText",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(str, msg),
            )
            QMetaObject.invokeMethod(
                self._loaded_label, "setStyleSheet",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(str, "font-size: 11px; color: #4ec9b0;"),
            )
        except Exception as exc:
            QMetaObject.invokeMethod(
                self._loaded_label, "setText",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(str, f"Error: {exc}"),
            )
            QMetaObject.invokeMethod(
                self._loaded_label, "setStyleSheet",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(str, "font-size: 11px; color: #f14c4c;"),
            )
        finally:
            QMetaObject.invokeMethod(
                self._load_btn, "setEnabled",
                Qt.ConnectionType.QueuedConnection,
                Q_ARG(bool, True),
            )
