package dev.lions.unionflow.client.view;

import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.client.service.MembreService;
import dev.lions.unionflow.client.service.OrganisationService;
import dev.lions.unionflow.client.service.NotificationService;
import dev.lions.unionflow.client.service.CotisationService;
import dev.lions.unionflow.client.service.ErrorHandlerService;
import dev.lions.unionflow.client.service.RetryService;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import jakarta.inject.Inject;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.logging.Logger;

import java.io.IOException;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Bean pour la page de liste des membres.
 *
 * <p>
 * Utilise {@link MembreLazyDataModel} pour la pagination/tri/filtrage côté
 * serveur.
 * Supprime la double conversion DTO et la classe Entite deprecated.
 *
 * @author UnionFlow Team
 * @version 2.0
 */
@Named("membreListeBean")
@ViewScoped
@Getter
@Setter
public class MembreListeBean implements Serializable {

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

    // === Services injectés ===

    @Inject
    @RestClient
    MembreService membreService;

    @Inject
    @RestClient
    OrganisationService organisationService;

    @Inject
    @RestClient
    NotificationService notificationService;

    @Inject
    @RestClient
    CotisationService cotisationService;

    @Inject
    ErrorHandlerService errorHandler;

    @Inject
    RetryService retryService;

    @Inject
    UserSession userSession;

    // === Statistiques ===

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private MembreService.StatistiquesMembreDTO statistiques;

    // === Critères de recherche (délégation au LazyDataModel) ===

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private MembreSearchCriteria searchCriteria = MembreSearchCriteria.builder().build();

    /**
     * Org ID imposé pour les non-SUPER_ADMIN. Jamais effacé par les filtres UI.
     * Garantit que setEntiteFilter("") ne supprime pas le scope organisationnel.
     */
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private UUID enforcedOrgId;

    // === Lazy Data Model pour la DataTable ===

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private MembreLazyDataModel lazyModel;

    // === Sélection multiple ===

    private List<MembreResponse> selectedMembres = new ArrayList<>();

    // === Organisations pour le filtre ===

    private List<OrganisationSummaryResponse> organisationsDisponibles = new ArrayList<>();

    // === Messages groupés ===

    private String sujetMessage;
    private String contenuMessage;
    private List<String> canauxMessage = new ArrayList<>();

    // === Contact membre ===

    private MembreResponse membreAContacter;
    /** Membre en attente de confirmation de suspension (flux explicite). */
    private MembreResponse membrePourSuspension;
    private String messageContact;
    private String sujetContact;
    private boolean dialogContactVisible = false;

    // === Import/Export ===

    private boolean mettreAJourExistants = false;
    private String formatExport = "EXCEL";
    private List<String> colonnesExport = new ArrayList<>();
    private boolean exporterSelectionUniquement = false;

    // ========================================================================
    // INITIALISATION
    // ========================================================================

    @PostConstruct
    public void init() {
        try {
            // Pré-filtrer par organisation pour les non-SUPER_ADMIN
            initialiserFiltreOrganisation();
            // Créer le LazyDataModel avec les critères (potentiellement avec org filter)
            this.lazyModel = new MembreLazyDataModel(membreService, searchCriteria);
            chargerStatistiques();
            chargerOrganisations();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'initialisation");
            errorHandler.handleException(e, "lors de l'initialisation du bean", null);
            this.statistiques = null;
        }
    }

