"""Record controller — populates QTableViews with DNS records, supports CRUD."""

from PyQt6.QtCore import Qt, QAbstractTableModel, QModelIndex, QSortFilterProxyModel
from PyQt6.QtWidgets import QMainWindow, QTableView, QHeaderView

from dnsctl.config import SUPPORTED_RECORD_TYPES
from dnsctl.core.state_manager import load_protected_records

# Column definitions for the record table
_COLUMNS = ("Type", "Name", "Content", "TTL", "Priority", "Proxied", "Protected")


class RecordTableModel(QAbstractTableModel):
    """In-memory table model backed by a list of record dicts."""

    def __init__(self, records: list[dict] | None = None) -> None:
        super().__init__()
        self._records: list[dict] = records or []
        self._protected_set: set[tuple[str, str]] = set()

    def set_records(self, records: list[dict]) -> None:
        self.beginResetModel()
        self._records = list(records)
        self._refresh_protected()
        self.endResetModel()

    def _refresh_protected(self) -> None:
        """Reload the protected records set from state."""
        protected = load_protected_records()
        self._protected_set = {
            (p.get("type", ""), p.get("name", "")) for p in protected
        }

    def refresh_protected(self) -> None:
        """Public: re-read protected list and repaint the table."""
        self._refresh_protected()
        if self._records:
            tl = self.index(0, 0)
            br = self.index(self.rowCount() - 1, self.columnCount() - 1)
            self.dataChanged.emit(tl, br)

    # -- QAbstractTableModel interface --

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
        return len(self._records)

    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
        return len(_COLUMNS)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
            return _COLUMNS[section]
        return None

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
        if not index.isValid() or role != Qt.ItemDataRole.DisplayRole:
            return None
        rec = self._records[index.row()]
        col = index.column()
        if col == 0:
            return rec.get("type", "")
        if col == 1:
            return rec.get("name", "")
        if col == 2:
            return rec.get("content", "")
        if col == 3:
            ttl = rec.get("ttl", 1)
            return "Auto" if ttl == 1 else str(ttl)
        if col == 4:
            return str(rec.get("priority", "")) if rec.get("type") in ("MX", "SRV") else ""
        if col == 5:
            return "Yes" if rec.get("proxied") else "No"
        if col == 6:
            key = (rec.get("type", ""), rec.get("name", ""))
            return "\U0001f6e1" if key in self._protected_set else ""
        return None

    def flags(self, index: QModelIndex) -> Qt.ItemFlag:
        return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable


class _TypeFilterProxy(QSortFilterProxyModel):
    """Filters records by DNS type."""

    def __init__(self, record_type: str | None = None) -> None:
        super().__init__()
        self._type = record_type  # None = show all

    def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex) -> bool:
        if self._type is None:
            return True
        model = self.sourceModel()
        idx = model.index(source_row, 0)
        return model.data(idx) == self._type


class RecordController:
    """Manages the record table views across all tabs."""

    def __init__(self, window: QMainWindow) -> None:
        self._window = window
        self._model = RecordTableModel()

        # Map each tab to its QTableView + proxy
        self._proxies: dict[str, _TypeFilterProxy] = {}
        self._setup_tables()

    def _setup_tables(self) -> None:
        """Wire each tab's QTableView to a filtered proxy model."""
        tab_map: dict[str, str | None] = {
            "recordTableAll": None,
            "recordTableA": "A",
            "recordTableAAAA": "AAAA",
            "recordTableCNAME": "CNAME",
            "recordTableMX": "MX",
            "recordTableTXT": "TXT",
            "recordTableSRV": "SRV",
        }

        for widget_name, rec_type in tab_map.items():
            table: QTableView = getattr(self._window, widget_name, None)
            if table is None:
                continue

            proxy = _TypeFilterProxy(rec_type)
            proxy.setSourceModel(self._model)
            table.setModel(proxy)
            self._proxies[widget_name] = proxy

            # Stretch columns to fill
            header = table.horizontalHeader()
            if header:
                header.setStretchLastSection(True)
                header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)

    def populate(self, records: list[dict]) -> None:
        """Replace the displayed records."""
        self._model.set_records(records)

    def refresh_protected(self) -> None:
        """Refresh the protected column after protect/unprotect changes."""
        self._model.refresh_protected()

    @property
    def records(self) -> list[dict]:
        """Return the current records list."""
        return list(self._model._records)

    def get_selected_record(self) -> dict | None:
        """Return the selected record from the currently visible table, or None."""
        # Find which table is currently showing
        tabs = self._window.recordTabs
        current_widget = tabs.currentWidget()
        if current_widget is None:
            return None

        # Find the QTableView inside the current tab
        table: QTableView | None = None
        for child in current_widget.findChildren(QTableView):
            table = child
            break
        if table is None:
            return None

        indexes = table.selectionModel().selectedRows()
        if not indexes:
            return None

        # Map proxy index → source index
        proxy_idx = indexes[0]
        proxy_model = table.model()
        if isinstance(proxy_model, QSortFilterProxyModel):
            source_idx = proxy_model.mapToSource(proxy_idx)
        else:
            source_idx = proxy_idx

        row = source_idx.row()
        if 0 <= row < len(self._model._records):
            return self._model._records[row]
        return None

    def connect_selection_changed(self, callback) -> None:
        """Connect selection-changed signals from all tables to *callback*."""
        for widget_name in self._proxies:
            table: QTableView = getattr(self._window, widget_name, None)
            if table is not None and table.selectionModel():
                table.selectionModel().selectionChanged.connect(
                    lambda _sel, _desel, cb=callback: cb()
                )

    def connect_double_click(self, callback) -> None:
        """Connect doubleClicked signals from all tables to *callback*."""
        for widget_name in self._proxies:
            table: QTableView = getattr(self._window, widget_name, None)
            if table is not None:
                table.doubleClicked.connect(
                    lambda _index, cb=callback: cb()
                )

    def add_record(self, record: dict) -> None:
        """Append a record to the in-memory list."""
        self._model.beginInsertRows(QModelIndex(), len(self._model._records), len(self._model._records))
        self._model._records.append(record)
        self._model.endInsertRows()

    def update_record(self, old_record: dict, new_record: dict) -> None:
        """Replace a record in-place by identity, id, or composite key."""
        old_id = old_record.get("id")
        old_key = (old_record.get("type", ""), old_record.get("name", ""), old_record.get("content", ""))
        for i, r in enumerate(self._model._records):
            matched = (
                r is old_record
                or (old_id and r.get("id") == old_id)
                or (not old_id and not r.get("id")
                    and (r.get("type", ""), r.get("name", ""), r.get("content", "")) == old_key)
            )
            if matched:
                self._model._records[i] = new_record
                tl = self._model.index(i, 0)
                br = self._model.index(i, self._model.columnCount() - 1)
                self._model.dataChanged.emit(tl, br)
                return

    def delete_record(self, record: dict) -> None:
        """Remove a record from the in-memory list."""
        rec_id = record.get("id")
        rec_key = (record.get("type", ""), record.get("name", ""), record.get("content", ""))
        for i, r in enumerate(self._model._records):
            matched = (
                r is record
                or (rec_id and r.get("id") == rec_id)
                or (not rec_id and not r.get("id")
                    and (r.get("type", ""), r.get("name", ""), r.get("content", "")) == rec_key)
            )
            if matched:
                self._model.beginRemoveRows(QModelIndex(), i, i)
                self._model._records.pop(i)
                self._model.endRemoveRows()
                return
