package dev.lions.btpxpress.view;

import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Classe de base pour les vues de type liste/CRUD.
 *
 * Fonctionnalités:
 * - Chargement et affichage de listes
 * - Filtrage multi-critères
 * - Tri (ascendant/descendant)
 * - Pagination
 * - CRUD complet (Create, Read, Update, Delete)
 * - Sélection simple/multiple
 * - Messages utilisateur (succès, erreur, warning)
 * - Lazy loading pour grandes listes
 *
 * Principe DRY: Toute la logique commune des écrans de liste est centralisée ici.
 *
 * @param <T> Type d'entité
 * @param <ID> Type de l'identifiant
 */
@Getter
@Setter
public abstract class BaseListView<T, ID> implements Serializable {

    protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class);
    private static final long serialVersionUID = 1L;

    // ========== Données ==========
    protected List<T> items = new ArrayList<>();
    protected List<T> filteredItems = new ArrayList<>();
    protected T selectedItem;
    protected List<T> selectedItems = new ArrayList<>();
    protected T entity; // Pour les formulaires create/edit

    // ========== États ==========
    protected boolean loading = false;
    protected boolean editing = false; // Mode édition vs création
    protected String globalFilter; // Recherche globale

    // ========== Pagination ==========
    protected int first = 0; // Index de départ
    protected int pageSize = 10; // Taille de page
    protected int totalRecords = 0; // Nombre total d'enregistrements

    // ========== Tri ==========
    protected String sortField; // Champ de tri
    protected boolean sortAscending = true; // Ordre de tri

    // ========== Sélection ==========
    protected String selectionMode = "single"; // single, multiple, checkbox

    /**
     * Initialisation du bean au chargement de la page.
     */
    @PostConstruct
    public void init() {
        LOG.debug("Initialisation de {}", getClass().getSimpleName());
        try {
            initializeFields();
            loadItems();
        } catch (Exception e) {
            LOG.error("Erreur lors de l'initialisation", e);
            addErrorMessage("Erreur lors du chargement des données");
        }
    }

    /**
     * Initialiser les champs spécifiques de la vue.
     * Override si nécessaire.
     */
    protected void initializeFields() {
        // À surcharger dans les classes filles si besoin
    }

    /**
     * Charger les items depuis la source de données.
     * DOIT être implémenté par les classes filles.
     */
    public abstract void loadItems();

    /**
     * Recharger les données (alias pour loadItems).
     */
    public void refresh() {
        LOG.debug("Rafraîchissement des données");
        loadItems();
    }

    // ========== Filtrage ==========

    /**
     * Appliquer les filtres à la liste d'items.
     */
    protected void applyFilters(List<T> sourceItems, List<Predicate<T>> filters) {
        if (filters == null || filters.isEmpty()) {
            filteredItems = new ArrayList<>(sourceItems);
            return;
        }

        filteredItems = sourceItems.stream()
            .filter(filters.stream().reduce(Predicate::and).orElse(x -> true))
            .collect(Collectors.toList());
    }

    /**
     * Recherche avec les critères de filtrage actuels.
     */
    public void search() {
        LOG.debug("Recherche lancée pour {}", getClass().getSimpleName());
        first = 0; // Retour à la première page
        loadItems();
    }

    /**
     * Réinitialiser tous les filtres.
     */
    public void resetFilters() {
        LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName());
        globalFilter = null;
        sortField = null;
        sortAscending = true;
        first = 0;
        resetFilterFields();
        loadItems();
    }

    /**
     * Réinitialiser les champs de filtre spécifiques.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract void resetFilterFields();

    // ========== Tri ==========

    /**
     * Trier la liste par un champ donné.
     */
    public void sort(String field) {
        if (field.equals(sortField)) {
            sortAscending = !sortAscending;
        } else {
            sortField = field;
            sortAscending = true;
        }
        LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC");
        loadItems();
    }

    // ========== Navigation ==========

    /**
     * Naviguer vers la page de détails d'un item.
     */
    public String viewDetails(ID id) {
        LOG.debug("Redirection vers détails : {}", id);
        return getDetailsPath() + "?id=" + id + "&faces-redirect=true";
    }

    /**
     * Naviguer vers la page de détails de l'item sélectionné.
     */
    public String viewSelectedDetails() {
        if (selectedItem == null) {
            addWarningMessage("Aucun élément sélectionné");
            return null;
        }
        return viewDetails(getEntityId(selectedItem));
    }

    /**
     * Obtenir le chemin de la page de détails.
     */
    protected abstract String getDetailsPath();

    /**
     * Naviguer vers la page de création.
     */
    public String createNew() {
        LOG.debug("Redirection vers création");
        return getCreatePath() + "?faces-redirect=true";
    }

    /**
     * Obtenir le chemin de la page de création.
     */
    protected abstract String getCreatePath();

    // ========== CRUD ==========

    /**
     * Préparer un nouvel item pour création.
     */
    public void prepareNew() {
        LOG.debug("Préparation nouvelle entité");
        entity = createNewEntity();
        editing = false;
    }

    /**
     * Créer une nouvelle instance de l'entité.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract T createNewEntity();

    /**
     * Préparer un item pour édition.
     */
    public void prepareEdit(T item) {
        LOG.debug("Préparation édition : {}", item);
        entity = item;
        editing = true;
    }

    /**
     * Sauvegarder l'entité (création ou modification).
     */
    public void save() {
        try {
            loading = true;

            if (editing) {
                performUpdate();
                addSuccessMessage("Modification réussie");
            } else {
                performCreate();
                addSuccessMessage("Création réussie");
            }

            loadItems();
            entity = null;
            editing = false;

        } catch (Exception e) {
            LOG.error("Erreur lors de la sauvegarde", e);
            addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage());
        } finally {
            loading = false;
        }
    }

    /**
     * Créer une nouvelle entité.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract void performCreate();

    /**
     * Mettre à jour une entité existante.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract void performUpdate();

    /**
     * Supprimer l'item sélectionné.
     */
    public void delete() {
        if (selectedItem == null) {
            addWarningMessage("Aucun élément sélectionné");
            return;
        }

        try {
            loading = true;
            LOG.info("Suppression : {}", selectedItem);
            performDelete();
            items.remove(selectedItem);
            selectedItem = null;
            addSuccessMessage("Suppression réussie");
            loadItems();
        } catch (Exception e) {
            LOG.error("Erreur lors de la suppression", e);
            addErrorMessage("Erreur lors de la suppression : " + e.getMessage());
        } finally {
            loading = false;
        }
    }

    /**
     * Supprimer les items sélectionnés (sélection multiple).
     */
    public void deleteSelected() {
        if (selectedItems == null || selectedItems.isEmpty()) {
            addWarningMessage("Aucun élément sélectionné");
            return;
        }

        try {
            loading = true;
            int count = selectedItems.size();
            for (T item : selectedItems) {
                selectedItem = item;
                performDelete();
            }
            loadItems();
            selectedItems.clear();
            selectedItem = null;
            addSuccessMessage(count + " élément(s) supprimé(s)");
        } catch (Exception e) {
            LOG.error("Erreur lors de la suppression multiple", e);
            addErrorMessage("Erreur lors de la suppression");
        } finally {
            loading = false;
        }
    }

    /**
     * Effectuer la suppression réelle.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract void performDelete();

    /**
     * Obtenir l'ID d'une entité.
     * DOIT être implémenté par les classes filles.
     */
    protected abstract ID getEntityId(T entity);

    // ========== Messages utilisateur ==========

    protected void addSuccessMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
    }

    protected void addErrorMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
    }

    protected void addWarningMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message));
    }

    protected void addInfoMessage(String message) {
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
    }

    // ========== Utilitaires ==========

    /**
     * Vérifier si la liste est vide.
     */
    public boolean isEmpty() {
        return items == null || items.isEmpty();
    }

    /**
     * Obtenir le nombre d'items.
     */
    public int getItemCount() {
        return items == null ? 0 : items.size();
    }

    /**
     * Vérifier si un item est sélectionné.
     */
    public boolean hasSelection() {
        return selectedItem != null;
    }

    /**
     * Vérifier si plusieurs items sont sélectionnés.
     */
    public boolean hasMultipleSelection() {
        return selectedItems != null && !selectedItems.isEmpty();
    }
}