    /**
     * Pré-initialise le filtre d'organisation si l'utilisateur n'est pas SUPER_ADMIN.
     * Fallback : appelle /api/membres/me si l'entité n'est pas encore en session.
     */
    private void initialiserFiltreOrganisation() {
        if (userSession.isSuperAdmin()) {
            return; // SUPER_ADMIN voit tous les membres, pas de filtre imposé
        }

        UUID orgId = null;

        // Cas 0 : organisation active déjà sélectionnée (source la plus fiable)
        try {
            orgId = userSession.getActiveOrganisationId();
            if (orgId != null) {
                LOG.infof("Filtre org depuis activeOrganisationId: %s", orgId);
            }
        } catch (Exception e) {
            LOG.warnf("userSession.getActiveOrganisationId() a échoué: %s", e.getMessage());
        }

        // Cas 1 : entité chargée en session (lazy-load depuis /api/membres/me)
        if (orgId == null) {
            try {
                if (userSession.getEntite() != null && userSession.getEntite().getId() != null) {
                    orgId = userSession.getEntite().getId();
                    LOG.infof("Filtre org depuis userSession.getEntite(): %s", orgId);
                }
            } catch (Exception e) {
                LOG.warnf("userSession.getEntite() a échoué: %s", e.getMessage());
            }
        }

        // Cas 2 : appel direct à /api/membres/me
        if (orgId == null) {
            try {
                MembreResponse moi = retryService.executeWithRetrySupplier(
                        () -> membreService.obtenirMembreConnecte(),
                        "chargement profil membre connecté");
                if (moi != null && moi.getOrganisationId() != null) {
                    orgId = moi.getOrganisationId();
                    LOG.infof("Filtre org depuis /api/membres/me: %s", orgId);
                }
            } catch (Exception e) {
                LOG.warnf("Impossible de récupérer l'org depuis /api/membres/me: %s", e.getMessage());
            }
        }

        if (orgId != null) {
            searchCriteria.setOrganisationIds(List.of(orgId));
            this.enforcedOrgId = orgId;
        } else {
            LOG.warn("Impossible de déterminer l'organisation pour cet utilisateur — aucune donnée ne sera affichée");
        }
    }

    // ========================================================================
    // LAZY DATA MODEL
    // ========================================================================

    /**
     * Retourne le LazyDataModel pour la DataTable PrimeFaces.
     * Utilisé par: {@code value="#{membreListeBean.lazyModel}"}
     */
    public MembreLazyDataModel getLazyModel() {
        return lazyModel;
    }

    // ========================================================================
    // STATISTIQUES
    // ========================================================================

    private void chargerStatistiques() {
        try {
            // Récupérer l'orgId depuis les critères de recherche (déjà initialisé dans init())
            UUID orgId = searchCriteria.getOrganisationIds() != null
                    && !searchCriteria.getOrganisationIds().isEmpty()
                    ? searchCriteria.getOrganisationIds().get(0)
                    : null;

            if (!userSession.isSuperAdmin() && orgId != null) {
                // Pour ADMIN_ORGANISATION/MODERATEUR : stats filtrées via rechercherAvance
                this.statistiques = chargerStatistiquesParOrg(orgId);
                LOG.infof("Statistiques org [%s] calculées: %d membres",
                        orgId, statistiques != null ? statistiques.getTotalMembres() : 0);
            } else {
                // SUPER_ADMIN ou aucun org filter : stats globales depuis l'API
                this.statistiques = retryService.executeWithRetrySupplier(
                        () -> membreService.obtenirStatistiques(),
                        "chargement des statistiques membres");
                LOG.infof("Statistiques globales chargées: %d membres",
                        statistiques != null ? statistiques.getTotalMembres() : 0);
            }
        } catch (Exception e) {
            LOG.errorf(e, "Impossible de charger les statistiques");
            errorHandler.handleException(e, "lors du chargement des statistiques", null);
            this.statistiques = null;
        }
    }

