# -*- coding: utf-8 -*-

# -------------------- 1. Python 标准库 --------------------
import os
import traceback

from qgis.PyQt import QtWidgets
# -------------------- 2. QGIS 封装的 PyQt（保证版本兼容） --------------------
from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtWidgets import QApplication, QMessageBox, QProgressDialog
from qgis._core import QgsMapLayerProxyModel

# -------------------- 3. QGIS 核心库（Core） --------------------
from qgis.core import (
    Qgis,                       # 包含各种枚举常量（如 Qgis.Warning）
    QgsApplication,
    QgsGeometry,
    QgsMessageLog,              # 日志记录
    QgsProcessingFeedback,      # 处理反馈
    QgsProject,                 # 项目实例
    QgsSpatialIndex,            # 空间索引
    QgsWkbTypes,                # 几何类型
)

# -------------------- 4. QGIS 图形界面库（GUI） --------------------
from qgis.gui import (
    QgsMapToolEmitPoint,        # 点击地图工具
    QgsRubberBand,              # 临时绘制的橡皮筋
    QgsVertexMarker,            # 顶点标记（原错误在 _gui）
)

# -------------------- 5. QGIS 处理框架与接口 --------------------
import processing              # 处理工具箱
from qgis.utils import iface   # 插件全局接口对象


# 加载UI文件
FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'cyanlove_neighbor_cengjiset_base.ui'))

from .cyanlove_config_read_intfile import readconfig

class cyanlove_neighbor_cengjiset(QtWidgets.QDockWidget, FORM_CLASS):
    """邻区选择工具停靠窗口（UI入口）"""
    def __init__(self, parent=None):
        super(cyanlove_neighbor_cengjiset, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.canvas = iface.mapCanvas()

        self.tool = None
        self.plugin = None  # 可由主插件注入

        self.setFloating(True)
        self.setWindowFlags(Qt.Dialog)
        self._load_depth_from_config()
        self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer)
        self.pushButton_voronoi.clicked.connect(self.voronoi_neight_create)
        self.pushButton_depth.clicked.connect(self.setdepth_neight)


    def get_point_layer(self):
        """返回当前选择的点图层，供邻区工具使用"""
        return self.mMapLayerComboBox.currentLayer()

    def voronoi_neight_create(self):
        """
        生成Voronoi多边形图层（不含属性聚合）
        流程：点去重 → 生成Voronoi → 添加到地图
        """
        layer = self.mMapLayerComboBox.currentLayer()
        if not layer or layer.geometryType() != QgsWkbTypes.PointGeometry:
            self._update_status("请先选择一个有效的点图层！", is_error=True)
            return

        self.progressBar.setValue(5)
        self._update_status("正在删除重复点...")
        feedback = QgsProcessingFeedback()

        try:
            # 1. 删除重复几何（点去重）
            unique_res = processing.run("qgis:deleteduplicategeometries", {
                'INPUT': layer,
                'OUTPUT': 'memory:unique_points'
            }, feedback=feedback)
            unique_layer = unique_res['OUTPUT']

            self.progressBar.setValue(50)
            self._update_status("正在生成Voronoi多边形...")

            # 2. 生成 Voronoi 多边形
            voronoi_res = processing.run("qgis:voronoipolygons", {
                'INPUT': unique_layer,
                'BUFFER': 10.0,
                'OUTPUT': 'memory:voronoi_polygons'
            }, feedback=feedback)
            voronoi_layer = voronoi_res['OUTPUT']

            self.progressBar.setValue(90)
            self._update_status("正在加载图层到地图...")

            # 3. 设置图层名称并添加到项目（不做属性聚合）
            final_name = f"{layer.name()}_Voronoi"
            voronoi_layer.setName(final_name)
            QgsProject.instance().addMapLayer(voronoi_layer)

            self.canvas.setExtent(voronoi_layer.extent())
            self.canvas.refresh()

            self.progressBar.setValue(100)
            self._update_status("生成完成！Voronoi图层已添加。")

        except Exception as e:
            self.progressBar.setValue(0)
            error_msg = str(e)
            self._update_status(f"生成失败: {error_msg}", is_error=True)
            QgsMessageLog.logMessage(f"Voronoi生成失败: {error_msg}", "CyanLovePlugin", Qgis.Critical)


    def _update_status(self, message, is_error=False):
        """统一更新状态标签和样式"""
        self.label_status.setText(f"运行状态：{message}")
        if is_error:
            self.label_status.setStyleSheet("color: #e74c3c; padding: 4px; font-weight: bold;")
        else:
            self.label_status.setStyleSheet("color: #27ae60; padding: 4px; font-weight: bold;")

    def _load_depth_from_config(self):
        """从配置文件读取邻区深度，默认1"""
        try:
            depth_str = readconfig.read_ini_file('NeighborSettings', 'depth')
            depth = int(depth_str)
        except:
            depth = 2
        self.spinBox_depth.setValue(depth)

    def setdepth_neight(self):
        """保存邻区深度设置，并在工具已激活时自动重激活"""
        depth = self.spinBox_depth.value()
        try:
            readconfig.write_ini_file('NeighborSettings', 'depth', str(depth))
            QMessageBox.information(self, "提示", "邻区深度配置保存成功！")
            if self.plugin is not None and hasattr(self.plugin, 'update_neighbor_tool'):
                self.plugin.update_neighbor_tool(depth)
        except Exception as e:
            QMessageBox.warning(self, "配置保存失败", f"无法保存深度设置：{str(e)}")

    def set_plugin(self, plugin):
        """供主插件注入自身引用，用于清理 dockwidget 字典"""
        self.plugin = plugin

    def closeEvent(self, event):
        """窗口关闭时清理工具并从插件字典中移除自身"""
        if self.tool is not None:
            if self.canvas.mapTool() is self.tool:
                self.canvas.unsetMapTool(self.tool)
            self.tool.deactivate()
            self.tool.deleteLater()
            self.tool = None
        if self.plugin is not None and hasattr(self.plugin, '_dockwidgets'):
            dockwidgets = self.plugin._dockwidgets
            for key, widget in list(dockwidgets.items()):
                if widget is self:
                    del dockwidgets[key]
                    break
        super().closeEvent(event)


