# utils.py


from qgis.core import (
    Qgis,
    QgsMessageLog,
    QgsProject,
    QgsVectorLayer,
)

from qgis.core import QgsFeature

from qgis.PyQt.QtCore import QSettings, QDate, QCoreApplication

from qgis.PyQt import QtCore, QtGui, QtWidgets

from qgis.PyQt.QtWidgets import QTableWidgetItem, QTableWidget, QMessageBox

from qgis.utils import iface

from .constantes import CADASTRE_FORMATS


import csv
import os
import itertools
import re
import datetime
import time

#Compatibilité Python < 3.12 : définition manuelle de itertools.batched

try:
    from itertools import batched
except ImportError:
    def batched(iterable, n):
        """Alternative à itertools.batched pour Python < 3.12"""
        it = iter(iterable)
        while True:
            batch = list(itertools.islice(it, n))
            if not batch:
                break
            yield tuple(batch)


# ----------------- Gestion traduction -----------------
def tr(message):
    return QCoreApplication.translate("GestionForestiere", message)


#-------------Fonctions utilitaires


def get_csv_path(filename="table_data.csv"):
    """
    Retourne le chemin complet d'un fichier CSV dans le dossier /data du plugin.
    Crée le dossier s'il n'existe pas.

    :param filename: Nom du fichier CSV à créer ou charger.
    :return: Chemin absolu vers le fichier CSV.
    """
    plugin_dir = os.path.dirname(__file__)
    ressources_dir = os.path.join(plugin_dir, "data")
    os.makedirs(ressources_dir, exist_ok=True)  # Crée le dossier /data s’il n’existe pas
    return os.path.join(ressources_dir, filename)


def get_finances_csv_path():
    """
    Retourne le chemin CSV pour les données financières,
    construit dynamiquement selon le nom de la couche active et le projet.

    :return: Chemin vers un fichier CSV du type fin_nomcouche_prefix.csv ou None si couche manquante.
    """
    layer = iface.activeLayer()
    if not layer:
        return None  # Tu peux aussi lever une exception ou afficher un QMessageBox

    layer_name = re.sub(r'\W+', '_', layer.name())  # Nettoyage nom couche : remplace les caractères spéciaux
    project_path = QgsProject.instance().fileName()

    if not project_path:
        project_prefix = "def"
    else:
        project_name = os.path.basename(project_path)
        project_prefix = project_name[:3].lower()

    filename = f"fin_{layer_name}_{project_prefix}.csv"
    return get_csv_path(filename)

# Fonction gèrant la sélection de la coucha active ainsi que le contrôle d'existence des champs
import traceback

def get_valid_active_layer(dlg=None, silent=False):
    """
    Retourne la couche vectorielle active si elle est valide et contient les champs requis.
    Affiche un message d'erreur seulement si silent=False.
    """
    layer = iface.activeLayer()

    if not layer or not isinstance(layer, QgsVectorLayer):
        if not silent:
            QMessageBox.warning(
                dlg,
                tr("Erreur"),
                tr("Aucune couche vectorielle active.")
            )
        return None

    expected_fields = {"id", "section", "numero", "SURFACE", "typeParc", "totalplants"}
    layer_fields = set(field.name() for field in layer.fields())

    if not expected_fields.issubset(layer_fields):
        if not silent:
            QMessageBox.critical(
                dlg,
                tr("Erreur"),
                tr("La couche active ne contient pas tous les champs \n nécessaires à l'utilisation du plugin")
            )
        return None

    return layer



def get_valid_active_layer_start(dlg):
    """
    Retourne la couche vectorielle active si elle est valide et contient les champs requis.
    Affiche un message d'erreur sinon.
    """
    layer = iface.activeLayer()
    if not layer or not isinstance(layer, QgsVectorLayer):
        QMessageBox.warning(
            dlg,
            tr("Erreur"),
            tr("Aucune couche vectorielle active.")
        )
        return None

    expected_fields = {"id", "section","numero", "SURFACE", "typeParc", "totalplants"}
    layer_fields = set(field.name() for field in layer.fields())

    if not expected_fields.issubset(layer_fields):
        QMessageBox.critical(
            dlg,
            tr("Erreur"),
            tr("La couche sélectionnée n'est pas compatible avec le plugin. \n "
            "Sélectionnez une couche valide \n"
            "et relancez le plugin")
        )
        return None
    return layer


# Evite d'afficher les valeurs nulles
# def safe_set_text(line_edit, value):
#     if value is None or str(value).upper() in ("NULL", "NONE"):
#         line_edit.setText("")
#     else:
#         line_edit.setText(str(value))