    /**
     * Calcule les statistiques pour une organisation via rechercherAvance (3 appels légers).
     */
    private MembreService.StatistiquesMembreDTO chargerStatistiquesParOrg(UUID orgId) {
        MembreService.StatistiquesMembreDTO dto = new MembreService.StatistiquesMembreDTO();

        // Total membres de l'org
        dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO total =
                membreService.rechercherAvance(
                        MembreSearchCriteria.builder()
                                .organisationIds(List.of(orgId)).build(),
                        0, 1, "nom", "asc");
        long totalCount = total != null ? total.getTotalElements() : 0L;

        // Membres actifs de l'org
        dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO actifs =
                membreService.rechercherAvance(
                        MembreSearchCriteria.builder()
                                .organisationIds(List.of(orgId))
                                .statut("ACTIF").build(),
                        0, 1, "nom", "asc");
        long actifsCount = actifs != null ? actifs.getTotalElements() : 0L;

        // Membres ayant adhéré dans les 30 derniers jours
        dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO nouveaux =
                membreService.rechercherAvance(
                        MembreSearchCriteria.builder()
                                .organisationIds(List.of(orgId))
                                .dateAdhesionMin(LocalDate.now().minusDays(30)).build(),
                        0, 1, "nom", "asc");
        long nouveauxCount = nouveaux != null ? nouveaux.getTotalElements() : 0L;

        dto.setTotalMembres(totalCount);
        dto.setMembresActifs(actifsCount);
        dto.setMembresInactifs(totalCount - actifsCount);
        dto.setNouveauxMembres30Jours(nouveauxCount);
        dto.setTauxActivite(totalCount > 0 ? (actifsCount * 100.0 / totalCount) : 0.0);
        return dto;
    }

    public int getTotalMembres() {
        return statistiques != null && statistiques.getTotalMembres() != null
                ? statistiques.getTotalMembres().intValue()
                : 0;
    }

    public int getMembresActifs() {
        return statistiques != null && statistiques.getMembresActifs() != null
                ? statistiques.getMembresActifs().intValue()
                : 0;
    }

    public int getMembresInactifs() {
        return statistiques != null && statistiques.getMembresInactifs() != null
                ? statistiques.getMembresInactifs().intValue()
                : 0;
    }

    public int getNouveauxMembres() {
        return statistiques != null && statistiques.getNouveauxMembres30Jours() != null
                ? statistiques.getNouveauxMembres30Jours().intValue()
                : 0;
    }

    public int getTauxActivite() {
        int total = getTotalMembres();
        if (total == 0) return 0;
        return (int) Math.round(getMembresActifs() * 100.0 / total);
    }

    // ========================================================================
    // ORGANISATIONS
    // ========================================================================

    private void chargerOrganisations() {
        organisationsDisponibles = new ArrayList<>();
        try {
            PagedResponse<OrganisationSummaryResponse> response = retryService.executeWithRetrySupplier(
                    () -> organisationService.listerToutes(0, 1000),
                    "chargement des organisations");
            if (response != null && response.getData() != null) {
                organisationsDisponibles.addAll(response.getData());
            }
            LOG.infof("Organisations chargées: %d", organisationsDisponibles.size());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du chargement des organisations");
            errorHandler.handleException(e, "lors du chargement des organisations", null);
        }
    }

    // ========================================================================
    // FILTRES — Délégation vers MembreSearchCriteria
    // ========================================================================

    public String getSearchFilter() {
        return searchCriteria.getQuery() != null ? searchCriteria.getQuery() : "";
    }

    public void setSearchFilter(String value) {
        searchCriteria.setQuery(value != null && !value.isEmpty() ? value : null);
    }

    public String getStatutFilter() {
        return searchCriteria.getStatut() != null ? searchCriteria.getStatut() : "";
    }

    public void setStatutFilter(String value) {
        searchCriteria.setStatut(value != null && !value.isEmpty() ? value : null);
    }

    /**
     * Filtre par rôle du membre (Responsable, Bureau, Membre).
     * Aligné avec le modèle métier réel (booléens responsable/membreBureau).
     */
    public String getRoleFilter() {
        if (Boolean.TRUE.equals(searchCriteria.getResponsable()))
            return "RESPONSABLE";
        if (Boolean.TRUE.equals(searchCriteria.getMembreBureau()))
            return "BUREAU";
        return "";
    }

