# -*- coding: utf-8 -*-
"""
Atlas Pro Germany - Main plugin module.

Embeds the Atlas web application in a QGIS dock widget and intercepts
custom 'atlas-plugin://' URLs to load raster layers directly into the QGIS project.

Copyright (C) 2026 GeoCode
Licensed under the terms of the GNU General Public License v2 or later.
"""

from urllib.parse import parse_qs, urlparse
import os
import sys

# Workaround for QtWebEngine crashing on Wayland (common on Linux/Fedora)
# Must be set before QWebEngineView is imported or instantiated
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu"
if os.environ.get("WAYLAND_DISPLAY"):
    # Sometimes forcing software rendering helps on Wayland if GPU crashes
    os.environ["LIBGL_ALWAYS_SOFTWARE"] = "1"

import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import socket

from qgis.PyQt.QtCore import Qt, QUrl, pyqtSignal, QObject
from qgis.PyQt.QtWidgets import (
    QAction, QDockWidget, QDialog, QVBoxLayout, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QMessageBox, QTextBrowser
)
from qgis.PyQt.QtGui import QIcon, QDesktopServices
try:
    from qgis.PyQt.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
    HAS_WEBENGINE = True
except ImportError:
    HAS_WEBENGINE = False

from qgis.core import QgsProject, QgsRasterLayer, QgsSettings, Qgis, QgsMessageLog


# Default URL for the Atlas web application
DEFAULT_ATLAS_URL = "https://atlas.geo-code.de"
SETTINGS_PREFIX = "atlas_pro_germany"


if HAS_WEBENGINE:
    class AtlasWebPage(QWebEnginePage):
        """Custom web engine page that intercepts 'atlas-plugin://' URLs."""

        def __init__(self, iface, parent=None):
            super().__init__(parent)
            self.iface = iface

        def acceptNavigationRequest(self, url, type, isMainFrame):
            """Intercepts navigation requests to handle custom plugin commands."""
            url_str = url.toString()
            if url_str.startswith("atlas-plugin://"):
                parsed = urlparse(url_str)
                if parsed.netloc == "add-layer":
                    params = parse_qs(parsed.query)
                    layer_url = params.get('url', [None])[0]
                    layer_name = params.get('name', ['Exported Layer'])[0]
                    if layer_url:
                        # Find the plugin instance to call the generic add method
                        # In QGIS, we usually find it via iface or global registry
                        # but here we can just use the provided iface logic
                        self.parent_plugin.add_raster_layer(layer_url, layer_name)
                return False  # Prevent the webview from actually navigating to this URL

            # Allow all other normal navigation
            return super().acceptNavigationRequest(url, type, isMainFrame)

        def addRasterLayer(self, url, name):
            """Add a raster layer to the QGIS project from a URL.

            Uses the GDAL /vsicurl/ virtual filesystem to load Cloud Optimized
            GeoTIFF (COG) files directly from HTTP URLs.
            """
            # Use /vsicurl/ for remote COG/GeoTIFF access via GDAL
            vsicurl_url = f"/vsicurl/{url}"
            layer = QgsRasterLayer(vsicurl_url, name, "gdal")

            if layer.isValid():
                QgsProject.instance().addMapLayer(layer)
                self.iface.messageBar().pushMessage(
                    "Atlas Pro Germany",
                    f"Layer '{name}' loaded successfully.",
                    level=Qgis.Success,
                    duration=5
                )
            else:
                # Fallback: try loading without /vsicurl/ prefix
                layer = QgsRasterLayer(url, name, "gdal")
                if layer.isValid():
                    QgsProject.instance().addMapLayer(layer)
                    self.iface.messageBar().pushMessage(
                        "Atlas Pro Germany",
                        f"Layer '{name}' loaded successfully.",
                        level=Qgis.Success,
                        duration=5
                    )
                else:
                    self.iface.messageBar().pushMessage(
                        "Atlas Pro Germany",
                        f"Failed to load layer '{name}'. The file may not be "
                        f"accessible or is not a valid raster format.",
                        level=Qgis.Critical,
                        duration=10
                    )

        def javaScriptConsoleMessage(self, level, message, line, source):
            """Forward JavaScript console messages to QGIS log."""
            if level >= QWebEnginePage.JavaScriptConsoleMessageLevel.WarningMessageLevel:
                QgsMessageLog.logMessage(
                    f"JS [{source}:{line}]: {message}",
                    "Atlas Pro Germany",
                    Qgis.Warning
                )
