# -*- coding: utf-8 -*-
"""
/***************************************************************************
 Clc2Manning
                                 A QGIS plugin
 Extrae el manning para HECRAS del CORINE2018
 Generated by Plugin Builder
 ***************************************************************************/
"""

import os
import csv
from pathlib import Path

from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

import processing

from qgis.core import (
    QgsProject,
    QgsVectorLayer,
    QgsField,
    QgsFeature,
    QgsGeometry,
    QgsWkbTypes
)
from qgis.PyQt.QtCore import QVariant

from .Mod1_dialog import Mod1Dialog


class Clc2Manning:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)

        locale = QSettings().value("locale/userLocale")[0:2]
        locale_path = os.path.join(self.plugin_dir, "i18n", f"Clc2Manning_{locale}.qm")
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        self.actions = []
        self.menu = self.tr("&CLC2Manning")

        # Estado para evitar “doble ejecución”
        self.dlg = None
        self._is_dialog_open = False
        self._action_main = None

    def tr(self, message):
        return QCoreApplication.translate("Clc2Manning", message)

    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None
    ):
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)
        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.iface.addToolBarIcon(action)
        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)

        self.actions.append(action)
        return action

    def initGui(self):
        # ✅ Icono por ruta absoluta (si falla, sale “enchufito”)
        icon_path = os.path.join(self.plugin_dir, "icon.png")

        self._action_main = self.add_action(
            icon_path,
            text=self.tr("CLC to Manning"),
            callback=self.run,
            parent=self.iface.mainWindow()
        )

    def unload(self):
        for action in self.actions:
            self.iface.removePluginMenu(self.tr("&CLC2Manning"), action)
            self.iface.removeToolBarIcon(action)
        self.actions = []
        self._action_main = None
        self.dlg = None
        self._is_dialog_open = False

    # =========================
    # CSV
    # =========================
    def _csv_path(self) -> Path:
        return Path(self.plugin_dir) / "data" / "clc2018_code18_to_manning_es.csv"

    def _load_manning_map(self) -> dict:
        csv_path = self._csv_path()
        if not csv_path.exists():
            raise Exception(f"No se encontró el CSV interno en: {csv_path}")

        mapping = {}
        with csv_path.open("r", encoding="utf-8-sig", newline="") as f:
            reader = csv.DictReader(f)
            if "CODE_18" not in (reader.fieldnames or []) or "MANNING" not in (reader.fieldnames or []):
                raise Exception("El CSV debe tener columnas 'CODE_18' y 'MANNING'.")

            for row in reader:
                code_raw = (row.get("CODE_18") or "").strip()
                man_raw = (row.get("MANNING") or "").strip()
                if not code_raw or not man_raw:
                    continue
                try:
                    code = int(code_raw)
                    man = float(man_raw.replace(",", "."))
                except Exception:
                    continue
                mapping[code] = man

        if not mapping:
            raise Exception("El CSV no cargó ningún mapeo CODE_18 → MANNING.")
        return mapping

    # =========================
    # AOI layer
    # =========================
    def _make_aoi_layer(self, geom: QgsGeometry, crs_authid: str = None) -> QgsVectorLayer:
        if not crs_authid:
            crs_authid = self.iface.mapCanvas().mapSettings().destinationCrs().authid()
        aoi_layer = QgsVectorLayer(f"Polygon?crs={crs_authid}", "AOI_TMP", "memory")
        pr = aoi_layer.dataProvider()
        pr.addAttributes([QgsField("id", QVariant.Int)])
        aoi_layer.updateFields()

        feat = QgsFeature(aoi_layer.fields())
        feat["id"] = 1
        feat.setGeometry(geom)
        pr.addFeature(feat)
        aoi_layer.updateExtents()
        return aoi_layer

    # =========================
    # Find CORINE
    # =========================
    def _find_corine_layer_in_project(self) -> QgsVectorLayer:
        for lyr in QgsProject.instance().mapLayers().values():
            if not isinstance(lyr, QgsVectorLayer) or not lyr.isValid():
                continue
            if lyr.geometryType() != QgsWkbTypes.PolygonGeometry:
                continue
            if "CODE_18" in [f.name() for f in lyr.fields()]:
                return lyr
        raise Exception("No se encontró CORINE en el proyecto. Marca 'Cargar CORINE en el proyecto' en el Paso 1.")

    # =========================
    # Paso 3
    # =========================
    def _run_step3(self, aoi_geom: QgsGeometry):
        manning_map = self._load_manning_map()

        # Cargar CORINE siempre desde GPKG (evita geometrias invalidas expuestas)
        corine_layer = None
        if self.dlg is not None:
            gpkg_path = getattr(self.dlg, "corine_gpkg_path", None)
            corine_lyr_ref = getattr(self.dlg, "corine_layer", None)
            layer_name = corine_lyr_ref.name() if corine_lyr_ref else None
            if gpkg_path and layer_name:
                uri = f"{gpkg_path}|layername={layer_name}"
                fresh = QgsVectorLayer(uri, layer_name, "ogr")
                if fresh.isValid():
                    corine_layer = fresh
        if corine_layer is None:
            corine_layer = self._find_corine_layer_in_project()

        # CRS del AOI y del CORINE
        aoi_crs = getattr(self.dlg, "aoi_crs", None)
        aoi_crs_authid = aoi_crs.authid() if aoi_crs and aoi_crs.isValid() else None
        corine_crs_authid = corine_layer.crs().authid()

        aoi_layer = self._make_aoi_layer(aoi_geom, aoi_crs_authid)

        # Reproyectar AOI al CRS del CORINE si son diferentes
        if aoi_crs_authid and aoi_crs_authid != corine_crs_authid:
            reproject_result = processing.run(
                "native:reprojectlayer",
                {"INPUT": aoi_layer, "TARGET_CRS": corine_crs_authid, "OUTPUT": "memory:"}
            )
            aoi_layer = reproject_result["OUTPUT"]

        # Subset por bbox con QgsFeatureRequest (rapido, sin processing)
        # aoi_layer ya esta en el CRS del CORINE tras la reproyeccion
        from qgis.core import QgsFeatureRequest
        bbox = aoi_layer.extent()

        subset_layer = QgsVectorLayer(
            f"Polygon?crs={corine_crs_authid}", "CLC_SUBSET", "memory"
        )
        subset_layer.dataProvider().addAttributes(corine_layer.fields().toList())
        subset_layer.updateFields()
        feats = []
        for feat in corine_layer.getFeatures(QgsFeatureRequest().setFilterRect(bbox)):
            if feat.geometry() and not feat.geometry().isEmpty():
                new_feat = QgsFeature(subset_layer.fields())
                new_feat.setGeometry(feat.geometry())
                new_feat.setAttributes(feat.attributes())
                feats.append(new_feat)
        subset_layer.dataProvider().addFeatures(feats)
        subset_layer.updateExtents()

        # Fix geometrias solo sobre el subset (rapido)
        fix_result = processing.run(
            "native:fixgeometries",
            {"INPUT": subset_layer, "METHOD": 1, "OUTPUT": "TEMPORARY_OUTPUT"}
        )
        fix_output = fix_result["OUTPUT"]
        corine_fixed = fix_output if isinstance(fix_output, QgsVectorLayer) else QgsVectorLayer(fix_output, "CLC_FIXED", "ogr")

        # Clip
        res = processing.run(
            "native:clip",
            {"INPUT": corine_fixed, "OVERLAY": aoi_layer, "OUTPUT": "memory:"}
        )
        out_layer = res["OUTPUT"]
        out_layer.setName("CLC_Manning_AOI")

        pr = out_layer.dataProvider()
        pr.addAttributes([
            QgsField("Manning", QVariant.Double),
            QgsField("AREA_M2", QVariant.Double),
            QgsField("AREA_HA", QVariant.Double),
        ])
        out_layer.updateFields()

        idx_code = out_layer.fields().indexOf("CODE_18")
        idx_man = out_layer.fields().indexOf("Manning")
        idx_m2 = out_layer.fields().indexOf("AREA_M2")
        idx_ha = out_layer.fields().indexOf("AREA_HA")

        missing = set()
        out_layer.startEditing()

        for f in out_layer.getFeatures():
            fid = f.id()

            try:
                code_int = int(f[idx_code])
            except Exception:
                code_int = None

            man = None
            if code_int is not None:
                man = manning_map.get(code_int)
                if man is None:
                    missing.add(code_int)

            area_m2 = f.geometry().area()

            if idx_man != -1:
                out_layer.changeAttributeValue(fid, idx_man, man)
            if idx_m2 != -1:
                out_layer.changeAttributeValue(fid, idx_m2, area_m2)
            if idx_ha != -1:
                out_layer.changeAttributeValue(fid, idx_ha, area_m2 / 10000.0)

        out_layer.commitChanges()

        # ✅ Heredar estilo del CORINE si el usuario lo pidió
        try:
            if self.dlg is not None:
                self.dlg.apply_corine_style_to(out_layer)
        except Exception:
            pass

        QgsProject.instance().addMapLayer(out_layer, False)
        root = QgsProject.instance().layerTreeRoot()
        root.insertLayer(0, out_layer)
        self.iface.setActiveLayer(out_layer)

        if missing:
            self.iface.messageBar().pushWarning(
                "CLC → Manning",
                f"Capa creada, pero faltan códigos en el CSV (Manning = NULL) para: {sorted(missing)}"
            )
        else:
            self.iface.messageBar().pushInfo("CLC → Manning", "Capa resultado creada: CLC_Manning_AOI")
            if self.dlg is not None:
                self.dlg.reset_after_execution()

    # =========================
    # Run (anti-doble clic + traer al frente)
    # =========================
    def run(self):
        # Si ya está abierto: traer al frente, no abrir otro
        if self._is_dialog_open and self.dlg is not None:
            self.dlg.show()
            self.dlg.raise_()
            self.dlg.activateWindow()
            return

        # Crear/reciclar diálogo
        if self.dlg is None:
            self.dlg = Mod1Dialog(self.iface)
            # cuando se cierre (Aceptar/Cancelar/X) liberar el “lock”
            self.dlg.finished.connect(self._on_dialog_finished)

        self._is_dialog_open = True

        # Desactivar el botón mientras el diálogo está activo (evita doble clic)
        if self._action_main is not None:
            self._action_main.setEnabled(False)

        self.dlg.show()
        result = self.dlg.exec()

        if result:
            try:
                self._run_step3(self.dlg.aoi_geom)
            except Exception as e:
                self.iface.messageBar().pushCritical("CLC → Manning", str(e))

    def _on_dialog_finished(self, _code):
        # Rehabilitar siempre, se cierre como se cierre
        self._is_dialog_open = False
        if self._action_main is not None:
            self._action_main.setEnabled(True)