    public void setRoleFilter(String value) {
        searchCriteria.setResponsable(null);
        searchCriteria.setMembreBureau(null);
        if ("RESPONSABLE".equals(value)) {
            searchCriteria.setResponsable(true);
        } else if ("BUREAU".equals(value)) {
            searchCriteria.setMembreBureau(true);
        }
    }

    public String getEntiteFilter() {
        if (searchCriteria.getOrganisationIds() != null && !searchCriteria.getOrganisationIds().isEmpty()) {
            return searchCriteria.getOrganisationIds().get(0).toString();
        }
        return "";
    }

    public void setEntiteFilter(String value) {
        if (value != null && !value.isEmpty()) {
            try {
                UUID orgId = UUID.fromString(value);
                searchCriteria.setOrganisationIds(List.of(orgId));
            } catch (IllegalArgumentException e) {
                LOG.warnf("ID d'organisation invalide: %s", value);
            }
        } else {
            // Si non-SUPER_ADMIN, maintenir le filtre d'organisation imposé
            if (enforcedOrgId != null) {
                searchCriteria.setOrganisationIds(List.of(enforcedOrgId));
            } else {
                searchCriteria.setOrganisationIds(null);
            }
        }
    }

    public Integer getAgeMin() {
        return searchCriteria.getAgeMin();
    }

    public void setAgeMin(Integer v) {
        searchCriteria.setAgeMin(v);
    }

    public Integer getAgeMax() {
        return searchCriteria.getAgeMax();
    }

    public void setAgeMax(Integer v) {
        searchCriteria.setAgeMax(v);
    }

    public String getGenreFilter() {
        return "";
    } // Non supporté par MembreSearchCriteria

    public void setGenreFilter(String v) {
        /* À implémenter si ajouté au serveur */ }

    public String getVilleFilter() {
        return searchCriteria.getVille() != null ? searchCriteria.getVille() : "";
    }

    public void setVilleFilter(String v) {
        searchCriteria.setVille(v != null && !v.isEmpty() ? v : null);
    }

    public LocalDate getDateAdhesionDebut() {
        return searchCriteria.getDateAdhesionMin();
    }

    public void setDateAdhesionDebut(LocalDate v) {
        searchCriteria.setDateAdhesionMin(v);
    }

    public LocalDate getDateAdhesionFin() {
        return searchCriteria.getDateAdhesionMax();
    }

    public void setDateAdhesionFin(LocalDate v) {
        searchCriteria.setDateAdhesionMax(v);
    }

    public String getProfessionFilter() {
        return searchCriteria.getProfession() != null ? searchCriteria.getProfession() : "";
    }

    public void setProfessionFilter(String v) {
        searchCriteria.setProfession(v != null && !v.isEmpty() ? v : null);
    }

    public MembreSearchCriteria getSearchCriteria() {
        return searchCriteria;
    }

    public void setSearchCriteria(MembreSearchCriteria sc) {
        this.searchCriteria = sc;
    }

    // ========================================================================
    // ACTIONS DE RECHERCHE
    // ========================================================================

    /**
     * Met à jour les critères du LazyModel pour forcer le rechargement.
     */
    public void rechercher() {
        searchCriteria.sanitize();
        lazyModel.setSearchCriteria(searchCriteria);
        LOG.infof("Recherche appliquée: %s", searchCriteria.getDescription());
    }

    public void reinitialiserFiltres() {
        searchCriteria = MembreSearchCriteria.builder().build();
        // Préserver le filtre d'organisation pour les non-SUPER_ADMIN
        initialiserFiltreOrganisation();
        lazyModel.setSearchCriteria(searchCriteria);
    }

    public void actualiser() {
        rechercher();
        chargerStatistiques();
        chargerOrganisations();
    }

    public void appliquerFiltresAvances() {
        searchCriteria.sanitize();
        lazyModel.setSearchCriteria(searchCriteria);
        LOG.infof("Filtres avancés appliqués: %s", searchCriteria.getDescription());
    }

    // ========================================================================
    // AUTOCOMPLETION
    // ========================================================================

