from pathlib import Path
from time import gmtime, strftime
from typing import Any
from qgis.PyQt.QtCore import QCoreApplication, QLocale
from qgis.PyQt.QtGui import QIcon

from qgis.core import (
    QgsProcessing,
    QgsProcessingContext,
    QgsProcessingLayerPostProcessorInterface,
    QgsRasterLayer,
    QgsProcessingAlgorithm,
    QgsProcessingException,
)

from ...helpers.helpers import (
    RasterLoadConfig,
    load_rasters_from_directory_to_qgis_instance,
    set_raster_layer_symbology,
    get_layer_name,
)
from ...helpers.helpers import run_command, get_console_command
from ...helpers.constants import CHLOE_PLUGIN_PATH

from ..helpers.helpers import (
    file_get_content,
)
from ..helpers.constants import CHLOE_PROVIDER_SUPPORTED_RASTER_EXTENSIONS
from ..styles.constants import STYLES_PATH
from ..gui.chloe_algorithm_dialog import ChloeAlgorithmDialog

from .helpers.constants import (
    INPUT_RASTER,
    OUTPUT_WINDOWS_PATH_DIR,
    SAVE_PROPERTIES,
    OUTPUT_RASTER,
)


class ChloeRasterStylePostProcessor(QgsProcessingLayerPostProcessorInterface):
    def __init__(self, qml_file_path: Path):
        super().__init__()
        self.qml_file_path = qml_file_path

    def postProcessLayer(self, layer, _context, _feedback) -> None:
        set_raster_layer_symbology(layer=layer, qml_file_path=self.qml_file_path)


