from qgis.core import QgsProject, QgsRasterLayer, QgsTask, QgsVectorLayer, QgsWkbTypes
from qgis.PyQt.QtCore import pyqtSignal

from ..compat import MESSAGE_LEVEL_CRITICAL, MESSAGE_LEVEL_SUCCESS, MESSAGE_LEVEL_WARNING, QGSTASK_CAN_CANCEL
from ..model.project import Project
from ..model.sample_frame import SampleFrame
from ..QRiS.settings import Settings
from .zonal_metrics import ZonalMetrics


class ZonalMetricsTask(QgsTask):
    """
    https://docs.qgis.org/3.22/en/docs/pyqgis_developer_cookbook/tasks.html
    """

    # Signal to notify when done and return the PourPoint and whether it should be added to the map
    on_complete = pyqtSignal(bool, SampleFrame, dict or None, dict or None)

    def __init__(self, project: Project, mask: SampleFrame):
        super().__init__(f"Zonal Metrics Task for {mask.db_table_name}", QGSTASK_CAN_CANCEL)

        self.polygons = {}
        self.data = {}

        mask_guid = f"QRiS::{project.map_guid}::{mask.db_table_name}::{mask.id}"

        self.map_layers = []
        for layer in QgsProject.instance().mapLayers().values():
            layer_node = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
            if layer_node.itemVisibilityChecked():
                # Skip the mask being used to summarize layers
                prop = layer_node.customProperty("QRiS")
                if prop is not None and isinstance(mask, SampleFrame) and prop == mask_guid:
                    continue

                layer_def = {"name": layer.name(), "url": layer.dataProvider().dataSourceUri()}
                if isinstance(layer, QgsRasterLayer):
                    layer_def["type"] = "raster"
                    self.map_layers.append(layer_def)
                elif isinstance(layer, QgsVectorLayer):
                    # Skip non-spatial tables (e.g., attribute-only layers) that cannot be transformed.
                    if layer.wkbType() != QgsWkbTypes.NoGeometry and layer.featureCount() > 0:
                        layer_def["type"] = "vector"
                        self.map_layers.append(layer_def)

        self.project = project
        self.mask = mask
        self.config = {}
        mask_layer = "sample_frame_features"
        self.metrics = ZonalMetrics(project.project_file, mask, self.map_layers, mask_layer)

    def run(self):
        """
        Originally intended to use this VectorTranslate method
        https://gdal.org/development/rfc/rfc59.1_utilities_as_a_library.html#swig-bindings-python-java-c-perl-changes

        But ended up using this ogr method
        https://subscription.packtpub.com/book/application-development/9781787124837/3/ch03lvl1sec58/exporting-a-layer-to-the-geopackage-format
        """
        if self.isCanceled():
            return False

        try:
            self.data = self.metrics.run(cancel_check=lambda: self.isCanceled())
            self.polygons = self.metrics.polygons

        except Exception as ex:
            self.exception = ex
            return False

        return True

    def finished(self, result: bool):
        """
        This function is automatically called when the task has completed (successfully or not).
        You implement finished() to do whatever follow-up stuff should happen after the task is complete.
        finished is always called from the main thread, so it's safe to do GUI operations and raise Python exceptions here.
        result is the return value from self.run.
        """

        if not result:
            if self.exception is None:
                Settings().log("Geospatial Metrics was canceled by the user", MESSAGE_LEVEL_WARNING)
            else:
                Settings().log(f"Geospatial metrics exception: {self.exception}", MESSAGE_LEVEL_CRITICAL)
        else:
            Settings().log("Metrics Complete", MESSAGE_LEVEL_SUCCESS)

        self.on_complete.emit(result, self.mask, self.polygons, self.data)

    def cancel(self):
        Settings().log("Geospatial Metrics was canceled", MESSAGE_LEVEL_WARNING)
        super().cancel()
