import asyncio
import gc
import logging
import os.path
import webbrowser
from qgis.PyQt import QtWidgets, sip
from qgis.PyQt.QtCore import Qt, pyqtSlot, QObject, QSizeF, QUrl, QDir
from qgis.PyQt.QtGui import QIcon, QPageLayout, QPainter
from qgis.PyQt.QtWidgets import QAction, QMenu, QFileDialog, QStyleOptionGraphicsItem, QToolBar, QMessageBox
from qgis.PyQt.QtPrintSupport import QPrinter
from qgis.core import QgsProject, QgsLayerTreeGroup, QgsAnnotationLayer, Qgis, QgsMapRendererCustomPainterJob, QgsRenderContext, QgsApplication
from qgis import processing
import qasync
from SAGisXPlanung.core.buildingtemplate import BuildingTemplateItem, BuildingTemplateRendererMetadata
from SAGisXPlanung.MapLayerRegistry import MapLayerRegistry
from SAGisXPlanung.gui.settings_dialog import Settings
from SAGisXPlanung.core.connection import attempt_connection, verify_db_connection
from SAGisXPlanung.gui.XPEditPreFilledObjects import XPEditPreFilledObjectsDialog
from SAGisXPlanung.core.canvas_display import plan_to_map, load_on_canvas
from SAGisXPlanung.gui.XPlanungDialog import XPlanungDialog
from SAGisXPlanung.gui.widgets import DatabaseConfigPage
from SAGisXPlanung.processing.provider import SAGisProvider
from SAGisXPlanung.utils import createXPlanungIndicators
try:
    from SAGisXPlanung import BASE_DIR, RELEASE
except ImportError as e:
    pass
logger = logging.getLogger(__name__)

