import numpy as np
from qgis.core import *
from qgis.core import QgsMessageLog
from qgis.core import Qgis

from geogst.core.geometries.projections.geodetic import *


def distance_projected_pts(
        x,
        y,
        delta_x,
        delta_y,
        src_crs,
        dest_crs
):
    qgspt_start_src_crs = qgs_pt(x, y)
    qgspt_end_src_crs = qgs_pt(x + delta_x, y + delta_y)

    qgspt_start_dest_crs = project_qgs_point(qgspt_start_src_crs, src_crs, dest_crs)
    qgspt_end_dest_crs = project_qgs_point(qgspt_end_src_crs, src_crs, dest_crs)

    pt2_start_dest_crs = Point(
        qgspt_start_dest_crs.x(),
        qgspt_start_dest_crs.y()
    )

    pt2d_end_dest_crs = Point(
        qgspt_end_dest_crs.x(),
        qgspt_end_dest_crs.y()
    )

    return pt2_start_dest_crs.distance(pt2d_end_dest_crs)


def project_point(
        pt: Point,
        srcCrs: QgsCoordinateReferenceSystem,
        destCrs: QgsCoordinateReferenceSystem
) -> Point:

    qgs_pt = QgsPointXY(pt.x, pt.y)

    proj_qgs_pt = project_qgs_point(qgs_pt, srcCrs, destCrs)
    proj_x, proj_y = proj_qgs_pt.x(), proj_qgs_pt.y()

    return Point(
        proj_x,
        proj_y
    )


def project_qgs_point(
        qgsPt: QgsPointXY,
        srcCrs: QgsCoordinateReferenceSystem,
        destCrs: QgsCoordinateReferenceSystem
) -> QgsPointXY:

    return QgsCoordinateTransform(
        srcCrs,
        destCrs,
        QgsProject.instance()
    ).transform(qgsPt)


def read_qgis_pt_layer(
    pt_layer: QgsVectorLayer,
    field_list=None,
    fid_choice=False,
    feature_request: QgsFeatureRequest = None,
) -> List:

    if field_list is None:
        field_list = []

    # Verify layer is valid
    if not isinstance(pt_layer, QgsVectorLayer):
        QgsMessageLog.logMessage(
            f"Invalid layer type: expected QgsVectorLayer, got {type(pt_layer)}",
            "qgSurf",
            Qgis.MessageLevel.Warning
        )
        return []

    if not pt_layer.isValid():
        QgsMessageLog.logMessage(
            f"Layer '{pt_layer.name()}' is not valid",
            "qgSurf",
            Qgis.MessageLevel.Warning
        )
        return []

    # Verify provider is valid
    provider = pt_layer.dataProvider()
    if provider is None:
        QgsMessageLog.logMessage(
            f"Layer '{pt_layer.name()}' has no valid data provider",
            "qgSurf",
            Qgis.MessageLevel.Warning
        )
        return []

    # Get field indices, filtering out -1 (field not found)
    field_indices = []
    for field_name in field_list:
        if field_name:
            idx = provider.fieldNameIndex(field_name)
            if idx >= 0:
                field_indices.append(idx)
            else:
                QgsMessageLog.logMessage(
                    f"Field '{field_name}' not found in layer '{pt_layer.name()}'",
                    "qgSurf",
                    Qgis.MessageLevel.Warning
                )

    if feature_request is not None:
        features = pt_layer.getFeatures(feature_request)
    elif pt_layer.selectedFeatureCount() > 0:
        features = pt_layer.selectedFeatures()
    else:
        features = pt_layer.getFeatures()

    rec_list = []
    for feature in features:

        fid = int(feature.id())

        geom = feature.geometry()

        # a null or empty geometry does not raise here, it silently yields NaN
        # coordinates: those records would then travel through the whole
        # projection chain, to be dropped (or plotted as nonsense) far from
        # their source

        if geom is None or geom.isNull() or geom.isEmpty():

            QgsMessageLog.logMessage(
                f"Feature {fid} has null or empty geometry, skipping",
                "qgSurf",
                Qgis.MessageLevel.Warning
            )
            continue

        pt = geom.vertexAt(0)
        x, y, z = pt.x(), pt.y(), pt.z()

        if not (np.isfinite(x) and np.isfinite(y)):

            QgsMessageLog.logMessage(
                f"Feature {fid} has undefined planar coordinates, skipping",
                "qgSurf",
                Qgis.MessageLevel.Warning
            )
            continue

        if z is None or not np.isfinite(z):
            feat_list = [x, y]
        else:
            feat_list = [x, y, z]

        if fid_choice:
            feat_list.insert(0, fid)

        # Use direct attribute access with index instead of attrs[field_ndx].name()
        for field_ndx in field_indices:
            feat_list.append(feature.attribute(field_ndx))

        rec_list.append(feat_list)

    return rec_list


def qgs_pt(x, y):

    return QgsPointXY(x, y)


def calculate_pts_in_projection(pts_in_orig_crs, srcCrs, destCrs):

    pts_in_prj_crs = []

    for pt in pts_in_orig_crs:
        qgs_pt = QgsPointXY(pt.x, pt.y)
        qgs_pt_prj_crs = project_qgs_point(qgs_pt, srcCrs, destCrs)
        pts_in_prj_crs.append(
            Point(
                qgs_pt_prj_crs.x(),
                qgs_pt_prj_crs.y()
            )
        )

    return pts_in_prj_crs

