from qgis.core import (
    QgsFeature,
    QgsField,
    QgsProcessing,
    QgsProcessingMultiStepFeedback,
    QgsProcessingParameterBoolean,
    QgsProcessingParameterField,
    QgsProcessingParameterFeatureSink,
    QgsProcessingParameterMultipleLayers,
    QgsProcessingParameterNumber,
    QgsProcessingParameterDefinition,
    QgsProcessingParameterFeatureSource,
    QgsFeatureRequest,
    QgsFeatureSink,
)
from qgis.PyQt.QtCore import QVariant
import processing

from PreCourlis.core.precourlis_file import PreCourlisFileLine
from PreCourlis.processing.precourlis_algorithm import PreCourlisAlgorithm
from PreCourlis.core.interpolation_area import (
    InterpolationAreaManager,
)


class ParameterInterpolationAreas(QgsProcessingParameterDefinition):
    def __init__(self, name="", description="", parent_layer=None):
        super().__init__(name, description, optional=True)
        self.parent_layer = parent_layer
        self.setMetadata(
            {
                "widget_wrapper": (
                    "PreCourlis.processing.ui.interpolation_areas_widget_wrapper"
                    ".InterpolationAreasWidgetWrapper"
                )
            }
        )

    def type(self):
        return "interpolation_areas"

    def clone(self):
        return ParameterInterpolationAreas(
            self.name(), self.description(), self.parent_layer
        )