    public List<String> completerVilles(String query) {
        try {
            return retryService.executeWithRetrySupplier(
                    () -> membreService.obtenirVilles(query),
                    "récupération des villes");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la récupération des villes");
            return new ArrayList<>();
        }
    }

    public List<String> completerProfessions(String query) {
        try {
            return retryService.executeWithRetrySupplier(
                    () -> membreService.obtenirProfessions(query),
                    "récupération des professions");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la récupération des professions");
            return new ArrayList<>();
        }
    }

    // ========================================================================
    // ACTIONS SUR UN MEMBRE
    // ========================================================================

    /**
     * Prépare la suspension (ouvre le dialogue de confirmation).
     */
    public void preparerSuspendre(MembreResponse membre) {
        this.membrePourSuspension = membre;
    }

    /**
     * Confirme et exécute la suspension après clic sur « Oui » dans le dialogue.
     */
    public void confirmerSuspendre() {
        if (membrePourSuspension == null) {
            return;
        }
        MembreResponse m = membrePourSuspension;
        membrePourSuspension = null;
        suspendreMembre(m);
    }

    public void suspendreMembre(MembreResponse membre) {
        try {
            retryService.executeWithRetrySupplier(
                    () -> {
                        membreService.suspendre(membre.getId());
                        return null;
                    },
                    "suspension d'un membre");
            membre.setStatutCompte("SUSPENDU"); // Corrigé: setStatutCompte
            LOG.infof("Membre suspendu: %s", membre.getNomComplet());
            errorHandler.showSuccess("Succès", "Le membre a été suspendu avec succès");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la suspension");
            errorHandler.handleException(e, "lors de la suspension d'un membre", null);
        }
    }

    public void reactiverMembre(MembreResponse membre) {
        try {
            retryService.executeWithRetrySupplier(
                    () -> {
                        membreService.activer(membre.getId());
                        return null;
                    },
                    "réactivation d'un membre");
            membre.setStatutCompte("ACTIF"); // Corrigé: setStatutCompte
            LOG.infof("Membre réactivé: %s", membre.getNomComplet());
            errorHandler.showSuccess("Succès", "Le membre a été réactivé avec succès");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la réactivation");
            errorHandler.handleException(e, "lors de la réactivation d'un membre", null);
        }
    }

    // ========================================================================
    // CONTACT MEMBRE
    // ========================================================================

    public void contacterMembre(MembreResponse membre) {
        this.membreAContacter = membre;
        this.sujetContact = "";
        this.messageContact = "";
        this.dialogContactVisible = true;
        LOG.infof("Ouverture du dialogue de contact pour: %s", membre.getNomComplet());
    }

    public void envoyerMessageContact() {
        if (membreAContacter == null || messageContact == null || messageContact.trim().isEmpty()) {
            errorHandler.showWarning("Attention", "Veuillez saisir un message");
            return;
        }

        try {
            String sujet = sujetContact != null && !sujetContact.trim().isEmpty()
                    ? sujetContact
                    : "Message depuis UnionFlow";

            List<UUID> membreIds = List.of(membreAContacter.getId());
            List<String> canaux = List.of("IN_APP", "EMAIL");

            NotificationService.NotificationGroupeeRequest request = new NotificationService.NotificationGroupeeRequest(
                    membreIds, sujet, messageContact, canaux);

            retryService.executeWithRetrySupplier(
                    () -> {
                        notificationService.envoyerNotificationsGroupees(request);
                        return null;
                    },
                    "envoi d'un message de contact");

            errorHandler.showSuccess("Succès", "Message envoyé à " + membreAContacter.getNomComplet());
            this.dialogContactVisible = false;
            this.membreAContacter = null;
            this.sujetContact = "";
            this.messageContact = "";
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi du message");
            errorHandler.handleException(e, "lors de l'envoi d'un message de contact", null);
        }
    }

    public void annulerContact() {
        this.dialogContactVisible = false;
        this.membreAContacter = null;
        this.sujetContact = "";
        this.messageContact = "";
    }

