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

Detects common point 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)
from .base import BaseChecker


class PointDuplicateChecker(BaseChecker):
    """Detect duplicate point locations."""
    name = "point_duplicates"
    display_name_en = "Duplicate Points"
    display_name_vi = "Diem trung nhau"
    geom_type = "point"

    def check(self, layer, params):
        tol = params.get("dup_point_tol", 0.001)
        points = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            pt = geom.asPoint()
            points.append((feat.id(), pt))

        errors = []
        seen = set()
        for i, (fid_a, pt_a) in enumerate(points):
            if fid_a in seen:
                continue
            for j in range(i + 1, len(points)):
                fid_b, pt_b = points[j]
                if fid_b in seen:
                    continue
                if abs(pt_a.x() - pt_b.x()) < tol and abs(pt_a.y() - pt_b.y()) < tol:
                    errors.append(QgsGeometry.fromPointXY(
                        QgsPointXY(pt_b.x(), pt_b.y())))
                    seen.add(fid_b)
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} duplicate points"]}

    def fix(self, input_path, output_path, params):
        tol = params.get("dup_point_tol", 0.001)
        layer = QgsVectorLayer(input_path, "in", "ogr")
        points = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            pt = geom.asPoint()
            points.append((feat.id(), pt))

        to_delete = []
        seen = set()
        for i, (fid_a, pt_a) in enumerate(points):
            if fid_a in seen:
                continue
            for j in range(i + 1, len(points)):
                fid_b, pt_b = points[j]
                if fid_b in seen:
                    continue
                if abs(pt_a.x() - pt_b.x()) < tol and abs(pt_a.y() - pt_b.y()) < tol:
                    to_delete.append(fid_b)
                    seen.add(fid_b)
        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 PointInvalidCoordsChecker(BaseChecker):
    """Detect points with invalid or out-of-range coordinates."""
    name = "point_invalid_coords"
    display_name_en = "Invalid Coordinates"
    display_name_vi = "Toa do khong hop le"
    geom_type = "point"

    def check(self, layer, params):
        errors = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                errors.append(QgsGeometry())
                continue
            pt = geom.asPoint()
            # Check for NaN, Inf, or obviously wrong coords
            x, y = pt.x(), pt.y()
            if x != x or y != y or abs(x) > 1e10 or abs(y) > 1e10:  # noqa: E712
                errors.append(geom.clone())
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} invalid coordinates"]}

    def fix(self, input_path, output_path, params):
        layer = QgsVectorLayer(input_path, "in", "ogr")
        to_delete = []
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isNull():
                to_delete.append(feat.id())
                continue
            pt = geom.asPoint()
            x, y = pt.x(), pt.y()
            if x != x or y != y or abs(x) > 1e10 or abs(y) > 1e10:  # noqa: E712
                to_delete.append(feat.id())
        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 PointIsolatedChecker(BaseChecker):
    """Detect isolated points far from all others."""
    name = "point_isolated"
    display_name_en = "Isolated Points"
    display_name_vi = "Diem co lap"
    geom_type = "point"

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

        for fid, feat in features.items():
            geom = feat.geometry()
            if not geom or geom.isNull():
                continue
            pt = geom.asPoint()
            # Find nearest neighbor (not itself)
            nearest = index.nearestNeighbor(QgsPointXY(pt), 2)
            is_isolated = True
            for nid in nearest:
                if nid == fid:
                    continue
                n_geom = features[nid].geometry()
                if n_geom and not n_geom.isNull():
                    if geom.distance(n_geom) < max_dist:
                        is_isolated = False
                        break
            if is_isolated:
                errors.append(geom.clone())
        return {"error_geometries": errors, "error_features": [],
                "details": [f"{len(errors)} isolated points"]}

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