# SPDX-FileCopyrightText: 2025 XLeitstelle Planen und Bauen <xleitstelle@gv.hamburg.de>
# SPDX-FileContributor: Michael Holzapfel <michael.holzapfel@geocledian.com>
#
# SPDX-License-Identifier: EUPL-1.2

import logging
import re
from typing import Any, List, Optional

from qgis.core import (
    QgsFeature,
    QgsGeometry,
    QgsProject,
    QgsRectangle,
    QgsVectorLayer,
)
from qgis.gui import QgisInterface
from qgis.PyQt.QtCore import (
    QAbstractTableModel,
    QModelIndex,
    QSortFilterProxyModel,
    Qt,
    QVariant,
)
from qgis.PyQt.QtWidgets import QAction, QMenu
from qgis.utils import iface

from xmas_plugin.topo_check import TopoTableView
from xmas_plugin.util.geom import transform_to_project_crs
from xmas_plugin.util.metadata import PLUGIN_DIR_NAME

logger = logging.getLogger(PLUGIN_DIR_NAME)


class OverlapTableModel(QAbstractTableModel):
    def __init__(
        self,
        data: List[QgsFeature | QgsVectorLayer],
        columns: List[str],
        parent: Optional[Any] = None,
    ) -> None:
        super().__init__(parent)
        self._data = data
        self._columns = columns

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

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

    def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
        if index.isValid() and role == Qt.UserRole:
            return self._data[index.row()][self._columns.index("Fläche")]
        elif index.isValid() and role == Qt.UserRole + 1:
            return self._data[index.row()][self._columns.index("Feature1")]
        elif index.isValid() and role == Qt.UserRole + 2:
            return self._data[index.row()][self._columns.index("Layer1")]
        elif index.isValid() and role == Qt.UserRole + 3:
            return self._data[index.row()][self._columns.index("Feature2")]
        elif index.isValid() and role == Qt.UserRole + 4:
            return self._data[index.row()][self._columns.index("Layer2")]
        if not index.isValid() or role != Qt.DisplayRole:
            return QVariant()
        row = self._data[index.row()]
        item = row[index.column()]
        if index.column() == 0:
            ret_val = f"{self._data[index.row()][0].area():0.2f} m²"
            if (
                self._data[index.row()][self._columns.index("Feature1")]
                .geometry()
                .constGet()
                .hasCurvedSegments()
                or self._data[index.row()][self._columns.index("Feature2")]
                .geometry()
                .constGet()
                .hasCurvedSegments()
            ):
                ret_val = f"⚠∿ {ret_val}"
            return ret_val
        if isinstance(item, QgsFeature):
            item: QgsFeature
            return item.attribute("id")
        elif isinstance(item, QgsVectorLayer):
            item: QgsVectorLayer
            try:
                return item.name()
            except RuntimeError as e:
                logger.debug(f"qgsvector layer probably removed: {e}")
                return QVariant()
        else:
            return QVariant()

    def headerData(
        self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
    ) -> Any:
        if role != Qt.DisplayRole:
            return QVariant()
        if orientation == Qt.Horizontal:
            return self._columns[section]
        return section + 1

    def repopulate(self, new_data) -> None:
        # Notify views that model is about to reset
        self.beginResetModel()
        self._data = new_data
        # Notify views that model reset is done
        self.endResetModel()


