# -*- coding: utf-8 -*-
"""S2S-backed Nighttime Lights raster datasource widget."""

import os
from urllib.parse import quote

from qgis.core import (
    QgsApplication,
    QgsMapLayerProxyModel,
    QgsMapLayerType,
    QgsVectorLayer,
)
from qgis.PyQt.QtCore import QSettings
from qgis.PyQt.QtWidgets import QFileDialog, QLabel, QMessageBox, QSizePolicy

from geest.core import S2STaskGate
from geest.core.constants import DEFAULT_S2S_NTL_FIELD
from geest.core.tasks import S2SDownloaderTask

from .download_task_controls import DownloadTaskControls
from .raster_datasource_widget import RasterDataSourceWidget
from .s2s_datasource_widget import S2SDataSourceWidget


class S2SNTLRasterDataSourceWidget(RasterDataSourceWidget):
    """Datasource widget that fetches NTL from S2S for regional grid scoring."""

    VECTOR_EXTENSIONS = {".gpkg", ".shp", ".geojson", ".json", ".sqlite", ".fgb", ".parquet"}

    # ------------------------------------------------------------------
    # Configuration hooks — subclasses override these to customise behaviour
    # ------------------------------------------------------------------

    def _get_s2s_filename(self) -> str:
        """Return the output filename stem (without extension)."""
        return "s2s_nighttime_lights"

    def _get_gate_label(self) -> str:
        """Return the S2S gate label used for serialising concurrent downloads."""
        return "widget:nighttime_lights"

    def _get_s2s_fields(self) -> list:
        """Return the list of S2S fields to fetch."""
        return [self.s2s_ntl_field]

    def _get_s2s_output_path(self, working_directory: str) -> str:
        """Return the full output path for the S2S GeoPackage."""
        return os.path.join(working_directory, "study_area", f"{self._get_s2s_filename()}.gpkg")

    def _resolve_default_s2s_output_path(self, working_directory: str) -> str:
        """Return the default S2S output path for auto-load."""
        if not working_directory:
            return ""
        return self._get_s2s_output_path(working_directory)

    def _get_s2s_field_name(self) -> str:
        """Return a human-readable field name for UI messages."""
        return "nighttime lights"

    def _get_s2s_success_message(self) -> str:
        """Return the status text shown on successful download."""
        return "S2S nighttime lights downloaded"

    def _get_empty_fields_error(self) -> str:
        """Return the error message when no fields are entered."""
        return f"No S2S {self._get_s2s_field_name()} field is configured."

    def _get_missing_field_error(self) -> str:
        """Return the error message when a required field is not configured."""
        return f"No S2S {self._get_s2s_field_name()} field is configured."

    def _validate_required_fields(self, fields: list) -> str:
        """Validate fields and return an error message, or empty string if valid."""
        if not fields:
            return self._get_missing_field_error()
        return ""

    # ------------------------------------------------------------------
    # UI setup
    # ------------------------------------------------------------------

    def add_internal_widgets(self) -> None:
        """Build raster controls and append S2S fetch controls."""
        super().add_internal_widgets()

        self.raster_layer_combo.setFilters(
            QgsMapLayerProxyModel.Filter.RasterLayer | QgsMapLayerProxyModel.Filter.VectorLayer
        )
        self.raster_layer_combo.setToolTip("Select raster or vector layer from the map")
        self.raster_layer_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

        existing_s2s_field = getattr(self, "s2s_ntl_field", "")
        self.s2s_ntl_field = existing_s2s_field or self.attributes.get("s2s_ntl_field") or DEFAULT_S2S_NTL_FIELD
        self.s2s_controls = DownloadTaskControls(
            button_text="Download from S2S",
            tooltip="Download data from Space2Stats",
            click_handler=self.fetch_from_s2s,
        )
        self.s2s_fetch_button = self.s2s_controls.button
        self.layout.addWidget(self.s2s_controls.container)

        self.s2s_status_label = QLabel()
        self.s2s_status_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        self.s2s_status_label.setMinimumWidth(90)
        self.s2s_status_label.setMaximumWidth(170)
        self.layout.addWidget(self.s2s_status_label)

        self.raster_line_edit.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        self.layout.setStretchFactor(self.raster_layer_combo, 5)
        self.layout.setStretchFactor(self.raster_line_edit, 5)

        self.s2s_task = None
        self._s2s_gate_token = None
        self.s2s_output_path = self.attributes.get("s2s_output_path", "")
        self._s2s_error_handled = False

        if not self.s2s_output_path:
            settings = QSettings()
            working_directory = settings.value("last_working_directory", "")
            candidate_path = self._resolve_default_s2s_output_path(working_directory)
            if candidate_path and os.path.exists(candidate_path):
                self.s2s_output_path = candidate_path
        if self.s2s_output_path and os.path.exists(self.s2s_output_path):
            self._select_existing_s2s_output_layer()

    # ------------------------------------------------------------------
    # Template method: S2S download flow
    # ------------------------------------------------------------------

    def fetch_from_s2s(self) -> None:
        """Template method: fetch S2S summary rows for downstream grid-based regional scoring."""
        settings = QSettings()
        working_directory = settings.value("last_working_directory", "")
        if not working_directory or not os.path.exists(working_directory):
            QMessageBox.warning(
                self,
                "No Working Directory",
                "No valid working directory found. Please create or open a project first.",
            )
            return

        study_area_gpkg = os.path.join(working_directory, "study_area", "study_area.gpkg")
        if not os.path.exists(study_area_gpkg):
            QMessageBox.warning(
                self,
                "Study Area Required",
                "Study area GeoPackage not found. Please create a project first.",
            )
            return

        fields = self._get_s2s_fields()
        missing_field_error = self._validate_required_fields(fields)
        if missing_field_error:
            QMessageBox.warning(self, "S2S Field Required", missing_field_error)
            return

        aoi_layer = QgsVectorLayer(f"{study_area_gpkg}|layername=study_area_bboxes", "study_area_bboxes", "ogr")
        if not aoi_layer.isValid() or aoi_layer.featureCount() == 0:
            QMessageBox.warning(
                self,
                "Invalid Study Area",
                "Could not load study_area_bboxes from study_area.gpkg.",
            )
            return

        aoi_feature = S2SDataSourceWidget._build_aoi_feature(aoi_layer)
        if not aoi_feature:
            QMessageBox.warning(self, "Invalid AOI", "Failed to build AOI feature from study area geometry.")
            return

        filename = self._get_s2s_filename()
        self.s2s_output_path = self._get_s2s_output_path(working_directory)

        gate_label = self._get_gate_label()
        token = S2STaskGate.acquire(gate_label)
        if not token:
            active = S2STaskGate.active_label() or "another panel"
            QMessageBox.information(
                self,
                "S2S Busy",
                f"Another S2S download is currently running ({active}). Please wait for it to finish.",
            )
            return
        self._s2s_gate_token = token

        self.s2s_controls.set_running()
        self._set_status("Fetching S2S data...")
        self._s2s_error_handled = False

        self.s2s_task = S2SDownloaderTask(
            aoi=aoi_feature,
            fields=fields,
            working_dir=working_directory,
            filename=filename,
            spatial_join_method="centroid",
            geometry="point",
            delete_existing=True,
        )
        self.s2s_task.progress_updated.connect(self._on_s2s_progress)
        self.s2s_task.error_occurred.connect(self._on_s2s_error)
        self.s2s_task.taskCompleted.connect(self._on_s2s_completed)
        self.s2s_task.taskTerminated.connect(self._on_s2s_terminated)
        QgsApplication.taskManager().addTask(self.s2s_task)

    # ------------------------------------------------------------------
    # S2S task callbacks (shared across NTL and subclasses)
    # ------------------------------------------------------------------

    def _on_s2s_progress(self, message: str) -> None:
        """Update S2S status text from task progress."""
        self._set_status(message)
        self.s2s_controls.update_progress(message)

    def _on_s2s_error(self, message: str) -> None:
        """Handle S2S task errors."""
        self._s2s_error_handled = True
        self._release_s2s_gate()
        self._set_status("S2S download failed")
        self.s2s_controls.set_download_failed(message)
        friendly_message = S2SDataSourceWidget._humanize_s2s_error(message)
        QMessageBox.warning(self, "S2S Download Failed", friendly_message)

    def _on_s2s_terminated(self) -> None:
        """Handle cancelled/terminated S2S tasks."""
        self._release_s2s_gate()
        if self._s2s_error_handled:
            return
        self._set_status("S2S task terminated")
        self.s2s_controls.set_cancelled()

    def _on_s2s_completed(self) -> None:
        """Load output layer after successful S2S task completion."""
        self._release_s2s_gate()
        self.s2s_controls.reset()

        if not os.path.exists(self.s2s_output_path):
            self._set_status("S2S output not found")
            self.s2s_controls.set_not_found(self.s2s_output_path)
            return

        layer_name = os.path.splitext(os.path.basename(self.s2s_output_path))[0]
        s2s_layer = S2SDataSourceWidget._load_or_reuse_vector_layer(self.s2s_output_path, layer_name)
        if s2s_layer is None:
            self.s2s_controls.set_load_failed(self.s2s_output_path)
            self._set_status("S2S output invalid")
            QMessageBox.warning(self, "Invalid S2S Output", "S2S output file exists but could not be loaded.")
            return

        self.raster_line_edit.clear()
        self.raster_line_edit.setVisible(False)
        self.raster_layer_combo.setVisible(True)
        self.raster_layer_combo.setLayer(s2s_layer if s2s_layer.isValid() else None)

        self._set_status(self._get_s2s_success_message())
        self.s2s_controls.set_downloaded()
        self.update_attributes()

    def _release_s2s_gate(self) -> None:
        """Release global S2S gate lock for this widget task."""
        if getattr(self, "_s2s_gate_token", None):
            S2STaskGate.release(self._s2s_gate_token)
            self._s2s_gate_token = None

    def _set_status(self, message: str) -> None:
        """Set status label text when available."""
        if hasattr(self, "s2s_status_label") and self.s2s_status_label is not None:
            self.s2s_status_label.setText(message)

    def _select_existing_s2s_output_layer(self) -> None:
        """Auto-select existing S2S output layer from disk."""
        if not self.s2s_output_path or not os.path.exists(self.s2s_output_path):
            return

        layer_name = os.path.splitext(os.path.basename(self.s2s_output_path))[0]
        output_layer = S2SDataSourceWidget._load_or_reuse_vector_layer(self.s2s_output_path, layer_name)
        if output_layer is None:
            self._set_status("S2S output invalid")
            return

        self.raster_line_edit.clear()
        self.raster_line_edit.setVisible(False)
        self.raster_layer_combo.setVisible(True)
        self.raster_layer_combo.setLayer(output_layer)
        self.s2s_controls.set_downloaded()
        self.update_attributes()

    # ------------------------------------------------------------------
    # Manual raster selection (NTL-specific, kept here for subclasses)
    # ------------------------------------------------------------------

    def select_raster(self) -> None:
        """Select raster or vector file for nighttime lights input."""
        last_dir = self.settings.value("GeoE3/lastRasterDir", "")
        if not last_dir:
            last_dir = self.settings.value("GeoE3/lastShapefileDir", "")
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Select Nighttime Lights Layer",
            last_dir,
            "Supported (*.vrt *.tif *.asc *.gpkg *.shp *.geojson *.json *.sqlite *.fgb *.parquet);;"
            "Raster (*.vrt *.tif *.asc);;"
            "Vector (*.gpkg *.shp *.geojson *.json *.sqlite *.fgb *.parquet);;"
            "All files (*)",
        )
        if not file_path:
            return

        self.raster_layer_combo.setVisible(False)
        self.raster_line_edit.setVisible(True)
        self.raster_line_edit.setText(file_path)
        parent_directory = os.path.dirname(file_path)
        self.settings.setValue("GeoE3/lastRasterDir", parent_directory)
        self.settings.setValue("GeoE3/lastShapefileDir", parent_directory)
        self.resizeEvent(None)

    def clear_raster(self):
        """Clear selected file and reset widget state."""
        super().clear_raster()

    @classmethod
    def _is_vector_path(cls, file_path: str) -> bool:
        """Return True when file extension represents a known vector format."""
        if not file_path:
            return False
        extension = os.path.splitext(file_path)[1].lower()
        return extension in cls.VECTOR_EXTENSIONS

    def update_attributes(self):
        """Update raster attributes and S2S metadata."""
        super().update_attributes()
        selected_layer = self.raster_layer_combo.currentLayer()
        selected_path = self.raster_line_edit.text().strip()
        is_vector_file = self._is_vector_path(selected_path)
        is_vector_layer = bool(selected_layer and selected_layer.type() == QgsMapLayerType.VectorLayer)

        self.attributes[f"{self.widget_key}_input_type"] = "none"

        if is_vector_file:
            self.attributes[f"{self.widget_key}_vector"] = quote(selected_path)
            self.attributes[f"{self.widget_key}_raster"] = ""
            self.attributes[f"{self.widget_key}_selected_field"] = ""
            self.attributes[f"{self.widget_key}_input_type"] = "vector"
        elif is_vector_layer and self._is_vector_path(selected_layer.source()):
            self.attributes[f"{self.widget_key}_vector"] = quote(selected_layer.source())
            self.attributes[f"{self.widget_key}_selected_field"] = ""
            self.attributes[f"{self.widget_key}_input_type"] = "vector"
        elif selected_path:
            self.attributes[f"{self.widget_key}_input_type"] = "raster"
        else:
            self.attributes[f"{self.widget_key}_vector"] = ""
            self.attributes[f"{self.widget_key}_selected_field"] = ""
            if selected_layer:
                self.attributes[f"{self.widget_key}_input_type"] = "raster"

        if not hasattr(self, "s2s_ntl_field"):
            return
        self.attributes["s2s_ntl_field"] = self.s2s_ntl_field
        self.attributes["s2s_spatial_join_method"] = "centroid"
        if self.s2s_output_path:
            self.attributes["s2s_output_path"] = self.s2s_output_path
