# -*- coding: utf-8 -*-
"""📦 Single Point Buffer Workflow module.

This module contains functionality for single point buffer workflow.
"""

import os
from urllib.parse import unquote

from qgis import processing
from qgis.core import (
    Qgis,
    QgsFeedback,
    QgsField,
    QgsGeometry,
    QgsProcessingContext,
    QgsVectorLayer,
)
from qgis.PyQt.QtCore import QVariant

from geest.core import JsonTreeItem
from geest.core.workflows.mappings import MAPPING_REGISTRY
from geest.utilities import log_message

from .workflow_base import WorkflowBase


class SinglePointBufferWorkflow(WorkflowBase):
    """
    Concrete implementation of a 'use_single_buffer_point' workflow.
    """

    def __init__(
        self,
        item: JsonTreeItem,
        cell_size_m: float,
        analysis_scale: str,
        feedback: QgsFeedback,
        context: QgsProcessingContext,
        working_directory: str = None,
    ):
        """
        Initialize the workflow with attributes and feedback.
        :param item: JsonTreeItem representing the analysis, dimension, or factor to process.
        :param cell_size_m: Cell size in meters
        :param analysis_scale: Scale of the analysis, e.g., 'local', 'national'.
        :param feedback: QgsFeedback object for progress reporting and cancellation.
        :param context: QgsProcessingContext object for processing. This can be used to pass objects to the thread. e.g. the QgsProject Instance
        :param working_directory: Folder containing study_area.gpkg and where the outputs will be placed. If not set will be taken from QSettings.
        """
        super().__init__(
            item, cell_size_m, analysis_scale, feedback, context, working_directory
        )  # ⭐️ Item is a reference - whatever you change in this item will directly update the tree
        self.workflow_name = "use_single_buffer_point"

        layer_source = self.attributes.get("single_buffer_point_shapefile", None)
        if layer_source is not None:
            layer_source = unquote(layer_source)
        provider_type = "ogr"
        if not layer_source:
            layer_source = self.attributes.get("single_buffer_point_layer_source", None)
            provider_type = self.attributes.get("single_buffer_point_layer_provider_type", "ogr")
        if not layer_source:
            log_message(
                "single_buffer_point_shapefile not found",
                tag="GeoE3",
                level=Qgis.Critical,
            )
            return False
        self.features_layer = QgsVectorLayer(layer_source, "points", provider_type)
        if not self.features_layer.isValid():
            log_message("single_buffer_point_layer not valid", level=Qgis.Critical)
            log_message(f"Layer Source: {layer_source}", level=Qgis.Critical)
            return False
        factor_id = None
        if item.isIndicator() and item.parentItem:
            factor_id = item.parentItem.attribute("id", None)
        mapping_id = self.attributes.get("mapping_id")
        indicator_id = self.attributes.get("id")
        mapping = MAPPING_REGISTRY.get(factor_id or mapping_id or indicator_id)
        config = mapping.get(analysis_scale, mapping.get("national")) if mapping else None
        mapped_distance = config.get("buffer_distance") if config else None
        self.mapped_scores = config.get("scores", {}) if config else {}

        # Load scoring method and percentage scores for Regional scale
        self.scoring_method = config.get("scoring_method", "") if config else ""
        self.percentage_scores = config.get("percentage_scores", {}) if config else {}

        default_buffer_distance = int(self.attributes.get("default_single_buffer_distance", 0))
        if mapped_distance:
            default_buffer_distance = int(mapped_distance)

        buffer_distance = self.attributes.get("single_buffer_point_layer_distance", default_buffer_distance)
        self.buffer_distance = int(buffer_distance) if buffer_distance else int(default_buffer_distance)

    def _process_features_for_area(
        self,
        current_area: "QgsGeometry",
        clip_area: "QgsGeometry",
        current_bbox: "QgsGeometry",
        area_features: "QgsVectorLayer",
        index: int,
    ) -> str:
        """
        Executes the actual workflow logic for a single area
        Must be implemented by subclasses.

        :current_area: Current polygon from our study area.
        :current_bbox: Bounding box of the above area.
        :area_features: A vector layer of features to analyse that includes only features in the study area.
        :index: Iteration / number of area being processed.

        :return: A raster layer file path if processing completes successfully, False if canceled or failed.
        """
        log_message(f"{self.workflow_name}  Processing Started")

        # Check if we should use percentage-based scoring (Regional scale)
        if self.scoring_method == "percentage_intersection" and self.percentage_scores:
            return self._process_with_percentage_scoring(
                current_area=current_area,
                clip_area=clip_area,
                current_bbox=current_bbox,
                area_features=area_features,
                index=index,
            )

        # Original binary scoring approach for National/Local scale
        # Step 1: Buffer the selected features
        buffered_layer = self._buffer_features(area_features, f"{self.layer_id}_buffered_{index}")

        # Step 2: Assign values to the buffered polygons
        scored_layer = self._assign_scores(buffered_layer)

        # Step 3: Rasterize the scored buffer layer
        raster_output = self._rasterize(scored_layer, current_bbox, index)

        return raster_output

    def _process_with_percentage_scoring(
        self,
        current_area: "QgsGeometry",
        clip_area: "QgsGeometry",
        current_bbox: "QgsGeometry",
        area_features: "QgsVectorLayer",
        index: int,
    ) -> str:
        """Process using percentage-based scoring (Regional scale).

        For each grid cell, calculate the percentage of the buffer within
        the hexagon and assign score based on percentage thresholds.

        Args:
            current_area: Polygon from study area.
            clip_area: Polygon to clip features to.
            current_bbox: Bounding box.
            area_features: Features to analyze.
            index: Area number being processed.

        Returns:
            Raster file path, or False if failed.
        """
        from geest.core.algorithms.utilities import subset_vector_layer

        log_message(
            f"Single Point Buffer: Using percentage scoring for Regional scale (buffer: {self.buffer_distance}m)",
            level=Qgis.Info,
        )

        # Step 1: Create buffer around points
        buffer_output = os.path.join(self.workflow_directory, f"simple_buffer_{index}.gpkg")
        if os.path.exists(buffer_output):
            os.remove(buffer_output)

        buffer_params = {
            "INPUT": area_features,
            "DISTANCE": self.buffer_distance,
            "OUTPUT": buffer_output,
        }
        result = processing.run("native:buffer", buffer_params)
        buffered_layer_path = result["OUTPUT"]

        if not buffered_layer_path or not os.path.exists(buffered_layer_path):
            log_message(
                f"Failed to create buffer for area {index}",
                level=Qgis.Warning,
            )
            return False

        buffered_layer = QgsVectorLayer(buffered_layer_path, "buffered", "ogr")
        if not buffered_layer.isValid():
            log_message(
                f"Failed to load buffered layer for area {index}",
                level=Qgis.Warning,
            )
            return False

        # Step 2: Get grid cells for current area
        area_grid = subset_vector_layer(
            self.workflow_directory,
            self.grid_layer,
            current_area,
            f"grid_area_{index}",
        )
        if not area_grid or not area_grid.isValid():
            log_message(
                f"Failed to get grid cells for area {index}",
                level=Qgis.Warning,
            )
            return False

        # Step 3: Score grid cells based on percentage intersection
        scored_grid = self._score_grid_for_percentage(
            grid_layer=area_grid,
            buffered_layer=buffered_layer,
        )

        if scored_grid is False:
            log_message(
                "No scored grid cells were created.",
                level=Qgis.Warning,
            )
            return False

        # Step 4: Rasterize
        raster_output = self._rasterize(
            input_layer=scored_grid,
            bbox=current_bbox,
            index=index,
            value_field="value",
        )

        return raster_output

    def _score_grid_for_percentage(
        self,
        grid_layer: "QgsVectorLayer",
        buffered_layer: "QgsVectorLayer",
    ) -> "QgsVectorLayer":
        """Score grid cells based on percentage intersection with buffered features.

        For Regional scale: calculates what percentage of each grid cell
        is covered by the accessibility buffer and assigns score accordingly.

        Args:
            grid_layer: The grid layer (H3 hexagons).
            buffered_layer: The buffered POI features.

        Returns:
            The grid layer with "value" field containing the assigned scores.
        """
        log_message("Scoring grid cells based on percentage intersection")

        field_names = [field.name() for field in grid_layer.fields()]
        if "value" not in field_names:
            grid_layer.dataProvider().addAttributes([QgsField("value", QVariant.Int)])
            grid_layer.updateFields()

        grid_layer.startEditing()
        for grid_feature in grid_layer.getFeatures():
            grid_geom = grid_feature.geometry()
            if grid_geom.isNull():
                continue

            grid_area = grid_geom.area()
            if grid_area == 0:
                continue

            max_score = 0
            max_overlap_percent = 0

            for buffered_feature in buffered_layer.getFeatures():
                buffered_geom = buffered_feature.geometry()
                if buffered_geom.isNull():
                    continue

                intersection = grid_geom.intersection(buffered_geom)
                if intersection.isNull() or intersection.area() == 0:
                    continue

                # Calculate % of buffer within hexagon
                buffer_area = buffered_geom.area()
                if buffer_area > 0:
                    overlap_percent = (intersection.area() / buffer_area) * 100
                else:
                    overlap_percent = 0

                if overlap_percent > max_overlap_percent:
                    max_overlap_percent = overlap_percent

            # Calculate score based on final max_overlap_percent after all buffers checked
            sorted_items = sorted(self.percentage_scores.items())
            max_score = 0
            for i, (min_pct, score) in enumerate(sorted_items):
                if score == 0:
                    if max_overlap_percent == 0:
                        max_score = 0
                elif i == len(sorted_items) - 1:
                    # Last score: prev_pct < overlap
                    prev_pct = sorted_items[i - 1][0]
                    if prev_pct < max_overlap_percent:
                        max_score = score
                else:
                    # Middle scores: prev_pct < overlap <= min_pct
                    prev_pct = sorted_items[i - 1][0]
                    if prev_pct < max_overlap_percent <= min_pct:
                        max_score = score

            grid_feature.setAttribute("value", max_score)
            grid_layer.updateFeature(grid_feature)

        grid_layer.commitChanges()
        return grid_layer

        return raster_output

    def _buffer_features(self, layer: QgsVectorLayer, output_name: str) -> QgsVectorLayer:
        """
        Buffer the input features by the buffer_distance km.

        Args:
            layer (QgsVectorLayer): The input feature layer.
            output_name (str): A name for the output buffered layer.

        Returns:
            QgsVectorLayer: The buffered features layer.
        """
        output_path = os.path.join(self.workflow_directory, f"{output_name}.shp")
        buffered_layer = processing.run(
            "native:buffer",
            {
                "INPUT": layer,
                "DISTANCE": self.buffer_distance,
                "SEGMENTS": 15,
                "DISSOLVE": True,
                "OUTPUT": output_path,
            },
        )["OUTPUT"]

        buffered_layer = QgsVectorLayer(output_path, output_name, "ogr")
        return buffered_layer

    def _assign_scores(self, layer: QgsVectorLayer) -> QgsVectorLayer:
        """
        Assign values to buffered polygons based 5 for presence of a polygon.

        Args:
            layer QgsVectorLayer: The buffered features layer.

        Returns:
            QgsVectorLayer: A new layer with a "value" field containing the assigned scores.
        """

        log_message(f"Assigning scores to {layer.name()}")
        # Create a new field in the layer for the scores
        layer.startEditing()
        layer.dataProvider().addAttributes([QgsField("value", QVariant.Int)])
        layer.updateFields()

        # Assign scores to the buffered polygons
        score = self.mapped_scores.get("intersects", 5)
        for feature in layer.getFeatures():
            feature.setAttribute("value", score)
            layer.updateFeature(feature)

        layer.commitChanges()

        return layer

    # Default implementation of the abstract method - not used in this workflow
    def _process_raster_for_area(
        self,
        current_area: QgsGeometry,
        clip_area: QgsGeometry,
        current_bbox: QgsGeometry,
        area_raster: str,
        index: int,
    ):
        """
        Executes the actual workflow logic for a single area using a raster.

        :current_area: Current polygon from our study area.
        :clip_area: Polygon to clip the raster to which is aligned to cell edges.
        :current_bbox: Bounding box of the above area.
        :area_raster: A raster layer of features to analyse that includes only bbox pixels in the study area.
        :index: Index of the current area.

        :return: Path to the reclassified raster.
        """
        pass

    def _process_aggregate_for_area(
        self,
        current_area: QgsGeometry,
        clip_area: QgsGeometry,
        current_bbox: QgsGeometry,
        index: int,
    ):
        """
        Executes the workflow, reporting progress through the feedback object and checking for cancellation.
        """
        pass
