# -*- coding: utf-8 -*-
"""Line topology algorithms for TopoFix.

Detects common line geometry errors in GIS vector layers.

Author: Loc Vu Trung tong hop vi dam me cung ban!
"""
from qgis.core import (
    QgsGeometry, QgsPointXY, QgsSpatialIndex,
    QgsVectorLayer, QgsVectorFileWriter,
    QgsCoordinateTransformContext, QgsWkbTypes)
from .base import BaseChecker


class LineDangleChecker(BaseChecker):
    """Detect dangling line endpoints not connected to other lines."""
    name = "line_dangles"
    display_name_en = "Dangles"
    display_name_vi = "Dau mut treo"
    geom_type = "line"

    def check(self, layer, params):
        tol = params.get("dangle_tol", 0.001)
        endpoints = []  # (fid, QgsPointXY, is_start)
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            if geom.isMultipart():
                for part in geom.asMultiPolyline():
                    if part:
                        endpoints.append(
                            (feat.id(), QgsPointXY(part[0][0], part[0][1]), True))
                        endpoints.append(
                            (feat.id(), QgsPointXY(part[-1][0], part[-1][1]), False))
            else:
                line = geom.asPolyline()
                if line:
                    endpoints.append(
                        (feat.id(), QgsPointXY(line[0][0], line[0][1]), True))
                    endpoints.append(
                        (feat.id(), QgsPointXY(line[-1][0], line[-1][1]), False))

        # Find endpoints not shared by any other line
        dangles = []
        for i, (fid_a, pt_a, _) in enumerate(endpoints):
            connected = False
            for j, (fid_b, pt_b, _) in enumerate(endpoints):
                if i == j or fid_a == fid_b:
                    continue
                if pt_a.distance(pt_b) < tol:
                    connected = True
                    break
            if not connected:
                dangles.append(QgsGeometry.fromPointXY(pt_a))

        return {"error_geometries": dangles, "error_features": [],
                "details": [f"{len(dangles)} dangle endpoints"]}

    def fix(self, input_path, output_path, params):
        # Dangles require manual review — copy as-is
        layer = QgsVectorLayer(input_path, "in", "ogr")
        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = "GPKG"
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, output_path, QgsCoordinateTransformContext(), opts)
        return output_path


class LineSelfIntersectionChecker(BaseChecker):
    """Detect lines that cross themselves."""
    name = "line_self_intersection"
    display_name_en = "Self-Intersection"
    display_name_vi = "Tu cat chinh no"
    geom_type = "line"

    def check(self, layer, params):
        errors = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            if not geom.isGeosValid():
                # Get the invalid point
                err = geom.validateGeometry()
                for e in err:
                    errors.append(QgsGeometry.fromPointXY(
                        QgsPointXY(e.where())))
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} self-intersections"]}

    def fix(self, input_path, output_path, params):
        layer = QgsVectorLayer(input_path, "in", "ogr")
        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = "GPKG"
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, output_path, QgsCoordinateTransformContext(), opts)
        return output_path


class LineDuplicateChecker(BaseChecker):
    """Detect duplicate line geometries."""
    name = "line_duplicates"
    display_name_en = "Duplicate Lines"
    display_name_vi = "Duong trung nhau"
    geom_type = "line"

    def check(self, layer, params):
        seen = {}
        errors = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            wkt = geom.asWkt(6)
            if wkt in seen:
                errors.append(geom.clone())
            else:
                seen[wkt] = feat.id()
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} duplicate lines"]}

    def fix(self, input_path, output_path, params):
        layer = QgsVectorLayer(input_path, "in", "ogr")
        seen = set()
        to_delete = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            wkt = geom.asWkt(6)
            if wkt in seen:
                to_delete.append(feat.id())
            else:
                seen.add(wkt)
        if to_delete:
            layer.dataProvider().deleteFeatures(to_delete)
        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = "GPKG"
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, output_path, QgsCoordinateTransformContext(), opts)
        return output_path


class LineOverlapChecker(BaseChecker):
    """Detect overlapping line segments between features."""
    name = "line_overlaps"
    display_name_en = "Overlapping Lines"
    display_name_vi = "Duong chong nhau"
    geom_type = "line"

    def check(self, layer, params):
        index = QgsSpatialIndex(layer.getFeatures())
        features = {f.id(): f for f in layer.getFeatures()}
        errors = []

        for fid_a, feat_a in features.items():
            geom_a = feat_a.geometry()
            if not geom_a or geom_a.isNull():
                continue
            for fid_b in index.intersects(geom_a.boundingBox()):
                if fid_b <= fid_a:
                    continue
                geom_b = features[fid_b].geometry()
                if not geom_b or geom_b.isNull():
                    continue
                inter = geom_a.intersection(geom_b)
                if inter and not inter.isNull() and inter.type() == QgsWkbTypes.LineGeometry:
                    errors.append(inter)
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} overlapping segments"]}

    def fix(self, input_path, output_path, params):
        layer = QgsVectorLayer(input_path, "in", "ogr")
        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = "GPKG"
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, output_path, QgsCoordinateTransformContext(), opts)
        return output_path