    // ========================================================================
    // ACTIONS GROUPÉES
    // ========================================================================

    public void rappelCotisationsGroupe() {
        if (selectedMembres == null || selectedMembres.isEmpty()) {
            errorHandler.showWarning("Attention", "Veuillez sélectionner au moins un membre");
            return;
        }

        try {
            LOG.infof("Envoi de rappels de cotisations à %d membres", selectedMembres.size());
            List<UUID> membreIds = selectedMembres.stream()
                    .map(MembreResponse::getId)
                    .collect(Collectors.toList());

            Map<String, Integer> result = retryService.executeWithRetrySupplier(
                    () -> cotisationService.envoyerRappelsGroupes(membreIds),
                    "envoi de rappels de cotisations");
            int rappelsEnvoyes = result != null && result.containsKey("rappelsEnvoyes")
                    ? result.get("rappelsEnvoyes")
                    : membreIds.size();

            errorHandler.showSuccess("Succès", "Rappels de cotisations envoyés à " + rappelsEnvoyes + " membres");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi des rappels");
            errorHandler.handleException(e, "lors de l'envoi de rappels de cotisations", null);
        }
    }

    public void exporterSelection() {
        if (selectedMembres == null || selectedMembres.isEmpty()) {
            errorHandler.showWarning("Attention", "Veuillez sélectionner au moins un membre");
            return;
        }

        try {
            LOG.infof("Export de la sélection: %d membres", selectedMembres.size());
            List<UUID> membreIds = selectedMembres.stream()
                    .map(MembreResponse::getId)
                    .collect(Collectors.toList());

            byte[] data = retryService.executeWithRetrySupplier(
                    () -> membreService.exporterSelection(membreIds, formatExport),
                    "export de la sélection de membres");

            telechargerFichier(data, "membres_selection_" + LocalDate.now(), formatExport);
            LOG.infof("Export sélection généré: %d bytes", data.length);
        } catch (IOException e) {
            LOG.errorf(e, "Erreur lors du téléchargement de l'export");
            errorHandler.handleException(e, "lors du téléchargement de l'export", null);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'export");
            errorHandler.handleException(e, "lors de l'export de la sélection", null);
        }
    }

    public void envoyerMessageGroupe() {
        if (selectedMembres == null || selectedMembres.isEmpty()) {
            errorHandler.showWarning("Attention", "Veuillez sélectionner au moins un membre");
            return;
        }
        if (sujetMessage == null || sujetMessage.trim().isEmpty()) {
            errorHandler.showWarning("Attention", "Le sujet du message est obligatoire");
            return;
        }
        if (contenuMessage == null || contenuMessage.trim().isEmpty()) {
            errorHandler.showWarning("Attention", "Le contenu du message est obligatoire");
            return;
        }

        try {
            LOG.infof("Envoi de message groupé à %d membres", selectedMembres.size());
            List<UUID> membreIds = selectedMembres.stream()
                    .map(MembreResponse::getId)
                    .collect(Collectors.toList());

            NotificationService.NotificationGroupeeRequest request = new NotificationService.NotificationGroupeeRequest(
                    membreIds,
                    sujetMessage,
                    contenuMessage,
                    canauxMessage != null ? canauxMessage : new ArrayList<>());

            Map<String, Integer> result = retryService.executeWithRetrySupplier(
                    () -> notificationService.envoyerNotificationsGroupees(request),
                    "envoi de message groupé");
            int n = result != null && result.containsKey("notificationsCreees")
                    ? result.get("notificationsCreees")
                    : membreIds.size();

            errorHandler.showSuccess("Succès", "Message envoyé à " + n + " membres");
            sujetMessage = null;
            contenuMessage = null;
            canauxMessage = new ArrayList<>();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi du message groupé");
            errorHandler.handleException(e, "lors de l'envoi d'un message groupé", null);
        }
    }

