import json
import os
import shutil

from qgis.PyQt.QtWidgets import QAction, QMessageBox
from qgis.PyQt.QtGui import QIcon, QKeySequence
from qgis.core import QgsApplication, QgsProject, QgsRasterLayer

from .sources_editor import SourcesEditorDialog

DEFAULT_SOURCES_PATH = os.path.join(os.path.dirname(__file__), "basemaps_default.json")


def _user_sources_path():
    return os.path.join(QgsApplication.qgisSettingsDirPath(), "basemap_loader", "basemaps.json")


class BasemapLoader:
    def __init__(self, iface):
        self.iface = iface
        self.load_action = None
        self.edit_action = None

    def initGui(self):
        icon_path = os.path.join(os.path.dirname(__file__), "icon.png")
        icon = QIcon(icon_path) if os.path.exists(icon_path) else QIcon()

        self.load_action = QAction(icon, "Load Basemaps", self.iface.mainWindow())
        self.load_action.setToolTip("Load all basemaps (Switzerland + World) at the bottom of the layer tree")
        self.load_action.setShortcut(QKeySequence("Ctrl+Shift+B"))
        self.load_action.triggered.connect(self.load_basemaps)
        self.iface.addToolBarIcon(self.load_action)
        self.iface.addPluginToMenu("&Basemap Loader", self.load_action)

        self.edit_action = QAction("Edit Basemap Sources...", self.iface.mainWindow())
        self.edit_action.triggered.connect(self.edit_sources)
        self.iface.addPluginToMenu("&Basemap Loader", self.edit_action)

    def unload(self):
        self.iface.removeToolBarIcon(self.load_action)
        self.iface.removePluginMenu("&Basemap Loader", self.load_action)
        self.iface.removePluginMenu("&Basemap Loader", self.edit_action)

    def _add_layer(self, group, name, url, active=False):
        layer = QgsRasterLayer(url, name, "wms")
        if not layer.isValid():
            return False
        QgsProject.instance().addMapLayer(layer, False)
        node = group.addLayer(layer)
        node.setItemVisibilityChecked(active)
        node.setExpanded(False)
        return True

    def _ensure_user_sources(self):
        user_path = _user_sources_path()
        if not os.path.exists(user_path):
            os.makedirs(os.path.dirname(user_path), exist_ok=True)
            shutil.copyfile(DEFAULT_SOURCES_PATH, user_path)
        return user_path

    def _load_sources(self):
        user_path = self._ensure_user_sources()
        try:
            with open(user_path, "r", encoding="utf-8") as f:
                return json.load(f)
        except (OSError, ValueError) as e:
            QMessageBox.warning(
                self.iface.mainWindow(),
                "Basemap Loader",
                f"Could not read basemap sources file:\n{user_path}\n\n{e}\n\n"
                "Falling back to the built-in defaults.",
            )
            with open(DEFAULT_SOURCES_PATH, "r", encoding="utf-8") as f:
                return json.load(f)

    def edit_sources(self):
        user_path = self._ensure_user_sources()
        dialog = SourcesEditorDialog(user_path, DEFAULT_SOURCES_PATH, self.iface.mainWindow())
        dialog.exec()

    def load_basemaps(self):
        sources = self._load_sources()

        root = QgsProject.instance().layerTreeRoot()

        existing = root.findGroup("Basemaps")
        if existing:
            reply = QMessageBox.question(
                self.iface.mainWindow(),
                "Basemap Loader",
                "A 'Basemaps' group already exists.\n\nDo you want to replace it?",
                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            )
            if reply == QMessageBox.StandardButton.No:
                return
            for child in existing.findLayers():
                QgsProject.instance().removeMapLayer(child.layerId())
            root.removeChildNode(existing)

        basemaps_group = root.insertGroup(0, "Basemaps")
        basemaps_group.setExpanded(True)

        failed = []
        for group_name, entries in sources.items():
            group = basemaps_group.addGroup(group_name)
            group.setExpanded(False)
            for entry in entries:
                name = entry.get("name", "(unnamed)")
                active = bool(entry.get("active", False))
                if not self._add_layer(group, name, entry.get("url", ""), active):
                    failed.append(name)

        # Move Basemaps group to the bottom
        clone = basemaps_group.clone()
        root.addChildNode(clone)
        root.removeChildNode(basemaps_group)

        self.iface.mapCanvas().refresh()

        if failed:
            QMessageBox.warning(
                self.iface.mainWindow(),
                "Basemap Loader",
                "Basemaps loaded.\n\nThe following layers could not be added "
                "(check your internet connection or WMTS URL):\n"
                + "\n".join(f"  • {n}" for n in failed),
            )