class InterpolateProfilesPerAreasAlgorithm(PreCourlisAlgorithm):
    SECTIONS = "SECTIONS"
    AXIS = "AXIS"
    CONSTRAINT_LINES = "CONSTRAINT_LINES"
    CREATE_CONSTRAINT_LINES_ENDS_AT_END_OF_PROFILES = (
        "CREATE_CONSTRAINT_LINES_ENDS_AT_END_OF_PROFILES"
    )
    INTERPOLATION_AREAS = "INTERPOLATION_AREAS"
    LATERAL_STEP = "LATERAL_STEP"
    ATTR_CROSS_SECTION = "ATTR_CROSS_SECTION"
    OUTPUT = "OUTPUT"

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.SECTIONS,
                self.tr("Sections"),
                types=[QgsProcessing.TypeVectorLine],
                defaultValue=None,
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.AXIS,
                self.tr("Axis"),
                types=[QgsProcessing.TypeVectorLine],
                defaultValue=None,
            )
        )
        self.addParameter(
            QgsProcessingParameterMultipleLayers(
                self.CONSTRAINT_LINES,
                self.tr("Contraint lines"),
                layerType=QgsProcessing.TypeVectorLine,
                defaultValue=None,
                optional=True,
            )
        )
        self.addParameter(
            QgsProcessingParameterBoolean(
                self.CREATE_CONSTRAINT_LINES_ENDS_AT_END_OF_PROFILES,
                self.tr(
                    "Create constraint lines at profiles ends"
                    " (avoid errors when some constraint lines doesn't"
                    " cross all profiles)"
                ),
                defaultValue=False,
            )
        )
        self.addParameter(
            ParameterInterpolationAreas(
                self.INTERPOLATION_AREAS,
                self.tr("Areas"),
                parent_layer=self.SECTIONS,
            )
        )
        self.addParameter(
            QgsProcessingParameterNumber(
                self.LATERAL_STEP,
                self.tr("Lateral space step (in m)"),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=1.0,
            )
        )
        self.addParameter(
            QgsProcessingParameterField(
                self.ATTR_CROSS_SECTION,
                self.tr("Attribute to identify cross-sections"),
                parentLayerParameterName=self.SECTIONS,
                defaultValue="sec_id",
            )
        )
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr("Interpolated profiles"),
            )
        )

    def checkParameterValues(self, parameters, context):
        ok, msg = super().checkParameterValues(parameters, context)

        sections = self.parameterAsVectorLayer(parameters, self.SECTIONS, context)
        if sections is None or sections.isValid() is not True:
            return False, self.tr("Sections parameter is not set or invalid")

        ia = InterpolationAreaManager.readInterpolationAreasFromLayer(sections)
        ok, message = InterpolationAreaManager.validateInterpolationAreasOfLayer(
            ia,
            sections,
        )
        if not ok:
            return ok, message

        axis = self.parameterAsVectorLayer(parameters, self.AXIS, context)
        if axis is None or axis.isValid() is not True:
            return False, self.tr("Axis parameter is not set or invalid")

        return ok, msg

    def processAlgorithm(self, parameters, context, model_feedback):
        # Get parameters values
        sections = self.parameterAsVectorLayer(parameters, self.SECTIONS, context)

        axis = self.parameterAsVectorLayer(parameters, self.AXIS, context)

        lateral_space_step_in_meter = self.parameterAsDouble(
            parameters, self.LATERAL_STEP, context
        )

        source = self.parameterAsSource(parameters, self.SECTIONS, context)
        file = PreCourlisFileLine(source)
        fields = PreCourlisFileLine.base_fields()
        for layer in file.layers():
            fields.append(QgsField(layer, QVariant.String, len=100000))

        feature_sink, dest_id = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            fields,
            sections.wkbType(),
            sections.sourceCrs(),
        )

        # Get back interpolation areas from profiles layer custom properties
        interpolation_areas = InterpolationAreaManager.readInterpolationAreasFromLayer(
            sections,
        )

        # Use a multi-step feedback, so that individual child algorithm progress reports
        # are adjusted for the overall progress through the model
        NUMBER_OF_IA = len(interpolation_areas)
        feedback = QgsProcessingMultiStepFeedback(NUMBER_OF_IA, model_feedback)
        outputs = {}

        ia = 0
        sec_id = 1
        sec_id_index = fields.indexFromName("sec_id")

        # Check that interpolation_areas list are in the right order
        # TODO

        # Processing step :
        for step, interpolation_area in enumerate(interpolation_areas):
            # Check for cancel event
            feedback.setCurrentStep(step)
            if feedback.isCanceled():
                return {}

            # Const variables
            FROM_PROFIL = interpolation_area.from_profil_id
            TO_PROFIL = interpolation_area.to_profil_id
            LONGITUDINAL_STEP = interpolation_area.longitudinal_step

            # Extract by expression from FROM_PROFIL sec_id to TO_PROFIL sec_id
            alg_params = {
                "EXPRESSION": f"sec_id >= {FROM_PROFIL} and sec_id <= {TO_PROFIL}",
                "INPUT": parameters[self.SECTIONS],
                "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
            }

            EXTRACT_BY_EXPRESSION_NAME = f"ExtractFrom{FROM_PROFIL}To{TO_PROFIL}"
            outputs[EXTRACT_BY_EXPRESSION_NAME] = processing.run(
                "native:extractbyexpression",
                alg_params,
                context=context,
                feedback=feedback,
                is_child_algorithm=True,
            )

            # Interpolate lines
            INTERPOLATE_LINES_NAME = f"InterpolateLinesFrom{FROM_PROFIL}To{TO_PROFIL}"
            alg_params = {
                "SECTIONS": outputs[EXTRACT_BY_EXPRESSION_NAME]["OUTPUT"],
                "AXIS": axis,
                "CONSTRAINT_LINES": parameters.get(self.CONSTRAINT_LINES),
                "CREATE_CONSTRAINT_LINES_ENDS_AT_END_OF_PROFILES": parameters.get(
                    self.CREATE_CONSTRAINT_LINES_ENDS_AT_END_OF_PROFILES
                ),
                "LAT_STEP": lateral_space_step_in_meter,
                "LONG_STEP": LONGITUDINAL_STEP,
                "ATTR_CROSS_SECTION": parameters[self.ATTR_CROSS_SECTION],
                "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
            }

            outputs[INTERPOLATE_LINES_NAME] = processing.run(
                "precourlis:interpolate_lines",
                alg_params,
                context=context,
                feedback=feedback,
                is_child_algorithm=True,
            )

            interpolated_lines_layer = context.temporaryLayerStore().mapLayer(
                outputs[INTERPOLATE_LINES_NAME]["OUTPUT"]
            )

            # remove duplicates (but keep master profiles) and update sec_id
            orderby_sec_id_request = QgsFeatureRequest().addOrderBy(
                '"sec_id"', ascending=True
            )
            features_orderby_sec_id = interpolated_lines_layer.getFeatures(
                orderby_sec_id_request
            )
            NUMBER_OF_FEATURES = interpolated_lines_layer.featureCount()

            for i, feature in enumerate(features_orderby_sec_id):
                if i < (NUMBER_OF_FEATURES - 1) or ia == (NUMBER_OF_IA - 1):
                    # add each profiles to sink except the last one
                    # but we still add the last one of the last interpolation area
                    out_feature = QgsFeature()
                    out_feature.setAttributes(
                        [feature.attribute(field.name()) for field in fields]
                    )
                    out_feature.setAttribute(sec_id_index, sec_id)
                    out_feature.setGeometry(feature.geometry())

                    sec_id += 1

                    feature_sink.addFeature(
                        out_feature,
                        QgsFeatureSink.FastInsert,
                    )

            ia += 1

        # Keep "master" profiles (OK) and show this information (how ?) TODO

        # Return output layer id
        return {self.OUTPUT: dest_id}

    def name(self):
        return "interpolate_profiles_per_areas"

    def displayName(self):
        return self.tr("Interpolate profiles per areas")

    def group(self):
        return self.tr("Interpolate")

    def groupId(self):
        return "Interpolate"

    def createInstance(self):
        return InterpolateProfilesPerAreasAlgorithm()