class OverlapTableView(TopoTableView):
    def __init__(self, iface: QgisInterface, parent=None):
        super().__init__(parent)
        self.iface = iface

        self.model = OverlapTableModel(
            data=[], columns=["Fläche", "Feature1", "Layer1", "Feature2", "Layer2"]
        )
        self.proxymodel = QSortFilterProxyModel()
        self.proxymodel.setSourceModel(self.model)
        self.setModel(self.proxymodel)
        self.setSortingEnabled(True)

        self.clicked.connect(self.on_click)
        self.proxymodel.modelReset.connect(self.resizeColumnsToContents)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)
        self.proxymodel.modelReset.connect(self.resizeColumnsToContents)

    def show_context_menu(self, position):
        # Get cell under cursor (view coordinates)
        index = self.indexAt(position)
        if not index.isValid():
            return

        # Build menu
        menu = QMenu(self)
        menu.addAction(
            QAction(
                "Features filtern",
                self,
                triggered=lambda: self.filter_overlap_features(index),
            )
        )

        # Show at cursor position (map to global screen coordinates)
        menu.exec_(self.viewport().mapToGlobal(position))
        return

    def filter_overlap_features(self, index: QModelIndex):
        if not self.plan_id:
            return

        feature1 = index.model().data(index, Qt.UserRole + 1)
        layer1 = index.model().data(index, Qt.UserRole + 2)
        feature2 = index.model().data(index, Qt.UserRole + 3)
        layer2 = index.model().data(index, Qt.UserRole + 4)

        # remove all filters
        self.clear_subset_filters()

        # set substring filter for gap-layers
        subsets = []
        if layer1.id() == layer2.id():
            quoted_ids = f"('{feature1.attribute('id')}','{feature2.attribute('id')}')"
            filter_str = f' {self.subset_start_token} AND "id" IN {quoted_ids} {self.subset_end_token}'
            subsets.append({"layer": layer1, "subset_str": filter_str})
        else:
            filter_str = f" {self.subset_start_token} AND \"id\" = '{feature1.attribute('id')}' {self.subset_end_token}"
            subsets.append({"layer": layer1, "subset_str": filter_str})
            filter_str = f" {self.subset_start_token} AND \"id\" = '{feature2.attribute('id')}' {self.subset_end_token}"
            subsets.append({"layer": layer2, "subset_str": filter_str})
        for subset in subsets:
            layer = subset["layer"]
            current_subsetstr = layer.dataProvider().subsetString()
            if self.subset_slug in current_subsetstr:
                current_subsetstr = re.sub(
                    pattern=rf" {re.escape(self.subset_start_token)}.*?"
                    rf"{re.escape(self.subset_end_token)}",
                    repl="",
                    string=current_subsetstr,
                    flags=re.DOTALL,
                )
            layer.dataProvider().setSubsetString(
                f"{current_subsetstr}{subset['subset_str']}", True
            )
            layer.triggerRepaint()
            iface.layerTreeView().refreshLayerSymbology(layer.id())

        # show overlaping layers and hide all other layers
        overlap_feature_layers = [layer1, layer2]
        for layer in QgsProject.instance().mapLayers().values():
            if isinstance(layer, QgsVectorLayer):
                if (
                    layer.customProperties().value("xmas_plugin/plan_id")
                    == self.plan_id
                ):
                    if layer in overlap_feature_layers:
                        QgsProject.instance().layerTreeRoot().findLayer(
                            layer.id()
                        ).setItemVisibilityChecked(True)
                    else:
                        QgsProject.instance().layerTreeRoot().findLayer(
                            layer.id()
                        ).setItemVisibilityChecked(False)

        iface.mapCanvas().refresh()

    def on_click(self, index: QModelIndex) -> None:
        def _select_layer(layer: QgsVectorLayer) -> None:
            """
            Highlights/selects a layer in the QGis Layers Dock,
            if the layer is part of a (nested) group QGis should expand
            thos groups automatically
            :param layer:
            :return:
            """
            if not layer:
                return

            root = QgsProject.instance().layerTreeRoot()
            layer_node = root.findLayer(layer.id())
            if layer_node is None:
                return

            self.iface.layerTreeView().setCurrentLayer(layer)
            return

        src_index: QModelIndex = index.model().mapToSource(index)
        # select clicked layer in qgis layertree view
        if index.column() in [2, 4]:
            layer = src_index.model().data(src_index, Qt.UserRole + src_index.column())
            _select_layer(layer)
            return

        item: QgsGeometry | QgsFeature = self.model.data(
            src_index, Qt.UserRole + src_index.column()
        )
        if isinstance(item, QgsFeature):  # click on feature column
            _geom = item.geometry()
        elif isinstance(item, QgsGeometry):  # click on overlap-geom column
            _geom = item

        geom = transform_to_project_crs(_geom, self.plan_crs)
        _extent = geom.boundingBox()
        buffer_percentage = 0.4
        width = _extent.xMaximum() - _extent.xMinimum()
        height = _extent.yMaximum() - _extent.yMinimum()
        extent = QgsRectangle(
            _extent.xMinimum() - buffer_percentage * width,
            _extent.yMinimum() - buffer_percentage * height,
            _extent.xMaximum() + buffer_percentage * width,
            _extent.yMaximum() + buffer_percentage * height,
        )

        # flash geometry
        iface.mapCanvas().flashGeometries(
            [geom], crs=self.plan_crs, flashes=5, duration=500
        )
        iface.mapCanvas().flashGeometries(
            [QgsGeometry(x) for x in geom.vertices()],
            crs=self.plan_crs,
            flashes=5,
            duration=500,
        )

        # zoom to geometry
        canvas = self.iface.mapCanvas()
        canvas.setExtent(extent)
        canvas.refresh()

    def repopulate(self, new_data) -> None:
        self.model.repopulate(new_data)
