import csv
import os
import time
import traceback
from qgis.PyQt.QtCore import QThread, pyqtSignal
from qgis.PyQt.QtWidgets import QFileDialog
from qgis.core import (
    QgsFeatureRequest,
    QgsWkbTypes,
    QgsSpatialIndex,
    QgsProject,
    QgsMessageLog,
    Qgis          # ✅ 必须导入
)
from qgis.utils import iface
from qgis.PyQt import QtWidgets, uic, QtCore

# ✅ 直接导入 Shapely（不 try）
from shapely.geometry import Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
from shapely.wkb import loads as wkb_loads

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'cyanlove_calc_contain_point_in_geometry_base.ui'))


class cyanlove_calc_contain_point_in_geometry(QtWidgets.QDockWidget, FORM_CLASS):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.setupUi(self)
        self.setFloating(True)
        self.setWindowFlags(QtCore.Qt.Dialog)

        self.export_thread = None
        self.btn_calc_contain_point_geometry.clicked.connect(self.calc_contain_point_in_geometry)

    def calc_contain_point_in_geometry(self):
        """选择输出路径并启动计算线程"""
        try:
            options = QFileDialog.Options()
            options |= QFileDialog.ReadOnly
            csv_file_path, _ = QFileDialog.getSaveFileName(
                self,
                "保存CSV文件",
                "",
                "CSV文件 (*.csv);;所有文件 (*)",
                options=options
            )
            if not csv_file_path:
                return

            layer1 = self.mMapLayerComboBox.currentLayer()
            layer2 = self.mMapLayerComboBox_2.currentLayer()
            if not layer1 or not layer2:
                self.label_show.setText("请选择两个有效图层！")
                return

            # 检查第一个图层是否为多边形
            geom_type1 = layer1.wkbType()
            if geom_type1 not in (QgsWkbTypes.Polygon, QgsWkbTypes.MultiPolygon):
                self.label_show.setText("第一个图层必须是 Polygon 或 MultiPolygon 类型！")
                return

            # 检查第二个图层是否为允许的几何类型（点、线、面均可）
            geom_type2 = layer2.wkbType()
            allowed_types = (QgsWkbTypes.Point, QgsWkbTypes.MultiPoint,
                             QgsWkbTypes.LineString, QgsWkbTypes.MultiLineString,
                             QgsWkbTypes.Polygon, QgsWkbTypes.MultiPolygon)
            if geom_type2 not in allowed_types:
                self.label_show.setText("第二个图层几何类型不受支持，请使用点、线或面图层。")
                return

            self.progressBar.setRange(0, 100)
            self.progressBar.setValue(0)
            self.progressBar.repaint()
            self.progressBar.show()

            # 启动线程
            self.export_thread = ExportThread(layer1, layer2, csv_file_path)
            self.export_thread.progress.connect(self.update_progress)
            self.export_thread.updatelabel.connect(self.update_label)
            self.export_thread.finished.connect(self.export_finished)
            self.export_thread.error_occurred.connect(self.on_error)
            self.export_thread.start()
        except Exception as e:
            error_msg = f"启动计算时出错: {str(e)}\n{traceback.format_exc()}"
            self.label_show.setText("启动失败，请查看消息日志。")
            QgsMessageLog.logMessage(error_msg, "插件调试", level=Qgis.Critical)
            print(error_msg)

    def update_label(self, text):
        self.label_show.setText(text)

    def update_progress(self, value):
        try:
            self.progressBar.setValue(value)
            self.progressBar.repaint()   # 强制刷新
        except Exception as e:
            print(f"更新进度时出错: {e}")

    def export_finished(self):
        self.progressBar.setValue(100)
        self.progressBar.repaint()

    def on_error(self, error_msg):
        """接收线程中的错误并显示"""
        self.label_show.setText("计算过程中出现错误，请查看消息日志。")
        QgsMessageLog.logMessage(error_msg, "插件调试", level=Qgis.Critical)
        print(error_msg)



