import inspect
import os
from typing import Any, List, Tuple, Union
from speckle.converter.layers import getAllLayers
from speckle.converter.layers.utils import getElevationLayer, getLayerGeomType
from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog
from specklepy_qt_ui.qt_ui.logger import displayUserMsg
from specklepy_qt_ui.qt_ui.DataStorage import DataStorage

from speckle.utils.panel_logging import logToUser

from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsIconUtils

from PyQt5 import QtWidgets, uic, QtCore
from PyQt5.QtWidgets import QListWidgetItem

from specklepy.logging import metrics
from osgeo import gdal
import webbrowser
import specklepy_qt_ui.qt_ui

FORM_CLASS, _ = uic.loadUiType(
    os.path.join(
        os.path.join(
            os.path.dirname(specklepy_qt_ui.qt_ui.__file__), "ui", "transforms.ui"
        )
    )
)


class MappingSendDialogQGIS(MappingSendDialog, FORM_CLASS):
    def __init__(self, parent=None):
        super(MappingSendDialog, self).__init__(parent, QtCore.Qt.WindowStaysOnTopHint)
        self.setupUi(self)
        self.runAllSetup()

    def runSetup(self):
        self.attr_label.setEnabled(False)
        self.attrDropdown.setEnabled(False)
        self.dialog_button.setText("Apply")

        self.populateTransforms()
        self.populateLayersByTransform()
        self.populateSavedTransforms(self.dataStorage)
        self.populateSavedElevationLayer(self.dataStorage)

        # self.elevationLayerDropdown.currentIndexChanged.connect(self.saveElevationLayer)

    def populateSavedTransforms(
        self, dataStorage
    ):  # , savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None):
        if dataStorage is not None:
            self.dataStorage = dataStorage  # making sure lists are synced
        self.transformationsList.clear()
        vals = self.dataStorage.savedTransforms
        all_l_names = [l.name() for l in self.dataStorage.all_layers]

        for item in vals:
            layer_name = item.split("  ->  ")[0].split(" ('")[0]
            transform_name = item.split("  ->  ")[1]

            layer = None
            for l in self.dataStorage.all_layers:
                if layer_name == l.name():
                    layer = l
            if layer is None:
                displayUserMsg(
                    f"Layer '{layer_name}' not found in the project.\nTransformation is removed.",
                    level=2,
                )
                self.dataStorage.savedTransforms.remove(item)
            else:
                if transform_name not in self.dataStorage.transformsCatalog:
                    displayUserMsg(
                        f"Saved transformation '{transform_name}' is not valid.\nTransformation is removed.",
                        level=1,
                    )
                    self.dataStorage.savedTransforms.remove(item)
                elif all_l_names.count(layer.name()) > 1:
                    displayUserMsg(
                        f"Layer name '{layer.name()}' is used for more than 1 layer in the project.\nTransformation is removed.",
                        level=1,
                    )
                    self.dataStorage.savedTransforms.remove(item)
                else:
                    listItem = QListWidgetItem(item)
                    icon = QgsIconUtils().iconForLayer(layer)
                    listItem.setIcon(icon)

                    self.transformationsList.addItem(listItem)

    def onAddTransform(self):
        from speckle.utils.project_vars import set_transformations

        root = self.dataStorage.project.layerTreeRoot()
        self.dataStorage.all_layers = getAllLayers(root)

        if (
            len(self.layerDropdown.currentText()) > 1
            and len(self.transformDropdown.currentText()) > 1
        ):
            listItem = (
                str(self.layerDropdown.currentText())
                + "  ->  "
                + str(self.transformDropdown.currentText())
            )
            layer_name = listItem.split("  ->  ")[0].split(" ('")[0]
            transform_name = listItem.split("  ->  ")[1].lower()

            exists = 0
            for record in self.dataStorage.savedTransforms:
                current_layer_name = record.split("  ->  ")[0].split(" ('")[0]
                current_transf_name = record.split("  ->  ")[1].lower()
                if layer_name == current_layer_name:  # in layers
                    exists += 1
                    displayUserMsg(
                        "Selected layer already has a transformation applied", level=1
                    )
                    break

            if exists == 0:
                layer = None
                for l in self.dataStorage.all_layers:
                    if layer_name == l.name():
                        layer = l
                if layer is not None:
                    if (
                        "attribute" in transform_name
                        and self.attrDropdown.currentText() != ""
                    ):
                        listItem = (
                            str(self.layerDropdown.currentText())
                            + " ('"
                            + str(self.attrDropdown.currentText())
                            + "')  ->  "
                            + str(self.transformDropdown.currentText())
                        )

                    self.dataStorage.savedTransforms.append(listItem)
                    self.populateSavedTransforms(self.dataStorage)

                    try:
                        metrics.track(
                            "Connector Action",
                            self.dataStorage.active_account,
                            {
                                "name": "Transformation on Send Add",
                                "Transformation": listItem.split("  ->  ")[1],
                                "connector_version": str(
                                    self.dataStorage.plugin_version
                                ),
                            },
                        )
                    except Exception as e:
                        logToUser(e, level=2, func=inspect.stack()[0][3])

                    set_transformations(self.dataStorage)

    def onRemoveTransform(self):
        from speckle.utils.project_vars import set_transformations

        if self.transformationsList.currentItem() is not None:
            listItem = self.transformationsList.currentItem().text()
            # print(listItem)

            if listItem in self.dataStorage.savedTransforms:
                self.dataStorage.savedTransforms.remove(listItem)

                try:
                    metrics.track(
                        "Connector Action",
                        self.dataStorage.active_account,
                        {
                            "name": "Transformation on Send Remove",
                            "Transformation": listItem.split("  ->  ")[1],
                            "connector_version": str(self.dataStorage.plugin_version),
                        },
                    )
                except Exception as e:
                    logToUser(e, level=2, func=inspect.stack()[0][3])

            self.populateSavedTransforms(self.dataStorage)
            set_transformations(self.dataStorage)

    def onOkClicked(self):
        try:
            self.saveElevationLayer()
            self.close()
        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def populateLayers(self):
        try:
            self.layerDropdown.clear()
            root = self.dataStorage.project.layerTreeRoot()
            self.dataStorage.all_layers = getAllLayers(root)
            for i, layer in enumerate(self.dataStorage.all_layers):
                listItem = layer.name()
                self.layerDropdown.addItem(listItem)
                icon = QgsIconUtils().iconForLayer(layer)
                self.layerDropdown.setItemIcon(i, icon)

        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def populateLayersByTransform(self):
        try:
            self.layerDropdown.clear()
            root = self.dataStorage.project.layerTreeRoot()
            self.dataStorage.all_layers = getAllLayers(root)

            transform = str(self.transformDropdown.currentText())
            layers_dropdown = []

            for i, layer in enumerate(self.dataStorage.all_layers):
                listItem = None
                if "extrude" in transform.lower():
                    if isinstance(layer, QgsVectorLayer):
                        geom_type = getLayerGeomType(layer)
                        if "polygon" in geom_type.lower():
                            listItem = layer.name()

                elif "elevation" in transform.lower():
                    if isinstance(layer, QgsRasterLayer):
                        # avoiding tiling layers
                        ds = gdal.Open(layer.source(), gdal.GA_ReadOnly)
                        if ds is None:
                            continue

                        # for satellites
                        if "texture" in transform.lower():
                            listItem = layer.name()
                        # for elevation to mesh
                        elif "mesh" in transform.lower():
                            try:
                                if layer.bandCount() == 1:
                                    listItem = layer.name()
                            except:
                                pass

                if listItem is not None:
                    layers_dropdown.append(listItem)
                    self.layerDropdown.addItem(listItem)
                    icon = QgsIconUtils().iconForLayer(layer)
                    self.layerDropdown.setItemIcon(len(layers_dropdown) - 1, icon)

        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def populateAttributesByLayer(self):
        try:
            self.attrDropdown.clear()
            root = self.dataStorage.project.layerTreeRoot()
            self.dataStorage.all_layers = getAllLayers(root)

            layer_name = str(self.layerDropdown.currentText())
            transform_name = self.transformDropdown.currentText()
            layerForAttributes = None
            for i, layer in enumerate(self.dataStorage.all_layers):
                if layer_name == layer.name():
                    if isinstance(layer, QgsVectorLayer):
                        geom_type = getLayerGeomType(layer)
                        if "polygon" in geom_type.lower():
                            layerForAttributes = layer
                            break

            if layerForAttributes is not None and "attribute" in transform_name:
                self.attr_label.setEnabled(True)
                self.attrDropdown.setEnabled(True)

                if "ignore" not in transform_name:
                    self.attrDropdown.addItem("Random height")

                for field in layerForAttributes.fields():
                    field_type = field.type()
                    if field_type in [2, 6, 10]:
                        self.attrDropdown.addItem(str(field.name()))
            else:
                self.attr_label.setEnabled(False)
                self.attrDropdown.setEnabled(False)

        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def populateTransforms(self):
        try:
            self.transformDropdown.clear()
            for item in self.dataStorage.transformsCatalog:
                self.transformDropdown.addItem(item)
        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def populateSavedElevationLayer(
        self, dataStorage
    ):  # , savedTransforms: Union[List, None] = None, getLayer: Union[str, None] = None, getTransform: Union[str, None] = None):
        try:
            if dataStorage is not None:
                self.dataStorage = dataStorage  # making sure lists are synced
            elevationLayer = getElevationLayer(self.dataStorage)

            self.elevationLayerDropdown.clear()
            root = self.dataStorage.project.layerTreeRoot()
            self.dataStorage.all_layers = getAllLayers(root)

            self.elevationLayerDropdown.addItem("")

            setAsindex = 0
            countRaster = 1
            for i, layer in enumerate(self.dataStorage.all_layers):
                if isinstance(layer, QgsRasterLayer):
                    # avoiding tiling layers
                    ds = gdal.Open(layer.source(), gdal.GA_ReadOnly)
                    if ds is None:
                        continue
                    elif layer.bandCount() != 1:
                        continue

                    listItem = layer.name()
                    self.elevationLayerDropdown.addItem(listItem)
                    icon = QgsIconUtils().iconForLayer(layer)
                    self.elevationLayerDropdown.setItemIcon(countRaster, icon)

                    if elevationLayer is not None:
                        if listItem == elevationLayer.name():
                            setAsindex = countRaster
                    countRaster += 1
            self.elevationLayerDropdown.setCurrentIndex(setAsindex)

        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])
            return

    def saveElevationLayer(self):
        # print("saveElevationLayer")
        from speckle.utils.project_vars import set_elevationLayer

        root = self.dataStorage.project.layerTreeRoot()
        layer = None

        if self.dataStorage is None:
            return

        layerName = str(self.elevationLayerDropdown.currentText())
        try:
            if self.dataStorage.elevationLayer.name() == layerName:
                return
        except:
            pass

        if len(layerName) < 1:
            layer = None
        else:
            self.dataStorage.all_layers = getAllLayers(root)
            all_l_names = [l.name() for l in self.dataStorage.all_layers]
            # print(all_l_names)

            for l in self.dataStorage.all_layers:
                if layerName == l.name():
                    layer = l
                    try:
                        # print(layerName)
                        if all_l_names.count(layer.name()) > 1:
                            displayUserMsg(
                                f"Layer name '{layer.name()}' is used for more than 1 layer in the project",
                                level=1,
                            )
                            layer = None
                            break
                        else:
                            self.dataStorage.elevationLayer = layer
                            set_elevationLayer(self.dataStorage)
                            logToUser(
                                f"Elevation layer '{layerName}' successfully set",
                                level=0,
                            )
                            break
                    except:
                        displayUserMsg(
                            f"Layer '{layer.name()}' is not found in the project",
                            level=1,
                        )
                        layer = None
                        break

        try:
            metrics.track(
                "Connector Action",
                self.dataStorage.active_account,
                {
                    "name": "Add transformation on Send",
                    "Transformation": "Set Layer as Elevation",
                    "connector_version": str(self.dataStorage.plugin_version),
                },
            )
        except Exception as e:
            logToUser(e, level=2, func=inspect.stack()[0][3])

    def onMoreInfo(self):
        webbrowser.open("https://speckle.guide/user/qgis.html#transformations")
