import asyncio
import logging
import os
import threading
from asyncio import CancelledError
from typing import List
import qasync
from qgis.PyQt.QtWidgets import QMessageBox
from qgis.core import Qgis
from qgis.PyQt import QtWidgets, sip
from qgis.PyQt.QtGui import QIcon, QCursor, QKeySequence
from qgis.PyQt.QtCore import Qt, pyqtSlot, QItemSelectionModel, QUrl, QDir
from qgis.PyQt.QtWidgets import QAbstractItemView, QApplication
from qgis.gui import QgsDockWidget
from qgis.utils import iface
from SAGisXPlanung import compile_ui_file, BASE_DIR
from SAGisXPlanung.core.converter_tasks import import_plan, export_action, ActionCanceledException, run_import_prechecks, prepare_gml_input
from SAGisXPlanung.Tools.ContextMenuTool import ContextMenuTool
from SAGisXPlanung.XPlanungItem import XPlanungItem
from SAGisXPlanung.ext.spinner import loading_animation
from SAGisXPlanung.gui.nexus_dialog import NexusDialog
from SAGisXPlanung.gui.style import with_color_palette, ApplicationColor, apply_color, load_svg, SVGButtonEventFilter
from SAGisXPlanung.gui.widgets import QBuildingTemplateEdit
from SAGisXPlanung.gui.widgets.QExplorerView import XID_ROLE
from SAGisXPlanung.utils import CLASSES
from SAGisXPlanung.gui.XPCreatePlanDialog import XPCreatePlanDialog
from SAGisXPlanung.gui.XPPlanDetailsDialog import XPPlanDetailsDialog
from SAGisXPlanung.gui.widgets.QPlanComboBox import QPlanComboBox
logger = logging.getLogger(__name__)
uifile = os.path.join(BASE_DIR, 'ui/XPlanung_dialog_base.ui')
FORM_CLASS = compile_ui_file(uifile)
style = '\nQToolButton {{\n    background: palette(window); \n    border: 0px;\n    padding: 5px;\n    border-radius: 5px;\n}}\nQToolButton:hover {{\n    background-color: {_button_hover_bg};\n}}\n'