else:
    # Minimal stub class if WebEngine is missing
    class AtlasWebPage:
        pass


class Bridge(QObject):
    """Bridge to safely emit signals across threads."""
    load_requested = pyqtSignal(str, str)


class AtlasCommandHandler(BaseHTTPRequestHandler):
    """Local HTTP handler to receive commands from an external browser."""

    def log_message(self, format, *args):
        # Silence standard HTTP logs to keep QGIS console clean
        pass

    def do_GET(self):
        """Handle incoming command requests."""
        parsed = urlparse(self.path)
        
        if parsed.path == "/ping":
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.send_header('Access-Control-Allow-Origin', '*')
            self.end_headers()
            self.wfile.write(b'{"status": "ok", "plugin": "Atlas Pro Germany"}')
            return

        if parsed.path == "/add-layer":
            params = parse_qs(parsed.query)
            layer_url = params.get('url', [None])[0]
            layer_name = params.get('name', ['Exported Layer'])[0]
            
            QgsMessageLog.logMessage(
                f"HTTP Server: Received add-layer request for {layer_name} (URL: {layer_url})",
                "Atlas Pro Germany",
                Qgis.Info
            )
            
            if layer_url:
                # This call needs to happen on the main thread if it interacts with UI
                # but QgsProject.addMapLayer is generally thread-safe for the data part.
                # However, messageBar calls MUST be on main thread.
                # For simplicity and safety, we rely on the plugin instance.
                self.server.plugin.add_raster_layer(layer_url, layer_name)
                
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()
                self.wfile.write(b'{"status": "success"}')
                return

        self.send_response(404)
        self.end_headers()


class SettingsDialog(QDialog):
    """Simple settings dialog for configuring the Atlas URL."""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Atlas Pro Germany Settings")
        self.setMinimumWidth(400)

        layout = QVBoxLayout()

        # URL setting
        url_layout = QHBoxLayout()
        url_label = QLabel("Atlas URL:")
        self.url_input = QLineEdit()
        self.url_input.setPlaceholderText(DEFAULT_ATLAS_URL)

        settings = QgsSettings()
        current_url = settings.value(
            f"{SETTINGS_PREFIX}/url", DEFAULT_ATLAS_URL
        )
        self.url_input.setText(current_url)

        url_layout.addWidget(url_label)
        url_layout.addWidget(self.url_input)
        layout.addLayout(url_layout)

        # Buttons
        btn_layout = QHBoxLayout()
        self.reset_btn = QPushButton("Reset to Default")
        self.reset_btn.clicked.connect(self._reset_url)
        self.save_btn = QPushButton("Save")
        self.save_btn.clicked.connect(self._save)
        self.cancel_btn = QPushButton("Cancel")
        self.cancel_btn.clicked.connect(self.reject)

        btn_layout.addWidget(self.reset_btn)
        btn_layout.addStretch()
        btn_layout.addWidget(self.cancel_btn)
        btn_layout.addWidget(self.save_btn)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def _reset_url(self):
        """Reset URL to the default production URL."""
        self.url_input.setText(DEFAULT_ATLAS_URL)

    def _save(self):
        """Save settings and close the dialog."""
        settings = QgsSettings()
        url = self.url_input.text().strip() or DEFAULT_ATLAS_URL
        settings.setValue(f"{SETTINGS_PREFIX}/url", url)
        self.accept()