class ExportThread(QThread):
    progress = pyqtSignal(int)
    updatelabel = pyqtSignal(str)
    error_occurred = pyqtSignal(str)

    def __init__(self, layer1, layer2, csv_path):
        super().__init__()
        self.layer1 = layer1      # 多边形图层
        self.layer2 = layer2      # 点图层（或其他几何）
        self.csv_path = csv_path

    def _log_error(self, msg):
        full_msg = f"{msg}\n{traceback.format_exc()}"
        QgsMessageLog.logMessage(full_msg, "插件调试", level=Qgis.Critical)
        print(full_msg)
        self.error_occurred.emit(full_msg)

    def _qgsgeom_to_shapely(self, qgs_geom):
        try:
            if qgs_geom is None or qgs_geom.isEmpty():
                return None
            wkb = qgs_geom.asWkb()
            if wkb is None or len(wkb) == 0:
                return None
            wkb_bytes = bytes(wkb)
            if not wkb_bytes:
                return None
            return wkb_loads(wkb_bytes)
        except Exception as e:
            self._log_error(f"转换 QgsGeometry 到 Shapely 失败: {e}")
            return None

    def run(self):
        should_stop = False
        try:
            start = time.time()
            self.updatelabel.emit("正在构建多边形空间索引...")
            # 为多边形图层建立空间索引（用于快速候选）
            polygon_spatial_index = QgsSpatialIndex(self.layer1.getFeatures())

            fields1 = [f.name() for f in self.layer1.fields()]   # 多边形字段
            fields2 = [f.name() for f in self.layer2.fields()]   # 点字段
            header = fields1 + fields2

            total_points = self.layer2.featureCount()
            if total_points == 0:
                self.updatelabel.emit("第二个图层没有要素，无需计算。")
                self.progress.emit(100)
                return

            # 预提取所有多边形要素（Shapely几何 + 属性 + ID）
            polygon_features = []
            for feat in self.layer1.getFeatures():
                geom = feat.geometry()
                if geom and not geom.isEmpty():
                    shapely_poly = self._qgsgeom_to_shapely(geom)
                    if shapely_poly is None:
                        self._log_error(f"多边形要素 ID {feat.id()} 转换失败，中止计算。")
                        should_stop = True
                        break
                else:
                    shapely_poly = None
                attrs = {f: str(feat[f]) for f in fields1}
                polygon_features.append((shapely_poly, attrs, feat.id()))

            if should_stop:
                self.updatelabel.emit("预提取多边形要素时出错，已中止。")
                return

            self.updatelabel.emit(f"开始处理 {total_points} 个点...")

            # 用于记录至少包含一个点的多边形ID
            polygons_with_points = set()

            with open(self.csv_path, mode='w', newline='', encoding='gbk') as f:
                writer = csv.writer(f)
                writer.writerow(header)

                last_progress = -1
                # ----- 外层遍历点图层 -----
                for idx, point_feat in enumerate(self.layer2.getFeatures()):
                    if should_stop:
                        break

                    try:
                        point_geom = point_feat.geometry()
                        if point_geom is None or point_geom.isEmpty():
                            # 无效点，跳过（不输出）
                            progress = int((idx + 1) / total_points * 100)
                            if progress != last_progress:
                                self.progress.emit(progress)
                                last_progress = progress
                            continue

                        # 将点转为 Shapely
                        shapely_point = self._qgsgeom_to_shapely(point_geom)
                        if shapely_point is None:
                            self._log_error(f"点要素 ID {point_feat.id()} 转换失败，中止计算。")
                            should_stop = True
                            break

                        # 使用空间索引获取可能包含该点的多边形ID
                        candidate_poly_ids = polygon_spatial_index.intersects(point_geom.boundingBox())
                        found = False

                        # 遍历候选多边形
                        for shapely_poly, poly_attrs, poly_id in polygon_features:
                            if poly_id not in candidate_poly_ids or shapely_poly is None:
                                continue
                            try:
                                if shapely_poly.contains(shapely_point):
                                    # 找到一个包含关系，写入一行
                                    row1 = [poly_attrs[f] for f in fields1]
                                    row2 = [str(point_feat[f]) for f in fields2]
                                    writer.writerow(row1 + row2)
                                    found = True
                                    polygons_with_points.add(poly_id)
                            except Exception as e:
                                self._log_error(f"包含判断出错 (点 {point_feat.id()}, 多边形 {poly_id}): {e}")
                                should_stop = True
                                break

                        if should_stop:
                            break

                        # 更新进度（基于已处理点数）
                        progress = int((idx + 1) / total_points * 100)
                        if progress != last_progress:
                            self.progress.emit(progress)
                            last_progress = progress

                    except Exception as e:
                        self._log_error(f"处理点要素 ID {point_feat.id()} 时发生严重错误: {e}")
                        should_stop = True
                        break

                # ----- 处理没有包含任何点的多边形（输出空点行） -----
                if not should_stop:
                    self.updatelabel.emit("正在输出未包含点的多边形...")
                    for shapely_poly, poly_attrs, poly_id in polygon_features:
                        if poly_id not in polygons_with_points:
                            row1 = [poly_attrs[f] for f in fields1]
                            writer.writerow(row1 + [''] * len(fields2))

            if should_stop:
                self.updatelabel.emit("计算因错误而中止，请查看消息日志。")
                self.error_occurred.emit("计算过程中发生错误，已终止。")
            else:
                elapsed = time.time() - start
                self.updatelabel.emit(f"计算完成！耗时 {elapsed:.2f} 秒，共处理 {total_points} 个点。")
                self.progress.emit(100)

        except Exception as e:
            error_msg = f"线程运行中发生严重错误: {str(e)}\n{traceback.format_exc()}"
            self._log_error(error_msg)
            self.updatelabel.emit("计算出错，请查看消息日志。")
            self.error_occurred.emit(error_msg)