def safe_set_text(line_edit, value):
    if line_edit is None:
        return

    if value is None or str(value).upper() in ("NULL", "NONE", ""):
        line_edit.setText("")
    else:
        # Formatage des dates
        if isinstance(value, QDate):
            text = value.toString("dd/MM/yyyy")
        elif isinstance(value, (datetime.date, datetime.datetime)):
            text = value.strftime("%d/%m/%Y")
        else:
            text = str(value)

        line_edit.setText(text)

def safe_set_date(date_edit, value):
    if date_edit is None:
        return
    if value in (None, "", "NULL"):
        # Mettre un texte vide à l'affichage
        date_edit.clear()
    else:
        if isinstance(value, QDate):
            date_edit.setDate(value)
        else:
            try:
                y, m, d = map(int, str(value).split("-"))
                date_edit.setDate(QDate(y, m, d))
            except Exception as e:
                print(f"❌ Impossible de parser la date: {value} ({e})")
                date_edit.clear()

# Extraction des champs section et numero fonction du pays
def extraire_section_numero(id_val: str, code_pays: str) -> tuple[str, str] | tuple[None, None]:
    """Extrait la section et le numéro selon le code pays (hors France)."""
    config = CADASTRE_FORMATS.get(code_pays)
    if not config:
        return None, None

    match = re.match(config["regex"], id_val)
    if match:
        section = match.group(config["section_index"])
        numero = match.group(config["numero_index"])
        return section, numero

    return None, None

# Sauvegarde de la table des données

def save_table_to_csv(table_widget: QTableWidget, csv_path: str):
    try:
        table_widget.setUpdatesEnabled(False)
        table_widget.blockSignals(True)

        start_time = time.perf_counter()
        data = []
        for row in range(table_widget.rowCount()):
            row_data = [
                table_widget.item(row, col).text() if table_widget.item(row, col) else ""
                for col in range(table_widget.columnCount())
            ]
            data.append(row_data)

        with open(csv_path, "w", newline='', encoding='utf-8') as file:
            writer = csv.writer(file)
            writer.writerows(data)

        elapsed_time = time.perf_counter() - start_time
        print(f"✅ Données sauvegardées dans : {csv_path} en {elapsed_time:.4f} s")

    except Exception as e:
        print(f"❌ Erreur lors de la sauvegarde : {e}")

    finally:
        table_widget.blockSignals(False)
        table_widget.setUpdatesEnabled(True)

# Chargement de la table des données

def load_table_from_csv(table_widget, file_path):
    """Charge efficacement un fichier CSV dans un QTableWidget."""
    if not os.path.exists(file_path):
        return

    # Désactive les signaux et mises à jour visuelles pendant le chargement
    table_widget.setUpdatesEnabled(False)
    table_widget.blockSignals(True)

    try:
        with open(file_path, mode='r', newline='', encoding='utf-8') as file:
            reader = csv.reader(file)
            for row_index, row_data in enumerate(reader):
                if row_index >= table_widget.rowCount():
                    table_widget.insertRow(table_widget.rowCount())
                for col_index, value in enumerate(row_data):
                    if col_index < table_widget.columnCount():
                        item = QTableWidgetItem(value)
                        table_widget.setItem(row_index, col_index, item)
    finally:
        # Réactive les signaux et mises à jour
        table_widget.blockSignals(False)
        table_widget.setUpdatesEnabled(True)

def extract_table_values(table_widget, col_indices):
    """
    Extrait pour chaque ligne de table_widget les valeurs des colonnes
    listées dans col_indices, et renvoie une liste de listes.
    """
    data = []
    for row in range(table_widget.rowCount()):
        row_vals = []
        for col in col_indices:
            item = table_widget.item(row, col)
            row_vals.append(item.text() if item else "")
        data.append(row_vals)
    return data

def unique_values_per_column(data):
    """
    À partir d'une liste de lignes (listes de colonnes),
    renvoie pour chaque colonne une liste triée de valeurs uniques.
    """
    if not data:
        return []
    num_cols = len(data[0])
    uniques = [set() for _ in range(num_cols)]
    for row_vals in data:
        for idx, val in enumerate(row_vals):
            uniques[idx].add(val)
    return [sorted(col_set) for col_set in uniques]