class ChloeAlgorithm(QgsProcessingAlgorithm):
    def __init__(self):
        super().__init__()
        self.output_values: dict[str, Any] = {}
        self._layer_post_processors: list[ChloeRasterStylePostProcessor] = []

    def icon(self):
        icon_path: Path = CHLOE_PLUGIN_PATH / "images" / "chloe.png"
        return QIcon(str(icon_path))

    def tags(self):
        return ["chloe", self.commandName()]

    # def svgIconPath(self):
    #    return QgsApplication.iconPath("providerChloe.svg")

    def createInstance(self):
        return self.__class__()

    def createCustomParametersWidget(self, parent):
        return ChloeAlgorithmDialog(self, parent=parent)

    def flags(self):
        return (
            QgsProcessingAlgorithm.FlagSupportsBatch
            | QgsProcessingAlgorithm.FlagCanCancel
        )

    def get_properties_lines(self) -> "list[str]":
        """get property lines to write in properties file."""
        raise QgsProcessingException(
            f"property lines is not implemented for {self.name()}"
        )

    def set_properties_values(self, parameters, context, feedback) -> None:
        """set properties values."""
        raise QgsProcessingException(
            f"set properties values is not implemented for {self.name()}"
        )

    def create_properties_file(self, lines: "list[str]"):
        """Create Properties File."""
        properties_file_path: str = self.output_values.get(SAVE_PROPERTIES, "")
        if properties_file_path:
            s_time = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            try:
                with open(properties_file_path, "w+", encoding="utf-8") as file:
                    file.write(f"#{s_time}\n")
                    for line in lines:
                        file.write(f"{line}\n")
            except OSError as exc:
                raise QgsProcessingException(
                    self.tr(f"Cannot create properties file {properties_file_path}")
                ) from exc

    def get_properties_file_path(self, parameters) -> str:
        """Get properties file path.

        Returns the value that will be appended to the Chloe Command line
        preview and, at run time, the real .properties path that gets
        written to disk and passed to the Chloe Java executable.
        """

        value = (
            parameters.get(SAVE_PROPERTIES) if SAVE_PROPERTIES in parameters else None
        )

        if value and value != QgsProcessing.TEMPORARY_OUTPUT:
            return value

        return self.output_values.get(SAVE_PROPERTIES, "")

    def set_output_parameter_value(
        self, parameter_name: str, parameter_value: Any
    ) -> None:
        """
        Sets the value of the specified output parameter.

        Args:
            name (str): The name of the output parameter to set.
            value (Any): The value to assign to the output parameter.

        Returns:
            None
        """
        self.output_values[parameter_name] = parameter_value

    def processAlgorithm(self, parameters, context, feedback):
        """
        Process the algorithm.

        Args:
            parameters (dict): A dictionary of input parameters.
            context (QgsProcessingContext): The processing context.
            feedback (QgsProcessingFeedback): The feedback object.

        Returns:
            dict: A dictionary of output parameters.
        """

        self._layer_post_processors.clear()
        self.set_properties_values(parameters, context, feedback)
        self.create_properties_file(self.get_properties_lines())
        command: str = get_console_command(self.get_properties_file_path(parameters))
        if run_command(command_line=command, feedback=feedback):
            feedback.pushInfo("Exécution terminée avec succès.")

        results: dict[str, Any] = {}
        for definition in self.outputDefinitions():
            if definition.name() in parameters:
                results[definition.name()] = parameters[definition.name()]
        for param_name, param_value in self.output_values.items():
            results[param_name] = param_value

        if OUTPUT_RASTER in parameters and self.output_values.get(OUTPUT_RASTER):
            # add custom style to OUTPUT_ASC parameter if is to load on completion
            output_raster_path: str = self.output_values[OUTPUT_RASTER]
            load_on_completion = (
                output_raster_path in context.layersToLoadOnCompletion()
            )

            if load_on_completion:
                self.load_output_raster_to_qgis_instance(
                    output_raster_path=output_raster_path,
                    context=context,
                    results=results,
                )

        if OUTPUT_WINDOWS_PATH_DIR in parameters and self.output_values.get(
            OUTPUT_WINDOWS_PATH_DIR
        ):
            output_dir: Path = Path(self.output_values[OUTPUT_WINDOWS_PATH_DIR])
            if output_dir.exists():
                output_dir_rasters_config: RasterLoadConfig = RasterLoadConfig(
                    raster_directory=output_dir,
                    group_name="Windows_paths",
                    raster_file_prefix="window",
                    qml_file_path=STYLES_PATH / "continuous.qml",
                )
                load_rasters_from_directory_to_qgis_instance(output_dir_rasters_config)

        return results

    def load_output_raster_to_qgis_instance(
        self, output_raster_path: str, context, results: dict[str, Any]
    ) -> None:
        """
        Load output raster results to QGIS instance.

        Args:
            output_raster_path (str): The path to the output raster.
            context: The processing context.
            results (dict[str, Any]): The dictionary of results.

        Raises:
            QgsProcessingException: If the output raster cannot be loaded in the application.
        """
        raster_layer: QgsRasterLayer = QgsRasterLayer(output_raster_path, "hillshade")
        if not raster_layer.isValid():
            raise QgsProcessingException(
                self.tr("""Cannot load the output in the application""")
            )

        raster_layer_name: str = get_layer_name(
            layer=raster_layer, default_output=self.name()
        )

        raster_layer_details = QgsProcessingContext.LayerDetails(
            raster_layer_name, context.project(), OUTPUT_RASTER
        )
        post_processor = ChloeRasterStylePostProcessor(STYLES_PATH / "continuous.qml")
        self._layer_post_processors.append(post_processor)
        raster_layer_details.setPostProcessor(post_processor)

        context.addLayerToLoadOnCompletion(output_raster_path, raster_layer_details)

        results[OUTPUT_RASTER] = output_raster_path

    def helpUrl(self) -> str:
        localeName = QLocale.system().name()
        helpFilename = f"{self.name()}_{localeName}.html"
        documentation_folder: Path = (
            CHLOE_PLUGIN_PATH / "processing" / "algorithms" / "documentation"
        )
        helpfile = f"{documentation_folder / helpFilename}"
        return helpfile

    def shortHelpString(self):
        return self.helpString()

    def helpString(self):
        """Generation de l'onglet help"""
        helpfile = self.helpUrl()
        plugin_path: Path = CHLOE_PLUGIN_PATH

        context = {
            "plugin_path": f"file://{plugin_path.as_posix()}/",
            "image_path": f"file://{plugin_path / 'processing' / 'algorithms' / 'documentation' / 'images'}/",
        }

        # print(helpfile)
        content = file_get_content(helpfile, encoding="utf-8", context=context)

        if content is not None:
            return content
        else:
            return self.tr("No help available for this algorithm")

    def commandName(self):
        parameters = {}
        for param in self.parameterDefinitions():
            parameters[param.name()] = "1"
        name = get_console_command(self.get_properties_file_path(parameters))[0]
        if name.endswith(".py"):
            name = name[:-3]
        return name

    def tr(self, string, context=""):
        if context == "":
            context = self.__class__.__name__
        return QCoreApplication.translate(context, string)

    def checkParameterValues(self, parameters, context):
        """Override checkParameterValues base class method. Almost all algorithms will need to override this method to perform custom validation of the input raster parameter.
        This override is needed because QgsProcessingParameterRasterLayer does not support file filters on the selection combobox for existing layers in the canvas.
        Users can select any raster layer loaded in the canvas, regardless of the file extension. This method checks if the selected raster layer is a supported file extension.
        """

        input_raster: QgsRasterLayer = self.parameterAsRasterLayer(
            parameters, INPUT_RASTER, context
        )
        if input_raster and input_raster.isValid():
            input_raster_path: Path = Path(input_raster.dataProvider().dataSourceUri())
            input_raster_file_extension: str = input_raster_path.suffix.lower()[1:]

            if (
                input_raster_file_extension
                not in CHLOE_PROVIDER_SUPPORTED_RASTER_EXTENSIONS
            ):
                return False, self.tr(
                    f"The selected raster layer type {input_raster_file_extension} is not supported. Please select a GeoTIFF or ASCII file."
                )

        # If these parameters are valid, call the parent class's checkParameterValues method for the rest
        return super().checkParameterValues(parameters, context)