class NeighborSelectTool(QgsMapToolEmitPoint):
    """邻区选择地图工具，支持多层邻区高亮，并将内部点属性复制到剪贴板（含点图层索引优化）"""
    _index_cache = {}      # 图层ID -> 空间索引（多边形图层）
    _cache_layer_id = None

    def __init__(self, canvas, layer, depth=1, point_layer=None):
        super().__init__(canvas)
        self.canvas = canvas
        self.layer = layer
        self.depth = max(1, depth)
        self.point_layer = point_layer
        self.setCursor(Qt.CrossCursor)

        # 高亮橡皮筋
        self.source_rubberband = None
        self.neighbor_rubberbands = []

        # 点图层空间索引缓存
        self._point_index = None
        self._point_index_layer_id = None

        # 字段自动检测
        self.field = None
        self._detect_field()
        # 连接图层删除信号（当图层被销毁时，清理引用）
        if self.layer:
            self.layer.destroyed.connect(self._on_layer_destroyed)
        # 或者连接项目图层移除信号（更稳妥）
        QgsProject.instance().layersWillBeRemoved.connect(self._on_layers_removed)
        # 连接多边形图层的修改信号，当图层数据变化时清除缓存
        if self.layer:
            self.layer.dataChanged.connect(self._on_layer_changed)
            self.layer.geometryChanged.connect(self._on_layer_changed)
            self.layer.featureAdded.connect(self._on_layer_changed)
            self.layer.featureDeleted.connect(self._on_layer_changed)
            self.layer.attributeValueChanged.connect(self._on_layer_changed)

    @classmethod
    def clear_cache(cls):
        cls._index_cache.clear()
        cls._cache_layer_id = None

    def _on_layer_destroyed(self):
        """图层被销毁时清理引用"""
        self.layer = None
        # 可以自动停用工具，避免进一步操作
        if self.canvas.mapTool() is self:
            self.canvas.unsetMapTool(self)

    def _on_layers_removed(self, layer_ids):
        """项目图层移除时，检查是否包含我们的图层"""
        if self.layer and self.layer.id() in layer_ids:
            self.layer = None
            if self.canvas.mapTool() is self:
                self.canvas.unsetMapTool(self)


    def set_point_layer(self, point_layer):
        """动态设置点图层，同时清除点图层索引缓存"""
        if self.point_layer != point_layer:
            self._point_index = None
            self._point_index_layer_id = None
        self.point_layer = point_layer

    def _on_layer_changed(self):
        """图层数据变更时使所有空间索引缓存失效"""
        self.clear_cache()
        self._point_index = None
        self._point_index_layer_id = None

    def _detect_field(self):
        if self.layer is None or not self.layer.isValid():
            return
        fields = self.layer.fields()
        for f in fields:
            if f.type() == 10:  # QVariant.String
                self.field = f.name()
                break
        if self.field is None:
            if fields.count() > 0:
                self.field = fields[0].name()
            else:
                self.field = 'name'

    def canvasReleaseEvent(self, event):
        if event.button() != Qt.LeftButton:
            return

        # 用 try 包裹所有对 self.layer 的访问
        try:
            # 检查图层对象是否存在且有效
            if self.layer is None:
                iface.messageBar().pushMessage("错误", "多边形图层未设置", level=Qgis.Warning, duration=3)
                self.canvas.unsetMapTool(self)
                return
            if not self.layer.isValid():
                iface.messageBar().pushMessage("错误", "多边形图层已无效或被删除，请重新选择图层", level=Qgis.Warning,
                                               duration=3)
                self.canvas.unsetMapTool(self)
                return
        except RuntimeError:
            # 图层对象已被删除（访问 self.layer 时触发异常）
            self.layer = None  # 清理引用
            iface.messageBar().pushMessage("错误", "多边形图层已被删除，工具自动停用", level=Qgis.Warning, duration=3)
            self.canvas.unsetMapTool(self)
            return

        map_point = event.mapPoint()
        if map_point is None:
            return

        # 再次安全转换坐标
        try:
            layer_point = self.toLayerCoordinates(self.layer, map_point)
        except Exception as e:
            QMessageBox.critical(None, "坐标转换失败", f"请检查图层投影：{str(e)}")
            return

        self._select_neighbors(layer_point)

    def _select_neighbors(self, point):
        """核心邻区搜索逻辑：高亮多边形 + 复制点属性到剪贴板"""
        try:
            self._clear_rubberbands()

            # 1. 点击选择源要素
            map_units_per_pixel = self.canvas.mapUnitsPerPixel()
            buffer_size = max(map_units_per_pixel * 5, 1e-6)
            pnt_geom = QgsGeometry.fromPointXY(point)
            pnt_buff = pnt_geom.buffer(buffer_size, 8)
            rect = pnt_buff.boundingBox()

            self.layer.removeSelection()
            self.layer.selectByRect(rect)
            sel_features = self.layer.selectedFeatures()
            if not sel_features:
                QMessageBox.information(None, "提示", "未点击到任何多边形")
                return

            source_feature = sel_features[0]
            source_geom = source_feature.geometry()
            if source_geom.isNull():
                QMessageBox.critical(None, "错误", "源要素几何无效")
                return
            source_id = source_feature.id()

            # 2. 高亮源多边形
            self.source_rubberband = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
            self.source_rubberband.setColor(QColor(0, 0, 255, 100))
            self.source_rubberband.setStrokeColor(QColor(0, 0, 255))
            self.source_rubberband.setWidth(2)
            self.source_rubberband.setToGeometry(source_geom, self.layer)

            # 3. 获取多边形图层空间索引
            layer_id = self.layer.id()
            cls = self.__class__
            if cls._cache_layer_id != layer_id or layer_id not in cls._index_cache:
                index = self._build_spatial_index()
                if index is None:
                    QMessageBox.information(None, "提示", "空间索引构建失败或取消")
                    return
                cls._index_cache[layer_id] = index
                cls._cache_layer_id = layer_id
            else:
                index = cls._index_cache[layer_id]

            # 4. 多层邻区搜索
            depth = self.depth
            current_layer_ids = {source_id}
            visited = {source_id}
            layer_colors = [
                QColor(0, 255, 0, 80),
                QColor(255, 255, 0, 80),
                QColor(255, 165, 0, 80),
                QColor(255, 0, 0, 80),
                QColor(128, 0, 128, 80),
            ]

            for level in range(1, depth + 1):
                next_layer_ids = set()
                for fid in current_layer_ids:
                    f = self.layer.getFeature(fid)
                    if not f:
                        continue
                    geom = f.geometry()
                    if geom.isNull():
                        continue
                    bbox = geom.boundingBox()
                    candidate_ids = index.intersects(bbox)
                    for cand_id in candidate_ids:
                        if cand_id in visited:
                            continue
                        cand_feat = self.layer.getFeature(cand_id)
                        if not cand_feat:
                            continue
                        cand_geom = cand_feat.geometry()
                        if cand_geom.isNull():
                            continue
                        if self._geometries_touch(geom, cand_geom):
                            visited.add(cand_id)
                            next_layer_ids.add(cand_id)
                            self.layer.select(cand_id)
                            color_idx = (level - 1) % len(layer_colors)
                            rb = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
                            rb.setColor(layer_colors[color_idx])
                            rb.setStrokeColor(layer_colors[color_idx].lighter())
                            rb.setWidth(1.5)
                            rb.setToGeometry(cand_geom, self.layer)
                            self.neighbor_rubberbands.append(rb)
                if not next_layer_ids:
                    break
                current_layer_ids = next_layer_ids

            self.canvas.refresh()

            # 5. 复制点属性（使用索引优化）
            self._copy_point_attributes_to_clipboard(visited)

        except Exception as e:
            traceback.print_exc()
            QMessageBox.critical(None, "错误", f"处理失败: {str(e)}")

    def _copy_point_attributes_to_clipboard(self, selected_polygon_ids):
        """
        将落在选中多边形内部的点要素属性复制到剪贴板（制表符分隔）
        使用点图层空间索引加速包含判断
        """
        if self.point_layer is None or not self.point_layer.isValid():
            return

        # 收集选中多边形的几何并合并
        geoms = []
        for fid in selected_polygon_ids:
            feat = self.layer.getFeature(fid)
            if feat:
                g = feat.geometry()
                if not g.isNull():
                    geoms.append(g)
        if not geoms:
            return

        try:
            union_geom = QgsGeometry.unaryUnion(geoms)
            if union_geom.isNull():
                return
        except Exception:
            union_geom = None

        if union_geom is None:
            # 回退：合并失败则逐个多边形处理（效率较低，保留备用）
            point_features = []
            field_names = [field.name() for field in self.point_layer.fields()]
            for pfeat in self.point_layer.getFeatures():
                pt_geom = pfeat.geometry()
                if not pt_geom:
                    continue
                for g in geoms:
                    if g.contains(pt_geom):
                        point_features.append(pfeat)
                        break
            if not point_features:
                iface.messageBar().pushMessage("邻区选择", "没有点要素落在选中的多边形内", level=Qgis.Warning, duration=3)
                return
            # 构建剪贴板文本
            lines = ["\t".join(field_names)]
            for pfeat in point_features:
                attrs = [str(pfeat.attribute(fname)) for fname in field_names]
                lines.append("\t".join(attrs))
            text = "\n".join(lines)
            clipboard = QApplication.clipboard()
            clipboard.setText(text)
            iface.messageBar().pushMessage("邻区选择",
                                           f"已将 {len(point_features)} 个点要素的属性复制到剪贴板",
                                           level=Qgis.Success, duration=3)
            return

        # ========== 优化部分：使用点图层空间索引加速包含判断 ==========
        # 若点图层索引未建立或已失效，则重建（带进度条）
        if (self._point_index is None or
                self._point_index_layer_id != self.point_layer.id()):
            total_pts = self.point_layer.featureCount()
            pt_progress = QProgressDialog("正在构建点图层索引...", None, 0, total_pts, self.canvas)
            pt_progress.setWindowModality(Qt.WindowModal)
            pt_progress.setMinimumDuration(0)
            pt_progress.setValue(0)

            self._point_index = QgsSpatialIndex()
            features = self.point_layer.getFeatures()
            for i, f in enumerate(features):
                self._point_index.addFeature(f)
                if i % 50 == 0 or i == total_pts - 1:
                    pt_progress.setValue(i + 1)
                    QgsApplication.processEvents()
                    if pt_progress.wasCanceled():
                        break
            pt_progress.setValue(total_pts)
            self._point_index_layer_id = self.point_layer.id()

            # 用索引快速获取候选点
        candidate_ids = self._point_index.intersects(union_geom.boundingBox())

        # 精确判断包含关系
        point_features = []
        field_names = [field.name() for field in self.point_layer.fields()]
        for fid in candidate_ids:
            pfeat = self.point_layer.getFeature(fid)
            pt_geom = pfeat.geometry()
            if pt_geom and union_geom.contains(pt_geom):
                point_features.append(pfeat)

        if not point_features:
            iface.messageBar().pushMessage("邻区选择", "没有点要素落在选中的多边形内", level=Qgis.Warning, duration=3)
            return

        # 构建制表符分隔文本
        lines = ["\t".join(field_names)]
        for pfeat in point_features:
            attrs = [str(pfeat.attribute(fname)) for fname in field_names]
            lines.append("\t".join(attrs))
        text = "\n".join(lines)

        clipboard = QApplication.clipboard()
        clipboard.setText(text)
        iface.messageBar().pushMessage("邻区选择",
                                       f"已将 {len(point_features)} 个点要素的属性复制到剪贴板",
                                       level=Qgis.Success, duration=3)

    @staticmethod
    def _geometries_touch(geom1, geom2):
        if geom1.touches(geom2):
            return True
        if geom1.intersects(geom2):
            if geom1.equals(geom2):
                return False
            if geom1.distance(geom2) < 1e-6:
                return True
        return False

    def _clear_rubberbands(self):
        if self.source_rubberband is not None:
            self.canvas.scene().removeItem(self.source_rubberband)
            self.source_rubberband = None
        for rb in self.neighbor_rubberbands:
            self.canvas.scene().removeItem(rb)
        self.neighbor_rubberbands.clear()

    def _build_spatial_index(self):
        if self.layer is None:
            return None
        total = self.layer.featureCount()
        if total == 0:
            return None
        progress = QProgressDialog("正在构建空间索引...", "取消", 0, total, self.canvas)
        progress.setWindowModality(Qt.WindowModal)
        progress.setMinimumDuration(0)
        update_step = max(1, int(total * 0.05))
        next_update = update_step
        index = QgsSpatialIndex()
        features = self.layer.getFeatures()
        count = 0
        for feat in features:
            index.addFeature(feat)
            count += 1
            if count >= next_update or count == total:
                progress.setValue(count)
                QgsApplication.processEvents()
                if progress.wasCanceled():
                    progress.close()
                    return None
                if count < total:
                    next_update = min(count + update_step, total)
        progress.setValue(total)
        QgsApplication.processEvents()
        progress.close()
        return index

    def deactivate(self):
        self._clear_rubberbands()
        if self.layer is not None:
            try:
                if self.layer.isValid():

                    self.layer.dataChanged.disconnect(self._on_layer_changed)
                    self.layer.geometryChanged.disconnect(self._on_layer_changed)
                    self.layer.featureAdded.disconnect(self._on_layer_changed)
                    self.layer.featureDeleted.disconnect(self._on_layer_changed)
                    self.layer.attributeValueChanged.disconnect(self._on_layer_changed)
                    self.layer.destroyed.disconnect(self._on_layer_destroyed)
            except (RuntimeError, TypeError, AttributeError):
                pass
        # 清理点图层索引
        self._point_index = None
        self._point_index_layer_id = None
        super().deactivate()

    @staticmethod
    def clear_all_rubberbands(canvas):
        scene = canvas.scene()
        items_to_remove = []
        for item in scene.items():
            if isinstance(item, (QgsRubberBand, QgsVertexMarker)):
                items_to_remove.append(item)
        for item in items_to_remove:
            scene.removeItem(item)
        canvas.refresh()