    /**
     * Callback AJAX pour la mise à jour de l'affichage "N sélectionnés".
     */
    public void onRowSelect() {
        // Rien à faire — la mise à jour est gérée par l'attribut update du p:ajax
    }

    public void onRowUnselect() {
        // Rien à faire — la mise à jour est gérée par l'attribut update du p:ajax
    }

    // ========================================================================
    // IMPORT
    // ========================================================================

    /**
     * Importe des membres depuis un fichier Excel.
     * Utilise le service REST d'import.
     */
    public void importerMembres() {
        try {
            // L'import fichier nécessite le FileUploadEvent PrimeFaces.
            // La logique de construction du MembreImportMultipartForm est gérée
            // via le composant p:fileUpload dans la page XHTML.
            LOG.info("Import des membres déclenché");
            errorHandler.showInfo("Information",
                    "L'import par fichier sera disponible dans une prochaine version. " +
                            "Utilisez le formulaire d'inscription pour ajouter des membres individuellement.");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'import");
            errorHandler.handleException(e, "lors de l'import des membres", null);
        }
    }

    /**
     * Télécharge le modèle d'import Excel.
     */
    public void telechargerModele() {
        try {
            byte[] modele = retryService.executeWithRetrySupplier(
                    () -> membreService.telechargerModeleImport(),
                    "téléchargement du modèle d'import");
            telechargerFichier(modele, "modele_import_membres", "EXCEL");
            LOG.info("Modèle d'import téléchargé avec succès");
        } catch (IOException e) {
            LOG.errorf(e, "Erreur lors du téléchargement du modèle");
            errorHandler.handleException(e, "lors du téléchargement du modèle d'import", null);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du téléchargement du modèle");
            errorHandler.handleException(e, "lors du téléchargement du modèle d'import", null);
        }
    }

    // ========================================================================
    // EXPORT
    // ========================================================================

    public void exporterMembres() {
        try {
            byte[] data = membreService.exporterExcel(
                    formatExport, null,
                    searchCriteria.getStatut() != null && !searchCriteria.getStatut().isEmpty()
                            ? searchCriteria.getStatut()
                            : null,
                    null, null, null, null,
                    true, true, false, null);

            telechargerFichier(data, "membres_export_" + LocalDate.now(), formatExport);
            LOG.infof("Export généré: %d bytes", data.length);
        } catch (IOException e) {
            LOG.errorf(e, "Erreur lors du téléchargement de l'export");
            errorHandler.handleException(e, "lors du téléchargement de l'export", null);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'export");
            errorHandler.handleException(e, "lors de l'export des membres", null);
        }
    }

    // ========================================================================
    // NAVIGATION
    // ========================================================================

    public String gererCotisations(MembreResponse membre) {
        return "cotisationCollectPage?membreId=" + membre.getId() + "&faces-redirect=true";
    }

    // ========================================================================
    // UTILITAIRES
    // ========================================================================

    /**
     * Écrit un fichier binaire dans la réponse HTTP pour téléchargement.
     */
    private void telechargerFichier(byte[] data, String nomFichier, String format) throws IOException {
        String extension = switch (format != null ? format.toUpperCase() : "XLSX") {
            case "CSV" -> "csv";
            case "PDF" -> "pdf";
            default -> "xlsx";
        };
        String contentType = switch (extension) {
            case "csv" -> "text/csv";
            case "pdf" -> "application/pdf";
            default -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        };

        jakarta.faces.context.FacesContext ctx = jakarta.faces.context.FacesContext.getCurrentInstance();
        HttpServletResponse response = (HttpServletResponse) ctx.getExternalContext().getResponse();

        response.reset();
        response.setContentType(contentType);
        response.setHeader("Content-Disposition", "attachment; filename=\"" + nomFichier + "." + extension + "\"");
        response.setContentLength(data.length);
        response.getOutputStream().write(data);
        response.getOutputStream().flush();
        ctx.responseComplete();
    }
}