class XPlanungDialog(QgsDockWidget, FORM_CLASS):
    """
    Hauptdialog der Anwendung.
    """

    def __init__(self, parent=None):
        super(XPlanungDialog, self).__init__(parent)
        self.setupUi(self)
        self.setAllowedAreas(self.allowedAreas() | Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)
        self.iface = iface
        self.export_task = None
        self.import_task = None
        self.nexus_dialog = None
        self.cancellation_token = threading.Event()
        self.bCreate.clicked.connect(lambda: self.showCreateForm())
        self.bExport.clicked.connect(lambda: self.export())
        self.bImport.clicked.connect(self.on_import_clicked)
        self.bInfo.setIcon(QIcon(os.path.join(BASE_DIR, 'gui/resources/info.svg')))
        self.bInfo.clicked.connect(self.openDetails)
        self.button_cancel_import.setIcon(load_svg(os.path.join(BASE_DIR, 'gui/resources/cancel.svg'), color=ApplicationColor.Tertiary))
        self.button_cancel_import.setCursor(Qt.CursorShape.PointingHandCursor)
        self.hover_filter = SVGButtonEventFilter(ApplicationColor.Tertiary, ApplicationColor.Primary)
        self.button_cancel_import.installEventFilter(self.hover_filter)
        self.button_cancel_import.clicked.connect(self.on_cancel_import)
        self.identifyTool = ContextMenuTool(self.iface.mapCanvas(), self)
        self.identifyTool.accessAttributesRequested.connect(self.showObjectAttributes)
        self.identifyTool.highlightObjectTreeRequested.connect(self.selectTreeItem)
        self.identifyTool.featureSaved.connect(self.onFeatureSaved)
        self.bIdentify.setIcon(QIcon(':/images/themes/default/mActionIdentify.svg'))
        self.bIdentify.clicked.connect(self.onIdentifyClicked)
        self.bIdentify_shortcut = QtWidgets.QShortcut(QKeySequence(Qt.Modifier.ALT | Qt.Key.Key_Q), self)
        self.bIdentify_shortcut.activated.connect(lambda: self.bIdentify.click())
        self.iface.mapCanvas().mapToolSet.connect(self.onMapToolChanged)
        self.details_dialog = XPPlanDetailsDialog(parent=iface.mainWindow())
        self.details_dialog.planDeleted.connect(self.cbPlaene.refresh)
        self.details_dialog.nameChanged.connect(self.onPlanNameChanged)
        self.progress_widget.setVisible(False)
        self.opened.connect(self.onOpened)
        self.closed.connect(self.onClosed)
        self.cbPlaene.currentIndexChanged.connect(self.onIndexChanged)
        self.fwImportPath.fileChanged.connect(lambda file_path: self.bImport.setEnabled(bool(file_path)))
        self.button_show_all.clicked.connect(self.on_show_all_clicked)
        self.progress_widget.setStyleSheet(style.format(_button_hover_bg=ApplicationColor.Grey300))
        with_color_palette(self, [ApplicationColor.Secondary], class_='QPushButton')
        apply_color(self.button_show_all, ApplicationColor.Secondary)
        logger.debug('setup init completed')

    def __del__(self):
        try:
            if not sip.isdeleted(self.iface.mapCanvas()):
                self.iface.mapCanvas().mapToolSet.disconnect(self.onMapToolChanged)
        except TypeError:
            pass

    def selectedPlan(self):
        return self.cbPlaene.currentPlanId()

    @qasync.asyncSlot()
    async def onOpened(self):
        await self.onIndexChanged(self.cbPlaene.currentIndex())

    def onClosed(self):
        self.details_dialog.hide()

    @qasync.asyncSlot(int)
    async def onIndexChanged(self, i):
        self.bExport.setEnabled(False)
        if i != -1:
            xid = self.cbPlaene.currentPlanId()
            await self.details_dialog.initialize_data(xid)
        else:
            self.details_dialog.hide()
        self.bInfo.setDisabled(i == -1)
        self.bExport.setDisabled(i == -1)

    @qasync.asyncSlot(str, str)
    async def onPlanNameChanged(self, xid: str, updated_name: str):
        self.cbPlaene.setPlanName(xid, updated_name)

    def onIdentifyClicked(self, checked: bool):
        if checked:
            self.iface.mapCanvas().setMapTool(self.identifyTool)
        elif self.iface.mapCanvas().mapTool() == self.identifyTool:
            self.iface.mapCanvas().unsetMapTool(self.identifyTool)

    def onMapToolChanged(self, new_tool, old_tool):
        if old_tool == self.identifyTool and self.bIdentify.isChecked():
            self.bIdentify.setChecked(False)
        elif new_tool == self.identifyTool:
            self.bIdentify.setChecked(True)

    def on_show_all_clicked(self, checked: bool):
        if self.nexus_dialog is not None and self.nexus_dialog.isVisible():
            self.nexus_dialog.deleteLater()
        self.nexus_dialog = NexusDialog(self)
        self.nexus_dialog.accessAttributesRequested.connect(self.showObjectAttributes)
        self.nexus_dialog.dataUpdated.connect(self.cbPlaene.refresh)
        self.nexus_dialog.show()

    def showCreateForm(self):
        """
        Startet den Dialog zum Erstellen eines neuen Planinhalts aus dem augewählten Layer.
        """

        def _show_dialog(d):
            d.finished.connect(lambda: self.cbPlaene.refresh())
            d.show()
        if self.rbBPlan.isChecked():
            _show_dialog(XPCreatePlanDialog(iface, CLASSES['BP_Plan']))
        elif self.rbFPlan.isChecked():
            _show_dialog(XPCreatePlanDialog(iface, CLASSES['FP_Plan']))
        elif self.rbRPlan.isChecked():
            _show_dialog(XPCreatePlanDialog(iface, CLASSES['RP_Plan']))
        elif self.rbLPlan.isChecked():
            _show_dialog(XPCreatePlanDialog(iface, CLASSES['LP_Plan']))
        else:
            self.iface.messageBar().pushMessage('XPlanung Fehler', 'Keine Planart ausgewählt', level=Qgis.MessageLevel.Warning)

    @qasync.asyncSlot()
    async def export(self):
        """
        Wandelt einen in der ComboBox gewählten Planinhalt in ein XPlanGML-Dokument um.
        Nutzt die aktive PostgreSQL-Verbindung die über das XPlanung-Einstellungsmenü konfiguriert wurde.
        """
        self.bExport.setEnabled(False)
        QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
        self.bExport.repaint()
        try:
            plan_xid = str(self.cbPlaene.currentPlanId())
            out_file_format = 'gml' if self.rbGML.isChecked() else 'zip'
            file_name = await export_action(self, plan_xid, out_file_format)
            url = QUrl.fromLocalFile(file_name)
            path = QDir.toNativeSeparators(file_name)
            iface.messageBar().pushMessage('XPlanung', f'Planwerk erfolgreich exportiert! <a href="{url}">{path}</a>', level=Qgis.MessageLevel.Success)
        except ActionCanceledException:
            pass
        except Exception as e:
            logger.exception(e)
            iface.messageBar().pushMessage('XPlanung Fehler', 'XPlanGML-Dokument konnte nicht umgewandelt werden!', str(e), level=Qgis.MessageLevel.Critical)
        finally:
            self.bExport.setEnabled(True)
            QtWidgets.QApplication.restoreOverrideCursor()

    @qasync.asyncSlot()
    async def on_import_clicked(self):
        """
        import XPlanGML file into database
        """
        filepath = self.fwImportPath.filePath()
        if not filepath:
            self.iface.messageBar().pushMessage('XPlanung Fehler', 'Kein Pfad zur XPlanGML-Datei angegeben', level=Qgis.MessageLevel.Critical)
            return
        self.bImport.setEnabled(False)
        self.bImport.repaint()
        self.progress_widget.setVisible(True)
        prev_cursor = self.cursor()
        try:
            input_data = await asyncio.to_thread(prepare_gml_input, filepath)
            warnings = await asyncio.to_thread(run_import_prechecks, input_data)
            if warnings:
                warning_text = '\n\n'.join((f'{w.title}:\n{w.message}' for w in warnings))
                result = QMessageBox.warning(self, 'XPlanGML-Import unterbrochen', f'{warning_text}\n\nImport trotzdem fortsetzen?', QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
                if result == QMessageBox.StandardButton.No:
                    return
            self.setCursor(Qt.CursorShape.BusyCursor)
            coro = asyncio.to_thread(import_plan, input_data, self.import_progress)
            self.import_task = asyncio.create_task(coro)
            import_result = await self.import_task
            self.fwImportPath.setFilePath('')
            self.cbPlaene.refresh()
            plan_name = import_result.plan_name
            if import_result.warnings:
                warn_info_text = '\n\n '.join((w for w in import_result.warnings))
                iface.messageBar().pushMessage('XPlanung', f'Planwerk {plan_name} importiert! ({len(import_result.warnings)} Warnungen)', warn_info_text, level=Qgis.MessageLevel.Warning)
            else:
                iface.messageBar().pushMessage('XPlanung', f'Planwerk {plan_name} erfolgreich importiert!', level=Qgis.MessageLevel.Success)
        except CancelledError:
            self.cancellation_token.set()
        except Exception as e:
            logger.exception(e)
            iface.messageBar().pushMessage('XPlanung Fehler', 'XPlanGML-Dokument konnte nicht importiert werden!', str(e), level=Qgis.MessageLevel.Critical)
        finally:
            self.bImport.setEnabled(True)
            self.setCursor(prev_cursor)
            self.progress_widget.setVisible(False)

    def import_progress(self, progress):
        if self.cancellation_token.is_set():
            self.cancellation_token.clear()
            raise CancelledError()
        self.progress_label.setText(f'{progress[0]}/{progress[1]}')

    def on_cancel_import(self):
        if self.import_task and (not self.import_task.cancelled()):
            self.import_task.cancel()

    @pyqtSlot()
    def openDetails(self):
        self.details_dialog.show()
        self.details_dialog.raise_()

    @qasync.asyncSlot(XPlanungItem)
    async def showObjectAttributes(self, xplan_item: XPlanungItem):
        await self.selectTreeItem(xplan_item)
        self.details_dialog.stackedWidget.setCurrentIndex(0)
        self.details_dialog.show_attribute_page()
        self.openDetails()

    @qasync.asyncSlot(XPlanungItem)
    async def selectTreeItem(self, xplan_item: XPlanungItem):
        if self.details_dialog.plan_xid != xplan_item.plan_xid:
            await self.details_dialog.initialize_data(xplan_item.plan_xid)
        proxy_model = self.details_dialog.objectTree.proxy
        index_list = proxy_model.match(proxy_model.index(0, 0), XID_ROLE, xplan_item.xid, 1, Qt.MatchFlag.MatchWildcard | Qt.MatchFlag.MatchRecursive)
        if not index_list:
            return
        selection_model = self.details_dialog.objectTree.selectionModel()
        selection_model.select(index_list[0], QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows)
        self.details_dialog.objectTree.scrollTo(index_list[0], QAbstractItemView.ScrollHint.PositionAtCenter)

    @qasync.asyncSlot(XPlanungItem)
    async def onFeatureSaved(self, xplan_item: XPlanungItem):
        if self.details_dialog.plan_xid != xplan_item.plan_xid:
            return
        parent_id = xplan_item.parent_xid or xplan_item.bereich_xid or xplan_item.plan_xid
        model = self.details_dialog.objectTree.model
        index_list = model.match(model.index(0, 0), XID_ROLE, parent_id, -1, Qt.MatchFlag.MatchWildcard | Qt.MatchFlag.MatchRecursive)
        if not index_list:
            return
        self.details_dialog.addExplorerItem(model.itemAtIndex(index_list[0]), xplan_item)