def format_date(value):
    """
    Formate une date (QDate, datetime.date ou str) au format 'dd/MM/yyyy'.
    Retourne '???' si la valeur est vide ou invalide.
    """
    if hasattr(value, 'toString'):
        return value.toString("dd/MM/yyyy")  # QDate
    elif hasattr(value, 'strftime'):
        return value.strftime("%d/%m/%Y")    # datetime.date
    elif isinstance(value, str):
        try:
            dt = datetime.datetime.strptime(value, "%Y-%m-%d")  # format standard CSV
            return dt.strftime("%d/%m/%Y")
        except ValueError:
            return value  # retourne la chaîne brute si parsing échoue
    return "???"

def safe_str(value):
    """Convertit une valeur pour affichage dans Qt : vide si None/NULL."""
    if value is None or str(value).upper() in ("NULL", "NONE"):
        return ""
    return str(value)

def get_fill_color_from_layer(layer, field_value):
    if not layer or not isinstance(layer, QgsVectorLayer):
        return None

    renderer = layer.renderer()
    if renderer and renderer.type() == "categorizedSymbol":
        for cat in renderer.categories():
            try:
                if float(cat.value()) == float(field_value):
                    symbol = cat.symbol()
                    for i in range(symbol.symbolLayerCount()):
                        sym_layer = symbol.symbolLayer(i)
                        if hasattr(sym_layer, "fillColor"):
                            return sym_layer.fillColor().name()
                        elif hasattr(sym_layer, "color"):
                            return sym_layer.color().name()
                    return symbol.color().name()
            except (TypeError, ValueError):
                continue
    return None


def extract_features_data(layer, only_possessed=True):
    """
    Extrait les entités sous forme de dictionnaires avec géométrie.
    Si `only_possessed` est True, ne conserve que celles dont 'Possession' est True.
    """
    start = time.time()
    features_data = []

    for f in layer.getFeatures():
        if only_possessed and not f["Possession"]:
            continue

        data = {field.name(): f[field.name()] for field in layer.fields()}
        data["_geom"] = f.geometry()  # on ajoute la géométrie ici
        data["fid"] = f.id()
        features_data.append(data)

    elapsed = time.time() - start
    print(f"[DEBUG] Extraction features_data terminée en {elapsed:.3f}s, {len(features_data)} entités extraites")
    return features_data


# Différentes méthodes de Messages d'erreurs et d'avertissement

def log_debug(message, plugin_name="gestion_forestiere"):
    """Log un message d'information (message traduit automatiquement)."""
    QgsMessageLog.logMessage(tr(message), plugin_name, Qgis.Info)

def log_warning(message, plugin_name="gestion_forestiere"):
    """Log un message d'avertissement (message traduit automatiquement)."""
    QgsMessageLog.logMessage(tr(message), plugin_name, Qgis.Warning)

def log_error(message, plugin_name="gestion_forestiere"):
    """Log un message d'erreur (message traduit automatiquement)."""
    QgsMessageLog.logMessage(tr(message), plugin_name, Qgis.Critical)

def show_success_bar(iface, title, message):
    """Affiche un message de succès dans la barre d'info (titres et messages traduits automatiquement)."""
    iface.messageBar().pushSuccess(tr(title), tr(message))

def show_warning_bar(iface, title, message):
    """Affiche un message d'avertissement dans la barre d'info (titres et messages traduits automatiquement)."""
    iface.messageBar().pushWarning(tr(title), tr(message))

def show_error_bar(iface, title, message):
    """Affiche un message d'erreur dans la barre d'info (titres et messages traduits automatiquement)."""
    iface.messageBar().pushCritical(tr(title), tr(message))

def couche_a_contenu(layer):
    """
    Vérifie si au moins un champ est rempli dans au moins une entité.
    Retourne True si du contenu est présent, sinon False.
    """
    for feat in layer.getFeatures():
        for val in feat.attributes():
            if val not in (None, ''):
                return True
    return False



# Message au premier lancement
def show_update_message_once():
    settings = QSettings()
    version_key = "gestion_forestière/last_notified_version"
    current_version = "1.5.2"
    last_notified = settings.value(version_key, "")
    if last_notified != current_version:
        QMessageBox.information(
            None,
            tr("⚠️ Nouvelle configuration requise"),
            tr("Lors du passage de la version 1.3.3 à la version > 1.4.0 du plugin gestion_forestière,\n "
               "vous devez repasser par la configuration pour créer les champs 'prix' et 'dateAchat'.\n "
               "Ou alors le créer manuellement dans votre table format 'Réel' et 'Date'.\n"
               "Ce message n'apparaitra qu'une seule fois.\n")
        )
        settings.setValue(version_key, current_version)