class XPlanung(QObject):

    def __init__(self, iface):
        super(XPlanung, self).__init__()
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        self.main_action = None
        self.data_action = None
        self.snapshot_action = None
        self.processing_menu = None
        self.menu_name = 'SAGis XPlanung' if RELEASE else 'SAGis XPlanung (dev)'
        self.provider = None
        self.first_start = None
        self.sagis_menu = self.iface.mainWindow().findChild(QMenu, 'sagis_menu')
        if not self.sagis_menu:
            self.sagis_menu = QMenu('&SAGis', self.iface.mainWindow().menuBar())
            self.sagis_menu.setObjectName('sagis_menu')
            actions = self.iface.mainWindow().menuBar().actions()
            if len(actions) > 3:
                self.iface.mainWindow().menuBar().insertMenu(actions[-2], self.sagis_menu)
        self.sagis_toolbar = self.iface.mainWindow().findChild(QToolBar, 'sagis_toolbar')
        if not self.sagis_toolbar:
            self.sagis_toolbar = QToolBar('SAGis Toolbar', self.iface.mainWindow().menuBar())
            self.sagis_toolbar.setToolTip('SAGis Tools')
            self.sagis_toolbar.setObjectName('sagis_toolbar')
            self.iface.addToolBar(self.sagis_toolbar)
        self.menu = self.sagis_menu.addMenu(self.menu_name)
        self.menu.aboutToShow.connect(self.onXPlanungMenuAboutToShow)
        self.dockWidget = XPlanungDialog(parent=iface.mainWindow())
        self.dockWidget.setMaximumWidth(1000)
        self.iface.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidget.details_dialog)
        self.iface.addTabifiedDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.dockWidget, ['xplanung-details'], raiseTab=True)
        self.dockWidget.hide()
        self.dockWidget.details_dialog.hide()
        self.settings = Settings()
        QgsProject.instance().homePathChanged.connect(self.on_project_loaded)
        self.iface.layerTreeView().layerTreeModel().rowsInserted.connect(self.onRowsInserted)
        self.renderer_metdadata = BuildingTemplateRendererMetadata()
        QgsApplication.rendererRegistry().addRenderer(self.renderer_metdadata)
        MapLayerRegistry()

    def initProcessing(self):
        self.menu.addSeparator()
        self.processing_menu = self.menu.addMenu('Verarbeitungswerkzeuge')
        self.provider = SAGisProvider()
        self.provider.algorithmsLoaded.connect(self.onProcessingAlgorithmsLoaded)
        QgsApplication.processingRegistry().addProvider(self.provider)

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        if RELEASE:
            xp_icon = os.path.join(BASE_DIR, 'gui/resources/sagis_icon.png')
        else:
            xp_icon = os.path.join(BASE_DIR, 'gui/resources/sagis_dev_icon.png')
        settings_icon = ':/images/themes/default/mActionOptions.svg'
        self.main_action = QAction(QIcon(xp_icon), 'SAGis XPlanung', self.iface.mainWindow())
        self.main_action.setToolTip('Plugin zum Erfassen von XPlanung konformen Bauleitplänen')
        self.main_action.triggered.connect(self.run)
        self.sagis_toolbar.addAction(self.main_action)
        self.menu.addAction(self.main_action)
        settings_action = QAction(QIcon(settings_icon), 'Einstellungen', self.iface.mainWindow())
        settings_action.setToolTip('SAGis XPlanung konfigurieren')
        settings_action.triggered.connect(self.showSettings)
        self.menu.addAction(settings_action)
        docs_action = QAction(QIcon(':/images/themes/default/mActionHelpContents.svg'), 'Hilfe öffnen...', self.iface.mainWindow())
        docs_action.setToolTip('SAGis XPlanung konfigurieren')
        docs_action.triggered.connect(self.on_help_menu_clicked)
        self.menu.addAction(docs_action)
        self.menu.addSeparator()
        self.data_action = QAction('Allgemeine Daten bearbeiten...', self.iface.mainWindow())
        self.data_action.triggered.connect(self.showEditPreFilledObjects)
        self.menu.addAction(self.data_action)
        self.snapshot_action = QAction('Aktuellen Kartenauschnitt als PDF exportieren...', self.iface.mainWindow())
        self.snapshot_action.triggered.connect(self.onSnapshotActionTriggered)
        self.menu.addAction(self.snapshot_action)
        QtWidgets.QApplication.restoreOverrideCursor()
        self.initProcessing()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item, icons and dialogs from QGIS GUI."""
        self.sagis_toolbar.removeAction(self.main_action)
        self.menu.deleteLater()
        if self.sagis_menu.isEmpty():
            self.sagis_menu.deleteLater()
        MapLayerRegistry().unload()
        QgsProject.instance().homePathChanged.disconnect(self.on_project_loaded)
        self.iface.layerTreeView().layerTreeModel().rowsInserted.disconnect(self.onRowsInserted)
        QgsApplication.rendererRegistry().removeRenderer('sagisxplanung.buildingtemplate')
        self.iface.removeDockWidget(self.dockWidget.details_dialog)
        self.iface.removeDockWidget(self.dockWidget)
        self.dockWidget.details_dialog.deleteLater()
        self.dockWidget.deleteLater()
        if not sip.isdeleted(self.provider):
            QgsApplication.processingRegistry().removeProvider(self.provider)
        gc.collect()

    @pyqtSlot()
    def run(self):
        if self.is_valid_db():
            if self.first_start:
                self.dockWidget.cbPlaene.refresh()
            self.dockWidget.show()
        if self.first_start:
            self.first_start = False

    @pyqtSlot()
    def showSettings(self):
        self.settings.exec()
        self.dockWidget.details_dialog.hide()
        if not self.is_valid_db():
            self.dockWidget.hide()
        self.dockWidget.cbPlaene.refresh()

    @pyqtSlot()
    def on_help_menu_clicked(self):
        url = f'file:///{BASE_DIR}/docs/index.html'
        launch_succeeded = webbrowser.open_new_tab(url)

    @pyqtSlot(bool)
    def showEditPreFilledObjects(self, checked):
        d = XPEditPreFilledObjectsDialog(parent=self.iface.mainWindow())
        d.exec()

    @qasync.asyncSlot()
    async def onXPlanungMenuAboutToShow(self):
        try:
            attempt_connection()
            self.data_action.setDisabled(False)
        except Exception:
            self.data_action.setDisabled(True)

    @qasync.asyncSlot()
    async def onProcessingAlgorithmsLoaded(self):
        for alg in self.provider.algorithms():
            a = QAction(QIcon(':/images/themes/default/processingAlgorithm.svg'), alg.displayName(), self.processing_menu)
            a.triggered.connect(lambda checked, alg_id=alg.id(): processing.execAlgorithmDialog(alg_id, {}))
            self.processing_menu.addAction(a)
        self.provider.algorithmsLoaded.disconnect(self.onProcessingAlgorithmsLoaded)

    @qasync.asyncSlot(bool)
    async def onSnapshotActionTriggered(self, checked):
        filename = QFileDialog.getSaveFileName(self.iface.mainWindow(), 'Speicherort auswählen', directory=f'export.pdf', filter=f'*.pdf')
        settings = self.iface.mapCanvas().mapSettings()
        settings.setFlag(Qgis.MapSettingsFlag.Antialiasing, True)
        settings.setFlag(Qgis.MapSettingsFlag.ForceVectorOutput, True)
        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        printer.setOutputFileName(filename[0])
        printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
        printer.setPageOrientation(QPageLayout.Orientation.Portrait)
        outputSize = settings.outputSize()
        printer.setPaperSize(QSizeF(outputSize * 25.4 / settings.outputDpi()), QPrinter.Unit.Millimeter)
        printer.setPageMargins(0, 0, 0, 0, QPrinter.Unit.Millimeter)
        printer.setResolution(settings.outputDpi())
        dest_painter = QPainter(printer)
        render_job = QgsMapRendererCustomPainterJob(settings, dest_painter)
        render_job.prepare()
        render_job.renderPrepared()
        context = QgsRenderContext.fromMapSettings(settings)
        context.setPainter(dest_painter)
        option = QStyleOptionGraphicsItem()
        option.initFrom(self.iface.mapCanvas())
        dest_painter.begin(printer)
        for canvas_item in self.iface.mapCanvas().items():
            if isinstance(canvas_item, BuildingTemplateItem):
                dest_painter.save()
                itemScenePos = canvas_item.scenePos()
                dest_painter.translate(itemScenePos.x(), itemScenePos.y())
                canvas_item.paint(dest_painter, option)
                dest_painter.restore()
        dest_painter.end()
        del dest_painter
        del printer
        url = QUrl.fromLocalFile(filename[0])
        path = QDir.toNativeSeparators(filename[0])
        self.iface.messageBar().pushMessage('Als PDF Speichern', f'Kartenausschnitt erfolgreich unter <a href="{url}">{path}</a> gespeichert', level=Qgis.MessageLevel.Success)

    def on_project_loaded(self):
        logger.debug('project loaded')
        layers = QgsProject.instance().layerTreeRoot().findGroups(recursive=True)
        for group in layers:
            if not isinstance(group, QgsLayerTreeGroup) or 'xplanung_id' not in group.customProperties():
                continue
            try:
                for tree_layer in group.findLayers():
                    MapLayerRegistry().addLayer(tree_layer.layer(), add_to_legend=False)
                asyncio.create_task(load_on_canvas(group.customProperty('xplanung_id'), layer_group=group))
            except Exception as e:
                logger.exception(e)

    def onRowsInserted(self, parent, first, last):
        model = self.iface.layerTreeView().layerTreeModel()
        index = model.index(first, 0, parent)
        node = model.index2node(index)
        if not isinstance(node, QgsLayerTreeGroup) or 'xplanung_id' not in node.customProperties():
            return
        if not self.iface.layerTreeView().indicators(node):
            xp_indicator, reload_indicator = createXPlanungIndicators()
            plan_xid = node.customProperty('xplanung_id')
            reload_indicator.clicked.connect(lambda i, p=plan_xid: plan_to_map(p))
            self.iface.layerTreeView().addIndicator(node, xp_indicator)
            self.iface.layerTreeView().addIndicator(node, reload_indicator)

    def is_valid_db(self) -> bool:
        result, meta = verify_db_connection()
        if result:
            return True
        msg_box = QMessageBox()
        msg_box.setWindowTitle('Inkompatiblität mit der Datenbank')
        msg_box.setIcon(QMessageBox.Icon.Warning)
        msg_box.setText(meta.error)
        settings_button = msg_box.addButton('Einstellungen', QMessageBox.ButtonRole.YesRole)
        msg_box.addButton(QMessageBox.StandardButton.Cancel)
        msg_box.setEscapeButton(QMessageBox.StandardButton.Cancel)
        msg_box.exec()
        if msg_box.clickedButton() == settings_button:
            db_page = self.settings.navigate_to_page(DatabaseConfigPage)
            if db_page:
                db_page.setup_data()
                asyncio.create_task(db_page.test_connection())
            self.settings.exec()
        return False