import csv
import os
import time
from qgis.PyQt.QtCore import QVariant, QThread, pyqtSignal
from qgis.PyQt.QtWidgets import QFileDialog
from qgis.core import (
    QgsWkbTypes, QgsProject, QgsGeometry, QgsVectorLayer, QgsField,
    QgsFeature, QgsDistanceArea, QgsSpatialIndex, QgsPointXY
)
from qgis.utils import iface
from qgis.PyQt import QtWidgets, uic, QtCore

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


class cyanlove_calc_point_min_geometry_distince_v2(QtWidgets.QDockWidget, FORM_CLASS):
    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        super(cyanlove_calc_point_min_geometry_distince_v2, self).__init__(parent)
        self.export_thread = None
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.setupUi(self)
        self.setFloating(True)
        self.property_dict = None
        self.is_running = False
        self.setWindowFlags(QtCore.Qt.Dialog)

        self.btn_calc_point_distinct_geometry.clicked.connect(self.calc_point_min_distinct_geometry)

    def calc_point_min_distinct_geometry(self):
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        csv_file_path, _ = QFileDialog.getSaveFileName(self,
                                                       "选择文件",
                                                       "",
                                                       "CSV文件 (*.csv);;所有文件 (*)",
                                                       options=options)
        if not csv_file_path:
            return

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

        geom_type1 = layer1.geometryType()  # 0=点,1=线,2=面
        geom_type2 = layer2.geometryType()

        # 允许的组合: (点, 线), (点, 面), (线, 点), (面, 点)
        valid = False
        if geom_type1 == 0 and geom_type2 in (1, 2):  # 点 -> 线/面
            valid = True
        elif geom_type1 == 1 and geom_type2 == 0:  # 线 -> 点
            valid = True
        elif geom_type1 == 2 and geom_type2 == 0:  # 面 -> 点
            valid = True

        if not valid:
            self.label_show.setText("不支持的图层组合。请使用：点+线/面，线+点，或面+点。")
            return

        self.progressBar.setRange(0, 100)
        self.progressBar.setValue(0)
        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.start()

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

    def update_progress(self, value):
        self.progressBar.setValue(value)

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


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

    def __init__(self, layer1, layer2, csv_file_path):
        super().__init__()
        self.layer1 = layer1
        self.layer2 = layer2
        self.csv_file_path = csv_file_path

    def run(self):
        try:
            geom_type1 = self.layer1.geometryType()
            geom_type2 = self.layer2.geometryType()

            if geom_type1 == 0 and geom_type2 in (1, 2):
                self._run_point_to_line_or_polygon()
            elif geom_type1 == 1 and geom_type2 == 0:
                self._run_line_to_point()
            elif geom_type1 == 2 and geom_type2 == 0:
                self._run_polygon_to_point()
            else:
                self.updatelabel.emit(f"不支持的组合: 图层1类型={geom_type1}, 图层2类型={geom_type2}")
        except Exception as e:
            self.updatelabel.emit(f"运行错误: {str(e)}")
            import traceback
            traceback.print_exc()

    # ================= 原有功能：点 → 线/面 =================
    def _run_point_to_line_or_polygon(self):
        start_time = time.time()
        layer1 = self.layer1  # 点
        layer2 = self.layer2  # 线或面

        if layer1.featureCount() == 0:
            self.updatelabel.emit("错误：点图层没有要素！")
            return
        if layer2.featureCount() == 0:
            self.updatelabel.emit("错误：线/面图层没有要素！")
            return

        fields1 = [f.name() for f in layer1.fields()]
        fields2 = [f.name() for f in layer2.fields()]
        out_fields = fields1 + fields2 + ["最短距离(米)", "最近点经度", "最近点纬度"]

        total_points = layer1.featureCount()
        is_polygon = (layer2.geometryType() == 2)

        self.updatelabel.emit(f"开始计算，共 {total_points} 个点，目标要素 {layer2.featureCount()} 个...")

        distance_area = QgsDistanceArea()
        distance_area.setEllipsoid('WGS84')
        distance_area.setSourceCrs(layer1.crs(), QgsProject.instance().transformContext())

        # 建立目标索引
        target_index = QgsSpatialIndex(layer2.getFeatures())
        target_features = {f.id(): f for f in layer2.getFeatures()}

        line_layer = QgsVectorLayer(f"LineString?crs={layer1.crs().authid()}", "最短连接线", "memory")
        line_provider = line_layer.dataProvider()
        line_provider.addAttributes([
            QgsField("source_id", QVariant.Int),
            QgsField("distance_m", QVariant.Double),
            QgsField("nearest_x", QVariant.Double),
            QgsField("nearest_y", QVariant.Double)
        ])
        line_layer.updateFields()
        line_feat_list = []

        found_count = 0
        candidate_count = min(10, len(target_features))
        if candidate_count == 0:
            self.updatelabel.emit("错误：目标图层没有有效要素")
            return

        with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
            writer = csv.writer(csv_file)
            writer.writerow(out_fields)

            for idx, point_feat in enumerate(layer1.getFeatures()):
                point_geom = point_feat.geometry()
                row = [str(point_feat[f]) if point_feat[f] is not None else "" for f in fields1]

                if point_geom is None or point_geom.isEmpty():
                    row.extend([""] * (len(fields2) + 3))
                    writer.writerow(row)
                    self._update_progress(idx, total_points)
                    continue

                # 面图层：先判断是否在内部
                containing_feat = None
                if is_polygon:
                    candidate_ids = target_index.intersects(point_geom.boundingBox())
                    for fid in candidate_ids:
                        feat = target_features.get(fid)
                        if feat and feat.geometry() and feat.geometry().contains(point_geom):
                            containing_feat = feat
                            break

                if containing_feat is not None:
                    for fname in fields2:
                        val = containing_feat[fname]
                        row.append(str(val) if val is not None else "")
                    row.append("0")
                    row.append("")
                    row.append("")
                    writer.writerow(row)
                    found_count += 1
                    self._update_progress(idx, total_points)
                    continue

                # 计算最近距离
                nearest_ids = target_index.nearestNeighbor(point_geom.asPoint(), candidate_count)
                best_feat = None
                best_dist = float('inf')
                best_line = None
                best_pt = None

                for fid in nearest_ids:
                    feat = target_features.get(fid)
                    if not feat:
                        continue
                    geom = feat.geometry()
                    if not geom or geom.isEmpty():
                        continue
                    line = point_geom.shortestLine(geom)
                    if line is None or line.isEmpty():
                        continue
                    dist = distance_area.measureLength(line)
                    pts = line.asPolyline()
                    nearest_pt = pts[-1] if pts else point_geom.asPoint()
                    if dist < best_dist:
                        best_dist = dist
                        best_feat = feat
                        best_line = line
                        best_pt = nearest_pt

                if best_feat is None:
                    row.extend([""] * (len(fields2) + 3))
                    writer.writerow(row)
                    self._update_progress(idx, total_points)
                    continue

                found_count += 1
                for fname in fields2:
                    val = best_feat[fname]
                    row.append(str(val) if val is not None else "")
                row.append(f"{best_dist:.2f}")
                row.append(f"{best_pt.x():.6f}" if best_pt else "")
                row.append(f"{best_pt.y():.6f}" if best_pt else "")
                writer.writerow(row)

                if best_line and not best_line.isEmpty():
                    line_feat = QgsFeature()
                    line_feat.setGeometry(best_line)
                    line_feat.setAttributes([point_feat.id(), best_dist,
                                             best_pt.x() if best_pt else None,
                                             best_pt.y() if best_pt else None])
                    line_feat_list.append(line_feat)

                self._update_progress(idx, total_points)
                if (idx + 1) % max(1, total_points // 10) == 0:
                    self.updatelabel.emit(f"已处理 {idx + 1}/{total_points} 点，成功匹配 {found_count} 个")

        if line_feat_list:
            line_provider.addFeatures(line_feat_list)
            line_layer.updateExtents()
            QgsProject.instance().addMapLayer(line_layer)
            self.updatelabel.emit(f"已生成最短连接线图层，共 {len(line_feat_list)} 条线")

        self.updatelabel.emit(
            f"计算完成！共 {total_points} 点，成功匹配 {found_count} 点。"
        )

    # ================= 原有功能：线 → 点 =================
    def _run_line_to_point(self):
        start_time = time.time()
        line_layer = self.layer1  # 线
        point_layer = self.layer2  # 点

        if line_layer.featureCount() == 0:
            self.updatelabel.emit("错误：线图层没有要素！")
            return
        if point_layer.featureCount() == 0:
            self.updatelabel.emit("错误：点图层没有要素！")
            return

        line_fields = [f.name() for f in line_layer.fields()]
        point_fields = [f.name() for f in point_layer.fields()]
        out_fields = line_fields + point_fields + ["最短距离(米)", "线上最近点经度", "线上最近点纬度"]

        total_lines = line_layer.featureCount()
        self.updatelabel.emit(f"开始计算，共 {total_lines} 条线，点要素 {point_layer.featureCount()} 个...")

        distance_area = QgsDistanceArea()
        distance_area.setEllipsoid('WGS84')
        distance_area.setSourceCrs(line_layer.crs(), QgsProject.instance().transformContext())

        # 建立点的空间索引
        point_index = QgsSpatialIndex(point_layer.getFeatures())
        point_features = {f.id(): f for f in point_layer.getFeatures()}
        self.updatelabel.emit(f"已加载 {len(point_features)} 个点，索引建立完成")

        # 内存线图层（连接线上最近点到点的线段）
        conn_layer = QgsVectorLayer(f"LineString?crs={line_layer.crs().authid()}", "线到点连接线", "memory")
        conn_provider = conn_layer.dataProvider()
        conn_provider.addAttributes([
            QgsField("line_id", QVariant.Int),
            QgsField("point_id", QVariant.Int),
            QgsField("distance_m", QVariant.Double),
            QgsField("nearest_on_line_x", QVariant.Double),
            QgsField("nearest_on_line_y", QVariant.Double)
        ])
        conn_layer.updateFields()
        conn_feat_list = []

        found_count = 0
        # 候选点数量：取 min(总点数, 20) 因为每条线可能附近点多，增大候选数
        candidate_count = min(20, len(point_features))
        if candidate_count == 0:
            self.updatelabel.emit("错误：点图层没有有效要素")
            return

        with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
            writer = csv.writer(csv_file)
            writer.writerow(out_fields)

            for idx, line_feat in enumerate(line_layer.getFeatures()):
                line_geom = line_feat.geometry()
                row = [str(line_feat[f]) if line_feat[f] is not None else "" for f in line_fields]

                if line_geom is None or line_geom.isEmpty():
                    row.extend([""] * (len(point_fields) + 3))
                    writer.writerow(row)
                    self._update_progress(idx, total_lines)
                    continue

                # 用包围盒获取候选点
                bbox = line_geom.boundingBox()
                # 稍微扩大包围盒，避免漏掉紧邻的点
                bbox = bbox.buffered(0.0001)  # 约10米，根据坐标单位调整，仅用于筛选
                candidate_point_ids = point_index.intersects(bbox)
                if not candidate_point_ids:
                    # 无候选点，但仍需计算最近？直接全量遍历？为了性能，取最近邻1个
                    nearest_ids = point_index.nearestNeighbor(line_geom.asPoint(), 1)
                    candidate_point_ids = nearest_ids

                # 如果候选点太多，限制数量（最多100）
                if len(candidate_point_ids) > 100:
                    candidate_point_ids = candidate_point_ids[:100]

                best_point_feat = None
                best_distance = float('inf')
                best_line_on_line = None  # 线上最近点到点的连线（线段）
                best_nearest_on_line = None  # 线上最近点坐标

                for pid in candidate_point_ids:
                    point_feat = point_features.get(pid)
                    if not point_feat:
                        continue
                    point_geom = point_feat.geometry()
                    if point_geom is None or point_geom.isEmpty():
                        continue

                    # 计算点到线的最短连线（从点到线）
                    line_to_point = point_geom.shortestLine(line_geom)
                    if line_to_point is None or line_to_point.isEmpty():
                        continue
                    dist = distance_area.measureLength(line_to_point)
                    # 线上最近点是线段的终点（从点出发到线的终点）
                    pts = line_to_point.asPolyline()
                    if len(pts) >= 2:
                        nearest_on_line = pts[1]  # 终点（线上点）
                    else:
                        nearest_on_line = pts[0] if pts else None

                    if dist < best_distance:
                        best_distance = dist
                        best_point_feat = point_feat
                        best_line_on_line = line_to_point
                        best_nearest_on_line = nearest_on_line

                if best_point_feat is None:
                    row.extend([""] * (len(point_fields) + 3))
                    writer.writerow(row)
                    self._update_progress(idx, total_lines)
                    continue

                found_count += 1
                # 写入点属性
                for fname in point_fields:
                    val = best_point_feat[fname]
                    row.append(str(val) if val is not None else "")
                row.append(f"{best_distance:.2f}")
                row.append(f"{best_nearest_on_line.x():.6f}" if best_nearest_on_line else "")
                row.append(f"{best_nearest_on_line.y():.6f}" if best_nearest_on_line else "")
                writer.writerow(row)

                # 添加连接线（从线上最近点到点）
                if best_line_on_line and not best_line_on_line.isEmpty():
                    conn_feat = QgsFeature()
                    conn_feat.setGeometry(best_line_on_line)
                    conn_feat.setAttributes([
                        line_feat.id(),
                        best_point_feat.id(),
                        best_distance,
                        best_nearest_on_line.x() if best_nearest_on_line else None,
                        best_nearest_on_line.y() if best_nearest_on_line else None
                    ])
                    conn_feat_list.append(conn_feat)

                self._update_progress(idx, total_lines)
                if (idx + 1) % max(1, total_lines // 10) == 0:
                    self.updatelabel.emit(f"已处理 {idx + 1}/{total_lines} 条线，成功匹配 {found_count} 个")

        if conn_feat_list:
            conn_provider.addFeatures(conn_feat_list)
            conn_layer.updateExtents()
            QgsProject.instance().addMapLayer(conn_layer)
            self.updatelabel.emit(f"已生成线到点连接线图层，共 {len(conn_feat_list)} 条线")

        self.updatelabel.emit(
            f"计算完成！共 {total_lines} 条线，成功匹配 {found_count} 条。"

        )

    # ================= 新增功能：面 → 点 =================
    def _run_polygon_to_point(self):
        start_time = time.time()
        polygon_layer = self.layer1  # 面
        point_layer = self.layer2  # 点

        try:
            # 1. 基础检查
            self.updatelabel.emit("开始检查图层有效性...")

            if polygon_layer.featureCount() == 0:
                self.updatelabel.emit("错误：面图层没有要素！")
                return
            if point_layer.featureCount() == 0:
                self.updatelabel.emit("错误：点图层没有要素！")
                return

            # 2. 获取字段列表
            polygon_fields = [f.name() for f in polygon_layer.fields()]
            point_fields = [f.name() for f in point_layer.fields()]
            out_fields = polygon_fields + point_fields + ["最短距离(米)", "面上最近点经度", "面上最近点纬度",
                                                          "是否在面内"]

            total_polygons = polygon_layer.featureCount()
            self.updatelabel.emit(f"开始计算，共 {total_polygons} 个面，点要素 {point_layer.featureCount()} 个...")

            # 3. 设置距离计算器
            distance_area = QgsDistanceArea()
            distance_area.setEllipsoid('WGS84')
            distance_area.setSourceCrs(polygon_layer.crs(), QgsProject.instance().transformContext())

            # 4. 建立点的空间索引
            self.updatelabel.emit("正在建立点的空间索引...")
            point_index = QgsSpatialIndex(point_layer.getFeatures())
            point_features = {f.id(): f for f in point_layer.getFeatures()}
            self.updatelabel.emit(f"已加载 {len(point_features)} 个点，索引建立完成")

            # 5. 创建内存线图层（显示面上最近点到点的连接线）
            conn_layer = QgsVectorLayer(f"LineString?crs={polygon_layer.crs().authid()}", "面到点连接线", "memory")
            conn_provider = conn_layer.dataProvider()
            conn_provider.addAttributes([
                QgsField("polygon_id", QVariant.Int),
                QgsField("point_id", QVariant.Int),
                QgsField("distance_m", QVariant.Double),
                QgsField("nearest_on_polygon_x", QVariant.Double),
                QgsField("nearest_on_polygon_y", QVariant.Double),
                QgsField("inside", QVariant.Int)
            ])
            conn_layer.updateFields()
            conn_feat_list = []

            found_count = 0
            inside_count = 0

            # 6. 打开CSV文件准备写入
            with open(self.csv_file_path, 'w', newline='', encoding='utf-8-sig') as csv_file:
                writer = csv.writer(csv_file)
                writer.writerow(out_fields)

                for idx, polygon_feat in enumerate(polygon_layer.getFeatures()):
                    polygon_geom = polygon_feat.geometry()
                    row = [str(polygon_feat[f]) if polygon_feat[f] is not None else "" for f in polygon_fields]

                    # 检查面几何是否有效
                    if polygon_geom is None or polygon_geom.isEmpty():
                        self.updatelabel.emit(f"警告: 第 {idx + 1} 个面几何无效，已跳过")
                        row.extend([""] * (len(point_fields) + 4))
                        writer.writerow(row)
                        self._update_progress(idx, total_polygons)
                        continue

                    # 获取面的边界框用于快速筛选
                    bbox = polygon_geom.boundingBox()
                    # 稍微扩大边界框，避免遗漏边界附近的点
                    bbox = bbox.buffered(0.0005)  # 约50米，根据坐标单位调整

                    # 使用空间索引查找候选点
                    candidate_point_ids = point_index.intersects(bbox)

                    # 如果没有候选点，使用最近邻查找
                    if not candidate_point_ids:
                        # 获取面的中心点作为参考
                        centroid = polygon_geom.centroid().asPoint()
                        nearest_ids = point_index.nearestNeighbor(centroid, min(5, len(point_features)))
                        candidate_point_ids = nearest_ids
                    # 限制候选点数量以提高性能
                    if len(candidate_point_ids) > 100:
                        candidate_point_ids = candidate_point_ids[:100]

                    # 找出距离该面最近的点
                    best_point_feat = None
                    best_distance = float('inf')
                    best_line_segment = None
                    best_nearest_point = None
                    point_inside = False

                    for pid in candidate_point_ids:
                        point_feat = point_features.get(pid)
                        if not point_feat:
                            continue

                        point_geom = point_feat.geometry()
                        if point_geom is None or point_geom.isEmpty():
                            continue

                        # 检查点是否在面内
                        if polygon_geom.contains(point_geom):
                            point_inside = True
                            best_point_feat = point_feat
                            best_distance = 0
                            best_nearest_point = point_geom.asPoint()
                            # 创建从点到面的连接线（实际上就是点本身）
                            best_line_segment = point_geom
                            break  # 找到内部点就可以停止了

                        # 计算点到面的最短连线
                        point_to_polygon = point_geom.shortestLine(polygon_geom)
                        if point_to_polygon is None or point_to_polygon.isEmpty():
                            continue

                        # 测量距离
                        dist = distance_area.measureLength(point_to_polygon)

                        # 获取面上最近点（连线的终点）
                        pts = point_to_polygon.asPolyline()
                        if len(pts) >= 2:
                            nearest_on_polygon = pts[1]  # 终点（面上的点）
                        else:
                            nearest_on_polygon = pts[0] if pts else point_geom.asPoint()

                        if dist < best_distance:
                            best_distance = dist
                            best_point_feat = point_feat
                            best_line_segment = point_to_polygon
                            best_nearest_point = nearest_on_polygon
                            point_inside = False

                    if best_point_feat is None:
                        self.updatelabel.emit(f"警告: 面 {idx + 1} 未找到任何候选点，可能点图层为空或空间索引异常")
                        row.extend([""] * (len(point_fields) + 4))
                        writer.writerow(row)
                        self._update_progress(idx, total_polygons)
                        continue

                    found_count += 1
                    if point_inside:
                        inside_count += 1

                    # 写入点属性
                    for fname in point_fields:
                        val = best_point_feat[fname]
                        row.append(str(val) if val is not None else "")

                    row.append(f"{best_distance:.2f}")
                    row.append(f"{best_nearest_point.x():.6f}" if best_nearest_point else "")
                    row.append(f"{best_nearest_point.y():.6f}" if best_nearest_point else "")
                    row.append("是" if point_inside else "否")
                    writer.writerow(row)

                    # 添加连接线（仅当距离>0时，点在面内时不生成线）
                    if best_line_segment and not best_line_segment.isEmpty() and best_distance > 0:
                        conn_feat = QgsFeature()
                        conn_feat.setGeometry(best_line_segment)
                        conn_feat.setAttributes([
                            polygon_feat.id(),
                            best_point_feat.id(),
                            best_distance,
                            best_nearest_point.x() if best_nearest_point else None,
                            best_nearest_point.y() if best_nearest_point else None,
                            1 if point_inside else 0
                        ])
                        conn_feat_list.append(conn_feat)

                    # 更新进度
                    self._update_progress(idx, total_polygons)

                    # 定期输出进度信息
                    if (idx + 1) % max(1, total_polygons // 10) == 0:
                        self.updatelabel.emit(
                            f"已处理 {idx + 1}/{total_polygons} 个面，成功匹配 {found_count} 个，其中面内点 {inside_count} 个")

            # 7. 添加连接线图层
            if conn_feat_list:
                conn_provider.addFeatures(conn_feat_list)
                conn_layer.updateExtents()
                QgsProject.instance().addMapLayer(conn_layer)
                self.updatelabel.emit(f"已生成面到点连接线图层，共 {len(conn_feat_list)} 条线")
            else:
                self.updatelabel.emit("未生成连接线图层（所有点都在面内或距离为0）")

            # 8. 完成统计
            self.updatelabel.emit(
                f"计算完成！共 {total_polygons} 个面，成功匹配 {found_count} 个面。"
                f"其中点在面内的情况: {inside_count} 个。"

            )

        except Exception as e:
            error_msg = f"面到点计算错误: {str(e)}"
            self.updatelabel.emit(error_msg)
            import traceback
            traceback.print_exc()
            raise

    def _update_progress(self, current, total):
        progress_val = int((current + 1) / total * 100)
        self.progress.emit(progress_val)