import json
import os

from qgis.core import QgsRasterLayer
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtWidgets import (
    QApplication, QDialog, QFileDialog, QHBoxLayout, QHeaderView, QLabel,
    QMessageBox, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout,
)

VALID_COLOR = QColor(214, 245, 214)
INVALID_COLOR = QColor(250, 214, 214)


class SourcesEditorDialog(QDialog):
    COLUMNS = ["Group", "Name", "URL", "Active"]
    ACTIVE_COL = 3

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

        self.setWindowTitle("Edit Basemap Sources")
        self.resize(900, 500)

        layout = QVBoxLayout(self)
        layout.addWidget(QLabel(
            "Add, edit, or remove basemap sources below. Each row needs a "
            "group (e.g. 'Switzerland'), a display name, and a QGIS raster "
            "layer source URL (the same string used for WMS/WMTS/XYZ "
            "connections). Check 'Active' to have that layer turned on "
            "automatically when basemaps are loaded. Use 'Validate' to test "
            "that every URL loads, and 'Export...' to save the current list "
            "to a JSON file for sharing."
        ))

        self.table = QTableWidget(0, len(self.COLUMNS), self)
        self.table.setHorizontalHeaderLabels(self.COLUMNS)
        header = self.table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive)
        header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(self.ACTIVE_COL, QHeaderView.ResizeMode.ResizeToContents)
        layout.addWidget(self.table)

        action_row = QHBoxLayout()
        add_btn = QPushButton("Add Row")
        remove_btn = QPushButton("Remove Selected Row(s)")
        reset_btn = QPushButton("Reset to Defaults")
        validate_btn = QPushButton("Validate")
        export_btn = QPushButton("Export...")
        action_row.addWidget(add_btn)
        action_row.addWidget(remove_btn)
        action_row.addWidget(reset_btn)
        action_row.addWidget(validate_btn)
        action_row.addWidget(export_btn)
        action_row.addStretch()
        layout.addLayout(action_row)

        bottom_row = QHBoxLayout()
        save_btn = QPushButton("Save")
        cancel_btn = QPushButton("Cancel")
        bottom_row.addStretch()
        bottom_row.addWidget(save_btn)
        bottom_row.addWidget(cancel_btn)
        layout.addLayout(bottom_row)

        add_btn.clicked.connect(lambda: self._add_row())
        remove_btn.clicked.connect(self._remove_selected_rows)
        reset_btn.clicked.connect(self._reset_to_defaults)
        validate_btn.clicked.connect(self._validate)
        export_btn.clicked.connect(self._export)
        save_btn.clicked.connect(self._save)
        cancel_btn.clicked.connect(self.reject)

        start_path = (
            self.user_config_path
            if os.path.exists(self.user_config_path)
            else self.default_config_path
        )
        self._load(start_path)

    def _load(self, path):
        self.table.setRowCount(0)
        try:
            with open(path, "r", encoding="utf-8") as f:
                data = json.load(f)
        except (OSError, ValueError):
            data = {}
        for group, entries in data.items():
            for entry in entries:
                self._add_row(
                    group,
                    entry.get("name", ""),
                    entry.get("url", ""),
                    bool(entry.get("active", False)),
                )

    def _add_row(self, group="", name="", url="", active=False):
        row = self.table.rowCount()
        self.table.insertRow(row)
        self.table.setItem(row, 0, QTableWidgetItem(group))
        self.table.setItem(row, 1, QTableWidgetItem(name))
        self.table.setItem(row, 2, QTableWidgetItem(url))

        active_item = QTableWidgetItem()
        active_item.setFlags(
            Qt.ItemFlag.ItemIsUserCheckable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsSelectable
        )
        active_item.setCheckState(
            Qt.CheckState.Checked if active else Qt.CheckState.Unchecked
        )
        self.table.setItem(row, self.ACTIVE_COL, active_item)

    def _remove_selected_rows(self):
        rows = sorted({idx.row() for idx in self.table.selectedIndexes()}, reverse=True)
        for row in rows:
            self.table.removeRow(row)

    def _reset_to_defaults(self):
        reply = QMessageBox.question(
            self,
            "Reset to Defaults",
            "Discard all rows in this dialog and reload the built-in "
            "default sources?\n\nThis does not overwrite your saved file "
            "until you click Save.",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if reply == QMessageBox.StandardButton.Yes:
            self._load(self.default_config_path)

    def _validate(self):
        rows_to_check = [
            row for row in range(self.table.rowCount())
            if self._cell(row, 0) and self._cell(row, 1) and self._cell(row, 2)
        ]
        if not rows_to_check:
            QMessageBox.information(
                self, "Validate Sources", "No complete rows to validate.",
            )
            return

        QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
        invalid = []
        try:
            for row in rows_to_check:
                name = self._cell(row, 1)
                url = self._cell(row, 2)
                layer = QgsRasterLayer(url, name, "wms")
                ok = layer.isValid()
                color = VALID_COLOR if ok else INVALID_COLOR
                for col in range(3):
                    item = self.table.item(row, col)
                    if item:
                        item.setBackground(color)
                if not ok:
                    invalid.append(f"Row {row + 1}: {name}")
        finally:
            QApplication.restoreOverrideCursor()

        if invalid:
            QMessageBox.warning(
                self, "Validate Sources",
                f"{len(invalid)} of {len(rows_to_check)} source(s) could not "
                "be loaded (check the URL, or your internet connection):\n\n"
                + "\n".join(invalid),
            )
        else:
            QMessageBox.information(
                self, "Validate Sources",
                f"All {len(rows_to_check)} source(s) loaded successfully.",
            )

    def _cell(self, row, col):
        item = self.table.item(row, col)
        return item.text().strip() if item else ""

    def _collect(self):
        data = {}
        incomplete_rows = []
        for row in range(self.table.rowCount()):
            group = self._cell(row, 0)
            name = self._cell(row, 1)
            url = self._cell(row, 2)
            active_item = self.table.item(row, self.ACTIVE_COL)
            active = bool(active_item and active_item.checkState() == Qt.CheckState.Checked)
            if not group and not name and not url:
                continue
            if not (group and name and url):
                incomplete_rows.append(row + 1)
                continue
            data.setdefault(group, []).append({
                "name": name, "url": url, "active": active,
            })
        return data, incomplete_rows

    def _save(self):
        data, incomplete_rows = self._collect()
        if incomplete_rows:
            QMessageBox.warning(
                self,
                "Edit Basemap Sources",
                "Row(s) " + ", ".join(str(r) for r in incomplete_rows)
                + " are missing a group, name, or URL.\n\n"
                "Fill them in or remove the row before saving.",
            )
            return
        if not data:
            QMessageBox.warning(
                self, "Edit Basemap Sources",
                "Nothing to save: add at least one complete source.",
            )
            return

        os.makedirs(os.path.dirname(self.user_config_path), exist_ok=True)
        try:
            with open(self.user_config_path, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except OSError as e:
            QMessageBox.critical(
                self, "Edit Basemap Sources",
                f"Could not save sources file:\n{self.user_config_path}\n\n{e}",
            )
            return

        self.accept()

    def _export(self):
        data, incomplete_rows = self._collect()
        if incomplete_rows:
            QMessageBox.warning(
                self,
                "Export Basemap Sources",
                "Row(s) " + ", ".join(str(r) for r in incomplete_rows)
                + " are missing a group, name, or URL.\n\n"
                "Fill them in or remove the row before exporting.",
            )
            return
        if not data:
            QMessageBox.warning(
                self, "Export Basemap Sources",
                "Nothing to export: add at least one complete source.",
            )
            return

        path, _ = QFileDialog.getSaveFileName(
            self, "Export Basemap Sources", "basemaps.json", "JSON Files (*.json)",
        )
        if not path:
            return

        try:
            with open(path, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except OSError as e:
            QMessageBox.critical(
                self, "Export Basemap Sources",
                f"Could not export sources file:\n{path}\n\n{e}",
            )
            return

        QMessageBox.information(
            self, "Export Basemap Sources", f"Exported to:\n{path}",
        )