class DependencyMissingDialog(QDialog):
    """Dialog shown when QtWebEngineWidgets is missing."""

    def __init__(self, parent=None, plugin=None):
        super().__init__(parent)
        self.plugin = plugin
        self.setWindowTitle("Atlas Pro Germany - Component Missing")
        self.setMinimumSize(600, 500)

        layout = QVBoxLayout()

        # Header
        header = QLabel("<h3>Cannot start embedded Atlas Pro Germany</h3>")
        header.setStyleSheet("color: #d9534f;")  # Subtle red
        layout.addWidget(header)

        info = QLabel(
            "The <b>QtWebEngine</b> component is missing from your QGIS installation. "
            "This is required to show the Atlas web application directly inside QGIS."
        )
        info.setWordWrap(True)
        layout.addWidget(info)

        # Instructions (TextBrowser for Markdown/HTML)
        self.browser = QTextBrowser()
        self.browser.setReadOnly(True)
        self.browser.setOpenExternalLinks(True)
        
        # Load troubleshooting from README if possible
        troubleshooting_text = self._get_troubleshooting_info()
        self.browser.setHtml(troubleshooting_text)
        layout.addWidget(self.browser)

        # Buttons
        btn_layout = QHBoxLayout()
        self.browser_btn = QPushButton("Start in External Browser")
        self.browser_btn.setIcon(QIcon.fromTheme("browser"))
        self.browser_btn.clicked.connect(self._open_browser)
        self.browser_btn.setStyleSheet("font-weight: bold; padding: 5px;")

        self.close_btn = QPushButton("Close")
        self.close_btn.clicked.connect(self.reject)

        btn_layout.addWidget(self.browser_btn)
        btn_layout.addStretch()
        btn_layout.addWidget(self.close_btn)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def _get_troubleshooting_info(self):
        """Extract troubleshooting info from README.md or fallback to embedded text."""
        readme_path = os.path.join(os.path.dirname(__file__), "README.md")
        if os.path.exists(readme_path):
            try:
                with open(readme_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    # Extract troubleshooting section
                    start_marker = "## Troubleshooting and Compatibility"
                    end_marker = "## Usage"
                    if start_marker in content:
                        section = content.split(start_marker)[1]
                        if end_marker in section:
                            section = section.split(end_marker)[0]
                        
                        # Very basic Markdown-ish to HTML conversion for the dialog
                        html = f"<h2>{start_marker[3:]}</h2>"
                        lines = section.strip().split('\n')
                        for line in lines:
                            line = line.strip()
                            if line.startswith('###'):
                                html += f"<h3>{line[4:]}</h3>"
                            elif line.startswith('####'):
                                html += f"<h4>{line[5:]}</h4>"
                            elif line.startswith('- '):
                                html += f"<ul><li>{line[2:]}</li></ul>"
                            elif line.startswith('```'):
                                continue # Skip code block markers for simplicity or wrap them
                            elif '`' in line:
                                html += f"<p>{line.replace('`', '<code>', 1).replace('`', '</code>', 1)}</p>"
                            elif line:
                                html += f"<p>{line}</p>"
                        return html
            except Exception:
                pass

        # Fallback hardcoded HTML if README reading fails
        return (
            "<h2>How to fix this:</h2>"
            "<p><b>Ubuntu / Debian / Mint:</b><br>"
            "<code>sudo apt install python3-pyqt5.qtwebengine</code></p>"
            "<p><b>Fedora:</b><br>"
            "<code>sudo dnf install python3-qt5-webengine</code></p>"
            "<p><b>Windows (OSGeo4W):</b><br>"
            "1. Open <b>OSGeo4W Shell</b><br>"
            "2. Run: <code>python3 -m pip install PyQtWebEngine</code><br>"
            "3. Restart QGIS</p>"
            "<p><b>MacOS:</b><br>"
            "Ensure you are using the official QGIS.org distribution.</p>"
        )

    def _open_browser(self):
        """Call plugin's open_in_browser and close dialog."""
        if self.plugin:
            self.plugin.open_in_browser()
        self.accept()

class AtlasProGermany:
    """Main QGIS plugin class for Atlas Pro Germany."""

    def __init__(self, iface):
        """Initialize the plugin."""
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        self.menu_name = "Atlas Pro Germany"
        
        # Initialize attributes to avoid AttributeError
        self.actions = []
        self.dock_widget = None
        self.webview = None
        self.page = None
        
        # Bridge for thread-safe cross-thread calls
        self.bridge = Bridge()
        self.bridge.load_requested.connect(self._do_add_raster_layer)
        
        # Start local command server
        self.server_port = 5001
        self.server = None
        self.server_thread = None
        self._start_server()

    def _start_server(self):
        """Start a lightweight HTTP server in a background thread."""
        try:
            self.server = HTTPServer(('127.0.0.1', self.server_port), AtlasCommandHandler)
            self.server.plugin = self
            self.server_thread = threading.Thread(target=self.server.serve_forever)
            self.server_thread.daemon = True
            self.server_thread.start()
            QgsMessageLog.logMessage(
                f"Local command server started on port {self.server_port}",
                "Atlas Pro Germany",
                Qgis.Info
            )
        except Exception as e:
            QgsMessageLog.logMessage(
                f"Failed to start local server on port {self.server_port}: {e}",
                "Atlas Pro Germany",
                Qgis.Critical
            )

    def initGui(self):
        """Create the plugin menu items and toolbar icons."""
        # Main action: toggle the Atlas panel or open in browser
        icon_path = os.path.join(self.plugin_dir, "icon.png")
        
        if not HAS_WEBENGINE:
            # Fallback mode action: Open in system browser
            self.toggle_action = QAction(
                QIcon(icon_path),
                "Atlas Pro Germany (Browser)",
                self.iface.mainWindow()
            )
            self.toggle_action.triggered.connect(self.show_dependency_dialog)
            self.toggle_action.setStatusTip(
                "Open Atlas Pro Germany Troubleshooting / External Browser"
            )
            
            # Help action for missing dependencies
            self.help_action = QAction(
                "Installation Help...",
                self.iface.mainWindow()
            )
            self.help_action.triggered.connect(self.show_dependency_dialog)
            
            self.iface.messageBar().pushMessage(
                "Atlas Pro Germany",
                "WebEngine components missing. Please click the plugin icon for instructions.",
                level=Qgis.Warning,
                duration=7
            )
        else:
            # Normal embedded mode action
            self.toggle_action = QAction(
                QIcon(icon_path),
                "Atlas Pro Germany",
                self.iface.mainWindow()
            )
            self.toggle_action.setCheckable(True)
            self.toggle_action.triggered.connect(self.toggle_panel)
            self.toggle_action.setStatusTip(
                "Toggle the Atlas Pro Germany panel"
            )

        # Common setup for toggle action
        self.iface.addToolBarIcon(self.toggle_action)
        self.iface.addPluginToWebMenu(self.menu_name, self.toggle_action)
        self.actions.append(self.toggle_action)

        # Settings action (always available)
        self.settings_action = QAction(
            "Settings...",
            self.iface.mainWindow()
        )
        self.settings_action.triggered.connect(self.show_settings)
        self.iface.addPluginToWebMenu(self.menu_name, self.settings_action)
        self.actions.append(self.settings_action)

        if HAS_WEBENGINE:
            # Reload action only makes sense for embedded view
            self.reload_action = QAction(
                "Reload Atlas Pro Germany",
                self.iface.mainWindow()
            )
            self.reload_action.triggered.connect(self.reload_atlas)
            self.iface.addPluginToWebMenu(self.menu_name, self.reload_action)
            self.actions.append(self.reload_action)
        else:
            # "Open in browser" action for menu (duplicated to make it obvious)
            self.browser_action = QAction(
                "Open Atlas in Browser",
                self.iface.mainWindow()
            )
            self.browser_action.triggered.connect(self.show_dependency_dialog)
            self.iface.addPluginToWebMenu(self.menu_name, self.browser_action)
            self.actions.append(self.browser_action)
            
            # Add help action to menu if missing dependencies
            if hasattr(self, 'help_action'):
                self.iface.addPluginToWebMenu(self.menu_name, self.help_action)
                self.actions.append(self.help_action)

    def unload(self):
        """Remove the plugin menu items, toolbar icons, and dock widget."""
        for action in self.actions:
            self.iface.removePluginWebMenu(self.menu_name, action)
            self.iface.removeToolBarIcon(action)

        if self.dock_widget:
            self.iface.removeDockWidget(self.dock_widget)
            self.dock_widget.deleteLater()
            self.dock_widget = None

        self.webview = None
        self.page = None

        if self.server:
            self.server.shutdown()
            self.server = None
            if self.server_thread:
                self.server_thread.join(timeout=1)

    def add_raster_layer(self, url, name):
        """Add a raster layer to the QGIS project from a URL.
        
        This method is called from both the embedded web view and the 
        local HTTP command server. It uses a Qt signal to ensure the
        actual loading happens on the QGIS main thread.
        """
        self.bridge.load_requested.emit(url, name)

    def _do_add_raster_layer(self, url, name):
        """Internal helper to actually load the layer (must be on main thread)."""
        QgsMessageLog.logMessage(
            f"Attempting to load layer: {name} (URL: {url})",
            "Atlas Pro Germany",
            Qgis.Info
        )
        # Use /vsicurl/ for remote COG/GeoTIFF access via GDAL
        vsicurl_url = f"/vsicurl/{url}"
        layer = QgsRasterLayer(vsicurl_url, name, "gdal")

        if layer.isValid():
            QgsProject.instance().addMapLayer(layer)
            self.iface.messageBar().pushMessage(
                "Atlas Pro Germany",
                f"Layer '{name}' loaded successfully.",
                level=Qgis.Success,
                duration=5
            )
        else:
            # Fallback: try loading without /vsicurl/ prefix
            layer = QgsRasterLayer(url, name, "gdal")
            if layer.isValid():
                QgsProject.instance().addMapLayer(layer)
                self.iface.messageBar().pushMessage(
                    "Atlas Pro Germany",
                    f"Layer '{name}' loaded successfully.",
                    level=Qgis.Success,
                    duration=5
                )
            else:
                self.iface.messageBar().pushMessage(
                    "Atlas Pro Germany",
                    f"Failed to load layer '{name}'. The file may not be "
                    f"accessible or is not a valid raster format.",
                    level=Qgis.Critical,
                    duration=10
                )

    def _create_dock_widget(self):
        """Create the dock widget with the embedded Atlas web view."""
        self.dock_widget = QDockWidget(
            "Atlas Pro Germany", self.iface.mainWindow()
        )
        self.dock_widget.setObjectName("AtlasProGermanyDock")
        self.dock_widget.setAllowedAreas(
            Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
        )

        # Set up web view with custom page
        self.webview = QWebEngineView()
        self.page = AtlasWebPage(self.iface, self.webview)
        self.page.parent_plugin = self  # Give page access to plugin methods
        self.webview.setPage(self.page)

        # Inject the QGIS marker after page load
        self.webview.loadFinished.connect(self._on_page_loaded)

        # Load the configured Atlas URL
        settings = QgsSettings()
        url = settings.value(
            f"{SETTINGS_PREFIX}/url", DEFAULT_ATLAS_URL
        )
        self.webview.setUrl(QUrl(url))

        self.dock_widget.setWidget(self.webview)
        self.dock_widget.resize(520, 700)

        # Sync the toggle action with dock widget visibility
        self.dock_widget.visibilityChanged.connect(
            self.toggle_action.setChecked
        )

        self.iface.addDockWidget(
            Qt.RightDockWidgetArea, self.dock_widget
        )

    def _on_page_loaded(self, ok):
        """Inject QGIS environment markers after the page finishes loading."""
        if not ok:
            return

        # Inject the marker so the frontend knows it's inside QGIS
        js_code = "window.isAtlasPlugin = true; console.log('Atlas Plugin Marker Injected');"
        self.page.runJavaScript(js_code)

    def toggle_panel(self, checked):
        """Toggle the Atlas dock widget visibility."""
        if self.dock_widget is None:
            self._create_dock_widget()
            return

        if checked:
            self.dock_widget.show()
        else:
            self.dock_widget.hide()

    def show_settings(self):
        """Show the settings dialog."""
        dialog = SettingsDialog(self.iface.mainWindow())
        if dialog.exec_() == QDialog.Accepted:
            if self.webview:
                settings = QgsSettings()
                url = settings.value(
                    f"{SETTINGS_PREFIX}/url", DEFAULT_ATLAS_URL
                )
                self.webview.setUrl(QUrl(url))

    def reload_atlas(self):
        """Reload the Atlas web application in the web view."""
        if self.webview:
            self.webview.reload()
        else:
            QMessageBox.information(
                self.iface.mainWindow(),
                "Atlas Pro Germany",
                "The Atlas panel is not open. Click the toolbar icon "
                "to open it first."
            )

    def open_in_browser(self):
        """Open the Atlas web application in the system's default browser."""
        settings = QgsSettings()
        url = settings.value(
            f"{SETTINGS_PREFIX}/url", DEFAULT_ATLAS_URL
        )
        QDesktopServices.openUrl(QUrl(url))
        
        self.iface.messageBar().pushMessage(
            "Atlas Pro Germany",
            "Opening Atlas Pro in your browser. The plugin is ready to receive layers.",
            level=Qgis.Success,
            duration=5
        )

    def show_dependency_dialog(self):
        """Show the detailed dependency missing dialog."""
        dialog = DependencyMissingDialog(self.iface.mainWindow(), self)
        dialog.exec_()
