package dev.lions.unionflow.client.view;

import dev.lions.unionflow.server.api.dto.cotisation.request.CreateCotisationRequest;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.client.service.CotisationService;
import dev.lions.unionflow.client.service.OrganisationService;
import dev.lions.unionflow.client.service.MembreService;
import dev.lions.unionflow.client.service.WaveService;
import dev.lions.unionflow.client.service.NotificationClientService;
import dev.lions.unionflow.client.service.ExportClientService;
import org.primefaces.PrimeFaces;
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.annotation.PostConstruct;
import jakarta.faces.context.ExternalContext;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.logging.Logger;
import dev.lions.unionflow.client.service.ErrorHandlerService;
import dev.lions.unionflow.client.service.RetryService;
import java.io.OutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.HashMap;
import java.util.stream.Collectors;

/**
 * Bean JSF pour la gestion administrative des cotisations
 * Refactorisé pour utiliser directement CotisationResponse et se connecter au
 * backend
 * 
 * @author UnionFlow Team
 * @version 2.0
 */
@Named("cotisationsGestionBean")
@SessionScoped
public class CotisationsGestionBean implements Serializable {

    // Constantes de navigation outcomes (WOU/DRY - réutilisables)
    private static final String OUTCOME_DASHBOARD = "dashboardPage";

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

    @Inject
    @RestClient
    private CotisationService cotisationService;

    @Inject
    @RestClient
    private OrganisationService organisationService;

    @Inject
    @RestClient
    private MembreService membreService;

    @Inject
    @RestClient
    private WaveService waveService;

    @Inject
    @RestClient
    private NotificationClientService notificationService;

    @Inject
    @RestClient
    private ExportClientService exportService;

    @Inject
    private UserSession userSession;

    @Inject
    ErrorHandlerService errorHandler;

    @Inject
    RetryService retryService;

    // Propriétés principales
    private String periodeActuelle;
    private BigDecimal tauxRecouvrement;
    private int totalMembresActifs;

    // KPIs financiers
    private String montantCollecte;
    private String objectifMensuel;
    private int progressionMensuelle;
    private String membresAJour;
    private int pourcentageMembresAJour;
    private String montantEnAttente;
    private int nombreCotisationsEnAttente;
    private String montantImpayes;
    private int joursRetardMoyen;
    private String revenus2024;
    private BigDecimal croissanceAnnuelle;
    private String prelevementsActifs;
    private String montantPrelevementsPrevu;

    // Analytics
    private String periodeGraphique = "12M";
    private List<OrganisationPerformante> topOrganisations;
    private int paiementsWave;
    private int paiementsVirement;
    private int paiementsEspeces;

    // Filtres
    private FiltresCotisations filtres;
    private List<Organisation> listeOrganisations;

    // Données et sélections - Utilisation directe de CotisationResponse
    private List<CotisationResponse> cotisationsFiltrees;
    private List<CotisationResponse> cotisationsSelectionnees;
    private String montantTotalSelectionne;
    private CotisationResponse cotisationDetail;
    // Dialog paiement
    private CotisationResponse cotisationAPayer;
    private BigDecimal montantPaiement;
    private String modePaiement;
    private LocalDate datePaiement;
    private String referencePaiement;

    // Wave Money
    private int membresPrelevementActif;
    private String montantPrelevementMensuel;
    private String prochainPrelevement;

    // Nouvelle campagne
    private NouvelleCampagne nouvelleCampagne;

    // Propriétés pour les rappels (WOU/DRY)
    private List<MembreEnRetard> membresEnRetard = new ArrayList<>();
    private List<MembreEnRetard> membresSelectionnes = new ArrayList<>();
    private int nombreMembresEnRetard = 0;
    private int nombreRappelsEnvoyes = 0;

    @PostConstruct
    public void init() {
        chargerKPIs();
        initializeFiltres();
        chargerCotisations();
        chargerTopOrganisations();
        chargerRepartitionMethodes();
        initializeNouvelleCampagne();
        chargerMembresEnRetard();
    }

    /**
     * Charge les KPIs depuis le backend
     */
    private void chargerKPIs() {
        try {
            Map<String, Object> statsBackend = cotisationService.obtenirStatistiques();
            List<CotisationResponse> cotisationsDTO = cotisationService.listerToutes(0, 1000);

            Long totalCotisations = ((Number) statsBackend.getOrDefault("totalCotisations", 0L)).longValue();
            Long cotisationsPayees = ((Number) statsBackend.getOrDefault("cotisationsPayees", 0L)).longValue();
            Long cotisationsEnRetard = ((Number) statsBackend.getOrDefault("cotisationsEnRetard", 0L)).longValue();
            Double tauxPaiement = ((Number) statsBackend.getOrDefault("tauxPaiement", 0.0)).doubleValue();

            BigDecimal totalCollecte = cotisationsDTO.stream()
                    .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            long enAttente = cotisationsDTO.stream().filter(c -> "EN_ATTENTE".equals(c.getStatut())).count();
            BigDecimal montantAttente = cotisationsDTO.stream()
                    .filter(c -> "EN_ATTENTE".equals(c.getStatut()))
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal montantImpayes = cotisationsDTO.stream()
                    .filter(c -> "EN_RETARD".equals(c.getStatut()))
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            // Calcul du retard moyen
            long totalJoursRetard = cotisationsDTO.stream()
                    .filter(c -> "EN_RETARD".equals(c.getStatut()))
                    .mapToLong(CotisationResponse::getJoursRetard)
                    .sum();
            joursRetardMoyen = cotisationsEnRetard > 0 ? (int) (totalJoursRetard / cotisationsEnRetard) : 0;

            this.periodeActuelle = LocalDate.now().format(DateTimeFormatter.ofPattern("MMMM yyyy"));
            this.tauxRecouvrement = BigDecimal.valueOf(tauxPaiement);
            this.totalMembresActifs = totalCotisations.intValue();
            this.montantCollecte = formatMontant(totalCollecte);
            // Objectif mensuel = moyenne des montants dus sur les 3 derniers mois
            int moisCourant = LocalDate.now().getMonthValue();
            int anneeCourante = LocalDate.now().getYear();
            BigDecimal moyenneMensuelle = cotisationsDTO.stream()
                    .filter(c -> c.getAnnee() != null && c.getAnnee() == anneeCourante)
                    .filter(c -> c.getMois() != null && c.getMois() >= Math.max(1, moisCourant - 2) && c.getMois() <= moisCourant)
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            this.objectifMensuel = formatMontant(moyenneMensuelle);
            this.progressionMensuelle = tauxPaiement.intValue();
            this.membresAJour = String.valueOf(cotisationsPayees);
            this.pourcentageMembresAJour = tauxPaiement.intValue();
            this.montantEnAttente = formatMontant(montantAttente);
            this.nombreCotisationsEnAttente = (int) enAttente;
            this.montantImpayes = formatMontant(montantImpayes);
            // Revenus année courante = somme des montants réellement payés (statut PAYEE ou PARTIELLEMENT_PAYEE)
            BigDecimal revenusAnneeCourante = cotisationsDTO.stream()
                    .filter(c -> c.getAnnee() != null && c.getAnnee() == anneeCourante)
                    .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            this.revenus2024 = formatMontant(revenusAnneeCourante);
            this.croissanceAnnuelle = calculerCroissanceAnnuelle(cotisationsDTO);

            // Charger les informations Wave
            chargerInfosWave(cotisationsDTO);
            this.prochainPrelevement = LocalDate.now().plusMonths(1).format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du calcul des KPIs");
            errorHandler.handleException(e, "lors du calcul des KPIs", null);
            initialiserKPIsParDefaut();
        }
        this.cotisationsSelectionnees = new ArrayList<>();
        this.montantTotalSelectionne = "0 FCFA";
    }

    private void initialiserKPIsParDefaut() {
        this.periodeActuelle = LocalDate.now().format(DateTimeFormatter.ofPattern("MMMM yyyy"));
        this.tauxRecouvrement = BigDecimal.ZERO;
        this.totalMembresActifs = 0;
        this.montantCollecte = "0 FCFA";
        this.objectifMensuel = "0 FCFA";
        this.progressionMensuelle = 0;
        this.membresAJour = "0";
        this.pourcentageMembresAJour = 0;
        this.montantEnAttente = "0 FCFA";
        this.nombreCotisationsEnAttente = 0;
        this.montantImpayes = "0 FCFA";
        this.joursRetardMoyen = 0;
        this.revenus2024 = "0 FCFA";
        this.croissanceAnnuelle = BigDecimal.ZERO;
        this.prelevementsActifs = "0";
        this.montantPrelevementsPrevu = "0";
        this.membresPrelevementActif = 0;
        this.montantPrelevementMensuel = "0 FCFA";
        this.prochainPrelevement = LocalDate.now().plusMonths(1).format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
    }

    /**
     * Calcule la croissance annuelle depuis les données historiques
     */
    private BigDecimal calculerCroissanceAnnuelle(List<CotisationResponse> cotisationsDTO) {
        try {
            int anneeActuelle = LocalDate.now().getYear();
            int anneePrecedente = anneeActuelle - 1;

            BigDecimal montantAnneeActuelle = cotisationsDTO.stream()
                    .filter(c -> c.getDateCreation() != null && c.getDateCreation().getYear() == anneeActuelle)
                    .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal montantAnneePrecedente = cotisationsDTO.stream()
                    .filter(c -> c.getDateCreation() != null && c.getDateCreation().getYear() == anneePrecedente)
                    .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            if (montantAnneePrecedente.compareTo(BigDecimal.ZERO) > 0) {
                return montantAnneeActuelle.subtract(montantAnneePrecedente)
                        .multiply(BigDecimal.valueOf(100))
                        .divide(montantAnneePrecedente, 1, java.math.RoundingMode.HALF_UP);
            }
            return montantAnneeActuelle.compareTo(BigDecimal.ZERO) > 0 ? BigDecimal.valueOf(100) : BigDecimal.ZERO;
        } catch (Exception e) {
            LOG.errorf(e, "Erreur calcul croissance");
            return BigDecimal.ZERO;
        }
    }

    /**
     * Charge les informations Wave Money
     */
    private void chargerInfosWave(List<CotisationResponse> cotisationsDTO) {
        try {
            // Compter les prélèvements Wave actifs
            long prelevementsWave = cotisationsDTO.stream()
                    .filter(c -> "WAVE_MONEY".equals(c.getMethodePaiement()))
                    .filter(c -> "EN_ATTENTE".equals(c.getStatut()) || "PROGRAMMEE".equals(c.getStatut()))
                    .count();

            this.prelevementsActifs = String.valueOf(prelevementsWave);

            BigDecimal montantPrevu = cotisationsDTO.stream()
                    .filter(c -> "WAVE_MONEY".equals(c.getMethodePaiement()))
                    .filter(c -> "EN_ATTENTE".equals(c.getStatut()) || "PROGRAMMEE".equals(c.getStatut()))
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            this.montantPrelevementsPrevu = formatMontant(montantPrevu);
            this.membresPrelevementActif = (int) prelevementsWave;
            this.montantPrelevementMensuel = formatMontant(montantPrevu);

        } catch (Exception e) {
            LOG.errorf(e, "Erreur chargement infos Wave");
            this.prelevementsActifs = "0";
            this.montantPrelevementsPrevu = "0 FCFA";
            this.membresPrelevementActif = 0;
            this.montantPrelevementMensuel = "0 FCFA";
        }
    }

    private String formatMontant(BigDecimal montant) {
        if (montant == null)
            return "0 FCFA";
        return String.format("%,.0f FCFA", montant.doubleValue());
    }

    /**
     * Construit le body attendu par POST /api/notifications/groupees.
     * @param sujet   titre de la notification
     * @param corps   corps du message
     * @param ids     liste des IDs membres (chaînes UUID)
     */
    private Map<String, Object> buildNotificationRequest(String sujet, String corps, List<String> ids) {
        List<UUID> membreIds = ids.stream()
                .map(UUID::fromString)
                .collect(Collectors.toList());
        Map<String, Object> req = new HashMap<>();
        req.put("sujet", sujet);
        req.put("corps", corps);
        req.put("membreIds", membreIds);
        return req;
    }

    private void initializeFiltres() {
        this.filtres = new FiltresCotisations();
        this.listeOrganisations = new ArrayList<>();
        try {
            PagedResponse<OrganisationSummaryResponse> response = organisationService.listerToutes(0, 1000);
            if (response != null && response.getData() != null) {
                for (OrganisationSummaryResponse assoc : response.getData()) {
                    Organisation org = new Organisation();
                    org.setId(assoc.getId());
                    org.setNom(assoc.getNom());
                    listeOrganisations.add(org);
                }
            }
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du chargement des organisations");
        }
    }

    /**
     * Charge les cotisations depuis le backend
     */
    private void chargerCotisations() {
        this.cotisationsFiltrees = new ArrayList<>();
        try {
            this.cotisationsFiltrees = retryService.executeWithRetrySupplier(
                    () -> cotisationService.listerToutes(0, 1000),
                    "chargement des cotisations");
            LOG.infof("Cotisations chargées: %d cotisations", cotisationsFiltrees.size());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du chargement des cotisations");
            errorHandler.handleException(e, "lors du chargement des cotisations", null);
        }
    }

    /**
     * Calcule les top organisations depuis les données réelles
     */
    private void chargerTopOrganisations() {
        this.topOrganisations = new ArrayList<>();
        try {
            PagedResponse<OrganisationSummaryResponse> response = organisationService.listerToutes(0, 1000);
            List<OrganisationSummaryResponse> associations = response != null && response.getData() != null ? response.getData()
                    : new ArrayList<>();
            List<CotisationResponse> cotisationsDTO = cotisationService.listerToutes(0, 1000);

            for (OrganisationSummaryResponse assoc : associations.stream().limit(5).collect(Collectors.toList())) {
                List<CotisationResponse> cotisationsOrg = cotisationsDTO.stream()
                        .filter(c -> c.getOrganisationId() != null && c.getOrganisationId().equals(assoc.getId()))
                        .collect(Collectors.toList());

                long total = cotisationsOrg.size();
                long payees = cotisationsOrg.stream().filter(c -> "PAYEE".equals(c.getStatut())).count();
                int taux = total > 0 ? (int) ((double) payees / total * 100.0) : 0;

                BigDecimal montantCollecte = cotisationsOrg.stream()
                        .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut()))
                        .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                OrganisationPerformante org = new OrganisationPerformante();
                org.setNom(assoc.getNom());
                org.setTauxRecouvrement(taux);
                org.setMontantCollecte(formatMontant(montantCollecte));
                org.setNombreMembresAJour((int) payees);
                org.setTotalMembres((int) total);
                topOrganisations.add(org);
            }

            // Trier par taux de recouvrement décroissant
            topOrganisations.sort((a, b) -> b.getTauxRecouvrement() - a.getTauxRecouvrement());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du chargement des top organisations");
        }
    }

    /**
     * Calcule la répartition par méthode de paiement depuis les données réelles
     */
    private void chargerRepartitionMethodes() {
        try {
            List<CotisationResponse> cotisationsDTO = cotisationService.listerToutes(0, 1000);

            // Calculer le total des paiements
            BigDecimal totalPaiements = cotisationsDTO.stream()
                    .filter(c -> c.getMethodePaiement() != null
                            && ("PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            if (totalPaiements.compareTo(BigDecimal.ZERO) == 0) {
                paiementsWave = 0;
                paiementsVirement = 0;
                paiementsEspeces = 0;
                return;
            }

            // Calculer par méthode
            BigDecimal montantWave = cotisationsDTO.stream()
                    .filter(c -> "WAVE_MONEY".equals(c.getMethodePaiement())
                            && ("PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal montantVirement = cotisationsDTO.stream()
                    .filter(c -> "VIREMENT".equals(c.getMethodePaiement())
                            && ("PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal montantEspeces = cotisationsDTO.stream()
                    .filter(c -> "ESPECES".equals(c.getMethodePaiement())
                            && ("PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())))
                    .map(c -> c.getMontantPaye() != null ? c.getMontantPaye() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            paiementsWave = montantWave.multiply(BigDecimal.valueOf(100))
                    .divide(totalPaiements, 0, java.math.RoundingMode.HALF_UP)
                    .intValue();
            paiementsVirement = montantVirement.multiply(BigDecimal.valueOf(100))
                    .divide(totalPaiements, 0, java.math.RoundingMode.HALF_UP)
                    .intValue();
            paiementsEspeces = montantEspeces.multiply(BigDecimal.valueOf(100))
                    .divide(totalPaiements, 0, java.math.RoundingMode.HALF_UP)
                    .intValue();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du calcul de la répartition");
            paiementsWave = 0;
            paiementsVirement = 0;
            paiementsEspeces = 0;
        }
    }

    private void initializeNouvelleCampagne() {
        this.nouvelleCampagne = new NouvelleCampagne();
    }

    private String getInitiales(String nom) {
        if (nom == null || nom.trim().isEmpty())
            return "??";
        String[] parts = nom.trim().split("\\s+");
        if (parts.length >= 2) {
            return String.valueOf(parts[0].charAt(0)).toUpperCase()
                    + String.valueOf(parts[1].charAt(0)).toUpperCase();
        }
        return String.valueOf(nom.charAt(0)).toUpperCase() + "M";
    }

    // Actions principales

    /**
     * Crée une campagne de cotisations (plusieurs cotisations en une fois)
     */
    public void creerCampagne() {
        try {
            LOG.infof("Création de la campagne: %s", nouvelleCampagne.getNom());

            // Récupérer les membres selon le scope sélectionné
            List<MembreResponse> membres = new ArrayList<>();

            if ("TOUTES".equals(nouvelleCampagne.getScope())) {
                // Tous les membres actifs de toutes les associations
                membres = membreService.listerActifs();
            } else if (nouvelleCampagne.getScope() != null && !nouvelleCampagne.getScope().isEmpty()) {
                // Membres d'une association spécifique
                try {
                    UUID organisationId = UUID.fromString(nouvelleCampagne.getScope());
                    membres = membreService.listerParOrganisation(organisationId);
                } catch (IllegalArgumentException e) {
                    membres = membreService.listerActifs();
                }
            }

            int cotisationsCreees = 0;
            for (MembreResponse membre : membres) {
                if (membre.getOrganisationId() == null) {
                    LOG.warnf("Membre %s ignoré (pas d'organisation associée)", membre.getId());
                    continue;
                }
                CreateCotisationRequest request = CreateCotisationRequest.builder()
                        .membreId(membre.getId())
                        .organisationId(membre.getOrganisationId())
                        .typeCotisation(nouvelleCampagne.getType())
                        .libelle(nouvelleCampagne.getNom())
                        .description(nouvelleCampagne.getDescription() != null
                                ? nouvelleCampagne.getDescription()
                                : "Campagne: " + nouvelleCampagne.getNom())
                        .montantDu(nouvelleCampagne.getMontant())
                        .codeDevise("XOF")
                        .dateEcheance(nouvelleCampagne.getDateEcheance())
                        .recurrente(false)
                        .build();

                cotisationService.creer(request);
                cotisationsCreees++;
            }

            // Envoyer notification si relance automatique activée
            if (nouvelleCampagne.isRelanceAutomatique() && !membres.isEmpty()) {
                List<String> destinataires = membres.stream()
                        .map(m -> m.getId().toString())
                        .collect(Collectors.toList());

                try {
                    notificationService.envoyerNotificationGroupe(buildNotificationRequest(
                            "Nouvelle cotisation: " + nouvelleCampagne.getNom(),
                            "Une nouvelle cotisation de " + formatMontant(nouvelleCampagne.getMontant())
                                    + " est due pour le "
                                    + nouvelleCampagne.getDateEcheance()
                                            .format(DateTimeFormatter.ofPattern("dd/MM/yyyy")),
                            destinataires));
                } catch (Exception e) {
                    LOG.warnf(e, "Impossible d'envoyer les notifications");
                }
            }

            LOG.infof("Campagne créée avec succès: %d cotisation(s)", cotisationsCreees);
            errorHandler.showSuccess("Campagne", "Campagne créée avec succès: " + cotisationsCreees + " cotisation(s)");
            initializeNouvelleCampagne();
            chargerCotisations();
            chargerKPIs();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la création de la campagne");
            errorHandler.handleException(e, "lors de la création d'une campagne", null);
        }
    }

    /**
     * Envoie des relances groupées à tous les membres en retard
     */
    public void relancesGroupees() {
        try {
            LOG.info("Envoi de relances groupées");

            // Récupérer les cotisations en retard
            List<CotisationResponse> cotisationsEnRetard = retryService.executeWithRetrySupplier(
                    () -> cotisationService.obtenirEnRetard(0, 1000),
                    "récupération des cotisations en retard");

            if (cotisationsEnRetard.isEmpty()) {
                errorHandler.showInfo("Info", "Aucune cotisation en retard à relancer");
                return;
            }

            // Grouper par membre et envoyer notifications
            List<String> destinataires = cotisationsEnRetard.stream()
                    .filter(c -> c.getMembreId() != null)
                    .map(c -> c.getMembreId().toString())
                    .distinct()
                    .collect(Collectors.toList());

            BigDecimal montantTotalRetard = cotisationsEnRetard.stream()
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            retryService.executeWithRetrySupplier(
                    () -> {
                        notificationService.envoyerNotificationGroupe(buildNotificationRequest(
                                "Rappel: Cotisation(s) en retard",
                                "Vous avez des cotisations en retard. Montant total dû: "
                                        + formatMontant(montantTotalRetard)
                                        + ". Veuillez régulariser votre situation.",
                                destinataires));
                        return null;
                    },
                    "envoi de relances groupées");

            LOG.infof("%d relance(s) envoyée(s)", destinataires.size());
            errorHandler.showSuccess("Relances", destinataires.size() + " relance(s) envoyée(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi des relances");
            errorHandler.handleException(e, "lors de l'envoi de relances groupées", null);
        }
    }

    /**
     * Exporte toutes les cotisations en CSV
     */
    public void exporterTout() {
        try {
            LOG.info("Export global des cotisations");

            byte[] csvData = retryService.executeWithRetrySupplier(
                    () -> exportService.exporterCotisationsCSV(
                            filtres.getStatut(),
                            filtres.getType(),
                            filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty()
                                    ? UUID.fromString(filtres.getOrganisation())
                                    : null),
                    "export global des cotisations");

            telechargerFichier(csvData, "cotisations-export.csv", "text/csv");

            errorHandler.showSuccess("Export", "Export terminé avec succès");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'export");
            errorHandler.handleException(e, "lors de l'export global des cotisations", null);
        }
    }

    /**
     * Applique les filtres en utilisant la recherche backend
     */
    public void appliquerFiltres() {
        try {
            // Trouver l'ID de l'organisation
            UUID organisationId = null;
            if (filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty()) {
                for (Organisation org : listeOrganisations) {
                    if (org.getId().toString().equals(filtres.getOrganisation())) {
                        organisationId = org.getId();
                        break;
                    }
                }
            }

            final UUID organisationIdFinal = organisationId; // Variable final pour la lambda

            // Utiliser la recherche backend
            cotisationsFiltrees = cotisationService.rechercher(
                    null, // membreId
                    filtres.getStatut(),
                    filtres.getType(),
                    null, // annee
                    null, // mois
                    0,
                    1000);

            // Filtrer par association si nécessaire
            if (organisationIdFinal != null) {
                cotisationsFiltrees = cotisationsFiltrees.stream()
                        .filter(c -> c.getOrganisationId() != null && c.getOrganisationId().equals(organisationIdFinal))
                        .collect(Collectors.toList());
            }

            // Filtrer par recherche textuelle
            if (filtres.getRecherche() != null && !filtres.getRecherche().trim().isEmpty()) {
                String recherche = filtres.getRecherche().toLowerCase();
                cotisationsFiltrees = cotisationsFiltrees.stream()
                        .filter(c -> (c.getNomMembre() != null && c.getNomMembre().toLowerCase().contains(recherche))
                                || (c.getNumeroMembre() != null
                                        && c.getNumeroMembre().toLowerCase().contains(recherche))
                                || (c.getNumeroReference() != null
                                        && c.getNumeroReference().toLowerCase().contains(recherche)))
                        .collect(Collectors.toList());
            }

            // Filtrer par montant
            if (filtres.getMontantMin() != null) {
                cotisationsFiltrees = cotisationsFiltrees.stream()
                        .filter(c -> c.getMontantDu() != null
                                && c.getMontantDu().compareTo(filtres.getMontantMin()) >= 0)
                        .collect(Collectors.toList());
            }

            if (filtres.getMontantMax() != null) {
                cotisationsFiltrees = cotisationsFiltrees.stream()
                        .filter(c -> c.getMontantDu() != null
                                && c.getMontantDu().compareTo(filtres.getMontantMax()) <= 0)
                        .collect(Collectors.toList());
            }

            // Filtrer par méthode de paiement
            if (filtres.getModePaiement() != null && !filtres.getModePaiement().isEmpty()) {
                cotisationsFiltrees = cotisationsFiltrees.stream()
                        .filter(c -> filtres.getModePaiement().equals(c.getMethodePaiement()))
                        .collect(Collectors.toList());
            }

            LOG.infof("Filtres appliqués: %d cotisation(s) trouvée(s)", cotisationsFiltrees.size());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'application des filtres");
            errorHandler.handleException(e, "lors de l'application des filtres", null);
            cotisationsFiltrees = new ArrayList<>();
        }
    }

    public void reinitialiserFiltres() {
        this.filtres = new FiltresCotisations();
        chargerCotisations();
        appliquerFiltres();
    }

    /**
     * Exporte les cotisations filtrées en CSV (compatible Excel)
     */
    public void exporterExcel() {
        try {
            LOG.infof("Export Excel de %d cotisations", cotisationsFiltrees.size());

            if (cotisationsFiltrees.isEmpty()) {
                errorHandler.showWarning("Attention", "Aucune cotisation à exporter");
                return;
            }

            List<UUID> ids = cotisationsFiltrees.stream()
                    .map(CotisationResponse::getId)
                    .filter(id -> id != null)
                    .collect(Collectors.toList());

            byte[] csvData = retryService.executeWithRetrySupplier(
                    () -> exportService.exporterCotisationsSelectionneesCSV(ids),
                    "export Excel des cotisations filtrées");

            telechargerFichier(csvData, "cotisations-filtrees.csv", "text/csv");

            errorHandler.showSuccess("Export", "Export de " + cotisationsFiltrees.size() + " cotisation(s) terminé");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'export Excel");
            errorHandler.handleException(e, "lors de l'export Excel des cotisations", null);
        }
    }

    // Actions sur cotisations individuelles

    /**
     * Enregistre un paiement pour une cotisation via le backend
     */
    public void ouvrirDialogPaiement(CotisationResponse cotisation) {
        if (cotisation == null) return;
        this.cotisationAPayer = cotisation;
        this.montantPaiement = cotisation.getMontantDu();
        this.modePaiement = "ESPECES";
        this.datePaiement = LocalDate.now();
        this.referencePaiement = "";
        PrimeFaces.current().executeScript("PF('dlgPaiement').show();");
    }

    public void confirmerPaiement() {
        if (cotisationAPayer == null) return;
        try {
            Map<String, Object> data = new HashMap<>();
            data.put("montantPaye", montantPaiement != null ? montantPaiement : cotisationAPayer.getMontantDu());
            data.put("datePaiement", datePaiement != null ? datePaiement.toString() : LocalDate.now().toString());
            data.put("modePaiement", modePaiement != null ? modePaiement : "ESPECES");
            data.put("reference", referencePaiement != null ? referencePaiement : "");

            retryService.executeWithRetrySupplier(
                    () -> cotisationService.payer(cotisationAPayer.getId(), data),
                    "enregistrement d'un paiement");

            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();

            PrimeFaces.current().executeScript("PF('dlgPaiement').hide();");
            LOG.infof("Paiement enregistré pour: %s", cotisationAPayer.getNumeroMembre());
            errorHandler.showSuccess("Succès", "Paiement enregistré avec succès");
            cotisationAPayer = null;
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'enregistrement du paiement");
            errorHandler.handleException(e, "lors de l'enregistrement d'un paiement", null);
        }
    }

    /**
     * Génère un reçu pour une cotisation
     */
    public void genererRecu(CotisationResponse cotisation) {
        if (cotisation == null || cotisation.getId() == null) {
            errorHandler.showWarning("Attention", "Cotisation invalide");
            return;
        }

        try {
            LOG.infof("Génération reçu pour: %s", cotisation.getNumeroMembre());

            byte[] recu = retryService.executeWithRetrySupplier(
                    () -> exportService.genererRecu(cotisation.getId()),
                    "génération d'un reçu");

            telechargerFichier(recu, "recu-" + cotisation.getNumeroReference() + ".txt", "text/plain");

            errorHandler.showSuccess("Reçu", "Reçu généré pour " + cotisation.getNomMembre());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur génération reçu");
            errorHandler.handleException(e, "lors de la génération d'un reçu", null);
        }
    }

    /**
     * Envoie un rappel pour une cotisation
     */
    public void envoyerRappel(CotisationResponse cotisation) {
        if (cotisation == null || cotisation.getMembreId() == null) {
            errorHandler.showWarning("Attention", "Cotisation invalide");
            return;
        }

        try {
            LOG.infof("Envoi rappel pour: %s", cotisation.getNumeroMembre());

            final String messageFinal;
            String message = "Rappel: Votre cotisation de " + formatMontant(cotisation.getMontantDu())
                    + " est en attente de paiement.";
            if (cotisation.getDateEcheance() != null) {
                message += " Date d'échéance: "
                        + cotisation.getDateEcheance().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
            }
            messageFinal = message;

            retryService.executeWithRetrySupplier(
                    () -> {
                        notificationService.envoyerNotificationGroupe(buildNotificationRequest(
                                "Rappel de cotisation",
                                messageFinal,
                                List.of(cotisation.getMembreId().toString())));
                        return null;
                    },
                    "envoi d'un rappel de cotisation");

            errorHandler.showSuccess("Rappel", "Rappel envoyé à " + cotisation.getNomMembre());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur envoi rappel");
            errorHandler.handleException(e, "lors de l'envoi d'un rappel", null);
        }
    }

    /**
     * Affiche les détails d'une cotisation
     */
    public void voirDetails(CotisationResponse cotisation) {
        this.cotisationDetail = cotisation;
        PrimeFaces.current().executeScript("PF('dlgDetailCotisation').show();");
    }

    public CotisationResponse getCotisationDetail() {
        return cotisationDetail;
    }

    public void setCotisationDetail(CotisationResponse cotisationDetail) {
        this.cotisationDetail = cotisationDetail;
    }

    public CotisationResponse getCotisationAPayer() { return cotisationAPayer; }
    public void setCotisationAPayer(CotisationResponse c) { this.cotisationAPayer = c; }
    public BigDecimal getMontantPaiement() { return montantPaiement; }
    public void setMontantPaiement(BigDecimal montantPaiement) { this.montantPaiement = montantPaiement; }
    public String getModePaiement() { return modePaiement; }
    public void setModePaiement(String modePaiement) { this.modePaiement = modePaiement; }
    public LocalDate getDatePaiement() { return datePaiement; }
    public void setDatePaiement(LocalDate datePaiement) { this.datePaiement = datePaiement; }
    public String getReferencePaiement() { return referencePaiement; }
    public void setReferencePaiement(String referencePaiement) { this.referencePaiement = referencePaiement; }

    // Actions groupées

    /**
     * Marque plusieurs cotisations comme payées via le backend
     */
    public void marquerPayeesGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            errorHandler.showWarning("Attention", "Aucune cotisation sélectionnée");
            return;
        }

        try {
            int compteur = 0;
            for (CotisationResponse cotisation : cotisationsSelectionnees) {
                retryService.executeWithRetrySupplier(
                        () -> cotisationService.payer(cotisation.getId(), Map.of(
                                "montantPaye", cotisation.getMontantDu(),
                                "datePaiement", LocalDateTime.now().toLocalDate().toString(),
                                "modePaiement", "MANUEL",
                                "reference", cotisation.getNumeroReference() != null
                                        ? cotisation.getNumeroReference() : "")),
                        "marquage d'une cotisation comme payée");
                compteur++;
            }

            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();
            cotisationsSelectionnees.clear();
            calculerMontantTotalSelectionne();

            LOG.infof("Marquage %d cotisations comme payées", compteur);
            errorHandler.showSuccess("Succès", compteur + " cotisation(s) marquée(s) comme payée(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du marquage groupé");
            errorHandler.handleException(e, "lors du marquage groupé de cotisations", null);
        }
    }

    /**
     * Envoie des relances groupées pour les cotisations sélectionnées
     */
    public void envoyerRelancesGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            errorHandler.showWarning("Attention", "Aucune cotisation sélectionnée");
            return;
        }

        try {
            LOG.infof("Envoi relances pour %d cotisations", cotisationsSelectionnees.size());

            List<String> destinataires = cotisationsSelectionnees.stream()
                    .filter(c -> c.getMembreId() != null)
                    .map(c -> c.getMembreId().toString())
                    .distinct()
                    .collect(Collectors.toList());

            BigDecimal montantTotal = cotisationsSelectionnees.stream()
                    .map(c -> c.getMontantDu() != null ? c.getMontantDu() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            retryService.executeWithRetrySupplier(
                    () -> {
                        notificationService.envoyerNotificationGroupe(buildNotificationRequest(
                                "Rappel de paiement",
                                "Vous avez des cotisations en attente de paiement. Montant: "
                                        + formatMontant(montantTotal),
                                destinataires));
                        return null;
                    },
                    "envoi de relances groupées");

            LOG.infof("%d relance(s) envoyée(s)", destinataires.size());
            errorHandler.showSuccess("Relances", destinataires.size() + " relance(s) envoyée(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur envoi relances groupées");
            errorHandler.handleException(e, "lors de l'envoi de relances groupées", null);
        }
    }

    /**
     * Génère des reçus groupés pour les cotisations sélectionnées
     */
    public void genererRecusGroupes() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            errorHandler.showWarning("Attention", "Aucune cotisation sélectionnée");
            return;
        }

        try {
            LOG.infof("Génération reçus pour %d cotisations", cotisationsSelectionnees.size());

            List<UUID> ids = cotisationsSelectionnees.stream()
                    .map(CotisationResponse::getId)
                    .filter(id -> id != null)
                    .collect(Collectors.toList());

            byte[] recus = retryService.executeWithRetrySupplier(
                    () -> exportService.genererRecusGroupes(ids),
                    "génération de reçus groupés");

            telechargerFichier(recus, "recus-groupes.txt", "text/plain");

            errorHandler.showSuccess("Reçus", cotisationsSelectionnees.size() + " reçu(s) généré(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur génération reçus groupés");
            errorHandler.handleException(e, "lors de la génération de reçus groupés", null);
        }
    }

    /**
     * Annule plusieurs cotisations via le backend
     */
    public void annulerCotisationsGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            errorHandler.showWarning("Attention", "Aucune cotisation sélectionnée");
            return;
        }

        try {
            int compteur = 0;
            for (CotisationResponse cotisation : cotisationsSelectionnees) {
                // Vérifier que la cotisation peut être annulée (pas payée)
                if (!"PAYEE".equals(cotisation.getStatut())) {
                    retryService.executeWithRetrySupplier(
                            () -> {
                                cotisationService.supprimer(cotisation.getId());
                                return null;
                            },
                            "suppression d'une cotisation");
                    compteur++;
                }
            }

            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();
            cotisationsSelectionnees.clear();
            calculerMontantTotalSelectionne();

            LOG.infof("Annulation %d cotisations", compteur);
            errorHandler.showSuccess("Succès", compteur + " cotisation(s) annulée(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'annulation groupée");
            errorHandler.handleException(e, "lors de l'annulation groupée de cotisations", null);
        }
    }

    // Wave Money

    /**
     * Lance les prélèvements Wave Money pour les cotisations en attente
     */
    public void lancerPrelevements() {
        try {
            LOG.info("Lancement des prélèvements Wave Money");

            // Récupérer les cotisations en attente avec Wave comme méthode
            List<CotisationResponse> cotisationsWave = cotisationsFiltrees.stream()
                    .filter(c -> "WAVE_MONEY".equals(c.getMethodePaiement()) || c.getMethodePaiement() == null)
                    .filter(c -> "EN_ATTENTE".equals(c.getStatut()))
                    .collect(Collectors.toList());

            if (cotisationsWave.isEmpty()) {
                errorHandler.showInfo("Info", "Aucune cotisation Wave en attente");
                return;
            }

            int prelevementsLances = 0;
            for (CotisationResponse cotisation : cotisationsWave) {
                try {
                    // Créer une session de paiement Wave
                    String successUrl = "https://unionflow.lions.dev/paiement/succes?ref="
                            + cotisation.getNumeroReference();
                    String errorUrl = "https://unionflow.lions.dev/paiement/echec?ref="
                            + cotisation.getNumeroReference();

                    WaveCheckoutSessionDTO session = retryService.executeWithRetrySupplier(
                            () -> waveService.creerSessionPaiement(
                                    cotisation.getMontantDu(),
                                    "XOF",
                                    successUrl,
                                    errorUrl,
                                    cotisation.getNumeroReference(),
                                    "Cotisation: " + cotisation.getTypeCotisation(),
                                    cotisation.getOrganisationId(),
                                    cotisation.getMembreId()),
                            "création d'une session de paiement Wave");

                    if (session != null && session.getWaveSessionId() != null) {
                        // La session Wave est liée côté backend via IntentionPaiement.
                        // Pas de mise à jour de la cotisation ici (waveSessionId non géré par UpdateCotisationRequest).
                        prelevementsLances++;
                    }
                } catch (Exception e) {
                    LOG.warnf(e, "Erreur prélèvement Wave pour %s", cotisation.getNumeroReference());
                }
            }

            chargerCotisations();
            chargerKPIs();

            LOG.infof("%d prélèvement(s) Wave Money lancé(s)", prelevementsLances);
            errorHandler.showSuccess("Wave", prelevementsLances + " prélèvement(s) Wave Money lancé(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lancement prélèvements Wave");
            errorHandler.handleException(e, "lors du lancement des prélèvements Wave", null);
        }
    }

    /**
     * Teste la connexion à l'API Wave Money
     */
    public void testerAPIWave() {
        try {
            LOG.info("Test de l'API Wave Money");

            Map<String, Object> result = retryService.executeWithRetrySupplier(
                    () -> waveService.testerConnexion(),
                    "test de connexion à l'API Wave");

            if (result != null && Boolean.TRUE.equals(result.get("success"))) {
                errorHandler.showSuccess("Wave", "Connexion Wave API réussie");
            } else {
                String message = result != null ? String.valueOf(result.get("message")) : "Erreur inconnue";
                errorHandler.showWarning("Wave", "Test Wave: " + message);
            }
        } catch (Exception e) {
            LOG.errorf(e, "Erreur test API Wave");
            errorHandler.handleException(e, "lors du test de l'API Wave", null);
        }
    }

    /**
     * Affiche l'historique des prélèvements Wave
     */
    public void voirHistoriquePrelevements() {
        try {
            LOG.info("Affichage historique des prélèvements");

            // Filtrer pour afficher uniquement les cotisations Wave
            cotisationsFiltrees = cotisationsFiltrees.stream()
                    .filter(c -> "WAVE_MONEY".equals(c.getMethodePaiement()))
                    .collect(Collectors.toList());

            errorHandler.showInfo("Historique", cotisationsFiltrees.size() + " paiement(s) Wave Money trouvé(s)");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur historique prélèvements");
        }
    }

    // Actions rapides

    // Méthodes pour les rappels (WOU/DRY)
    public void envoyerRappelsGroupes() {
        if (membresSelectionnes == null || membresSelectionnes.isEmpty()) {
            errorHandler.showWarning("Attention", "Veuillez sélectionner au moins un membre");
            return;
        }
        try {
            List<UUID> membreIds = membresSelectionnes.stream()
                    .map(MembreEnRetard::getId)
                    .collect(Collectors.toList());
            retryService.executeWithRetrySupplier(
                    () -> {
                        cotisationService.envoyerRappelsGroupes(membreIds);
                        return null;
                    },
                    "envoi de rappels groupés");
            nombreRappelsEnvoyes += membreIds.size();
            errorHandler.showSuccess("Succès", nombreRappelsEnvoyes + " rappels envoyés avec succès");
            chargerMembresEnRetard();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi des rappels");
            errorHandler.handleException(e, "lors de l'envoi de rappels groupés", null);
        }
    }

    public void envoyerRappel(MembreEnRetard membre) {
        try {
            List<UUID> membreIds = List.of(membre.getId());
            retryService.executeWithRetrySupplier(
                    () -> {
                        cotisationService.envoyerRappelsGroupes(membreIds);
                        return null;
                    },
                    "envoi d'un rappel");
            nombreRappelsEnvoyes++;
            errorHandler.showSuccess("Succès", "Rappel envoyé à " + membre.getNomComplet());
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'envoi du rappel");
            errorHandler.handleException(e, "lors de l'envoi d'un rappel", null);
        }
    }

    private void chargerMembresEnRetard() {
        try {
            List<CotisationResponse> cotisationsEnRetard = retryService.executeWithRetrySupplier(
                    () -> cotisationService.obtenirEnRetard(0, 1000),
                    "chargement des cotisations en retard");
            membresEnRetard = new ArrayList<>();
            Map<UUID, MembreEnRetard> membresMap = new HashMap<>();

            for (CotisationResponse cotisation : cotisationsEnRetard) {
                UUID membreId = cotisation.getMembreId();
                MembreEnRetard membre = membresMap.get(membreId);
                if (membre == null) {
                    membre = new MembreEnRetard();
                    membre.setId(membreId);
                    membre.setNomComplet(cotisation.getNomMembre());
                    membre.setNumeroMembre(cotisation.getNumeroMembre());
                    membre.setMontantDu(BigDecimal.ZERO);
                    membre.setJoursRetard(0);
                    membresMap.put(membreId, membre);
                }
                membre.setMontantDu(membre.getMontantDu().add(cotisation.getMontantDu()));
                if (cotisation.getDateEcheance() != null) {
                    long jours = java.time.temporal.ChronoUnit.DAYS.between(
                            cotisation.getDateEcheance(),
                            java.time.LocalDate.now());
                    if (jours > membre.getJoursRetard()) {
                        membre.setJoursRetard((int) jours);
                    }
                }
            }

            membresEnRetard = new ArrayList<>(membresMap.values());
            nombreMembresEnRetard = membresEnRetard.size();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du chargement des membres en retard");
            errorHandler.handleException(e, "lors du chargement des membres en retard", null);
            membresEnRetard = new ArrayList<>();
            nombreMembresEnRetard = 0;
        }
    }

    // Méthodes pour les rapports (WOU/DRY)
    public void genererRapport() {
        LOG.info("Génération d'un rapport de cotisations");
        errorHandler.showInfo("Rapport", "Le rapport est en cours de génération");
    }

    public void genererRapportMensuel() {
        try {
            LOG.info("Génération rapport mensuel");

            int annee = LocalDate.now().getYear();
            int mois = LocalDate.now().getMonthValue();

            UUID organisationIdTemp = null;
            if (filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty()) {
                try {
                    organisationIdTemp = UUID.fromString(filtres.getOrganisation());
                } catch (IllegalArgumentException e) {
                    // Ignorer si pas un UUID valide
                }
            }
            final UUID organisationIdFinal = organisationIdTemp;

            byte[] rapport = retryService.executeWithRetrySupplier(
                    () -> exportService.genererRapportMensuel(annee, mois, organisationIdFinal),
                    "génération d'un rapport mensuel");

            telechargerFichier(rapport, "rapport-mensuel-" + annee + "-" + String.format("%02d", mois) + ".txt",
                    "text/plain");

            errorHandler.showSuccess("Rapport", "Rapport mensuel généré");
        } catch (Exception e) {
            LOG.errorf(e, "Erreur génération rapport");
            errorHandler.handleException(e, "lors de la génération d'un rapport mensuel", null);
        }
    }

    /**
     * Configure les relances automatiques
     */
    public void configurerRelancesAuto() {
        LOG.info("Configuration relances automatiques");
        // Navigation vers la page de configuration
        errorHandler.showInfo("Configuration",
                "Ouvrez les paramètres d'administration pour configurer les relances automatiques");
    }

    /**
     * Gère les types de cotisations
     */
    public void gererTypesCotisations() {
        LOG.info("Gestion des types de cotisations");
        // Navigation vers la page de gestion des types
        errorHandler.showInfo("Types", "Ouvrez les paramètres d'administration pour gérer les types de cotisations");
    }

    /**
     * Télécharge un fichier via le navigateur
     */
    private void telechargerFichier(byte[] data, String nomFichier, String contentType) {
        try {
            jakarta.faces.context.FacesContext fc = jakarta.faces.context.FacesContext.getCurrentInstance();
            ExternalContext ec = fc.getExternalContext();

            ec.responseReset();
            ec.setResponseContentType(contentType + "; charset=UTF-8");
            ec.setResponseContentLength(data.length);
            ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + nomFichier + "\"");

            OutputStream output = ec.getResponseOutputStream();
            output.write(data);
            output.flush();

            fc.responseComplete();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur téléchargement fichier");
            throw new RuntimeException("Erreur lors du téléchargement", e);
        }
    }

    /**
     * Retourne au tableau de bord
     */
    public String tableauDeBord() {
        // Utilisation de navigation outcome au lieu de chemin direct (WOU/DRY)
        return OUTCOME_DASHBOARD + "?faces-redirect=true";
    }

    /**
     * Actualise toutes les données depuis le backend
     */
    public void actualiser() {
        chargerKPIs();
        chargerCotisations();
        chargerTopOrganisations();
        chargerRepartitionMethodes();
        appliquerFiltres();
        errorHandler.showSuccess("Actualisation", "Données actualisées");
    }

    // Getters et Setters

    public String getPeriodeActuelle() {
        return periodeActuelle;
    }

    public void setPeriodeActuelle(String periodeActuelle) {
        this.periodeActuelle = periodeActuelle;
    }

    public BigDecimal getTauxRecouvrement() {
        return tauxRecouvrement;
    }

    public void setTauxRecouvrement(BigDecimal tauxRecouvrement) {
        this.tauxRecouvrement = tauxRecouvrement;
    }

    public int getTotalMembresActifs() {
        return totalMembresActifs;
    }

    public void setTotalMembresActifs(int totalMembresActifs) {
        this.totalMembresActifs = totalMembresActifs;
    }

    public String getMontantCollecte() {
        return montantCollecte;
    }

    public void setMontantCollecte(String montantCollecte) {
        this.montantCollecte = montantCollecte;
    }

    public String getObjectifMensuel() {
        return objectifMensuel;
    }

    public void setObjectifMensuel(String objectifMensuel) {
        this.objectifMensuel = objectifMensuel;
    }

    public int getProgressionMensuelle() {
        return progressionMensuelle;
    }

    public void setProgressionMensuelle(int progressionMensuelle) {
        this.progressionMensuelle = progressionMensuelle;
    }

    public String getMembresAJour() {
        return membresAJour;
    }

    public void setMembresAJour(String membresAJour) {
        this.membresAJour = membresAJour;
    }

    public int getPourcentageMembresAJour() {
        return pourcentageMembresAJour;
    }

    public void setPourcentageMembresAJour(int pourcentageMembresAJour) {
        this.pourcentageMembresAJour = pourcentageMembresAJour;
    }

    public String getMontantEnAttente() {
        return montantEnAttente;
    }

    public void setMontantEnAttente(String montantEnAttente) {
        this.montantEnAttente = montantEnAttente;
    }

    public int getNombreCotisationsEnAttente() {
        return nombreCotisationsEnAttente;
    }

    public void setNombreCotisationsEnAttente(int nombreCotisationsEnAttente) {
        this.nombreCotisationsEnAttente = nombreCotisationsEnAttente;
    }

    public String getMontantImpayes() {
        return montantImpayes;
    }

    public void setMontantImpayes(String montantImpayes) {
        this.montantImpayes = montantImpayes;
    }

    public int getJoursRetardMoyen() {
        return joursRetardMoyen;
    }

    public void setJoursRetardMoyen(int joursRetardMoyen) {
        this.joursRetardMoyen = joursRetardMoyen;
    }

    public String getRevenus2024() {
        return revenus2024;
    }

    public void setRevenus2024(String revenus2024) {
        this.revenus2024 = revenus2024;
    }

    public BigDecimal getCroissanceAnnuelle() {
        return croissanceAnnuelle;
    }

    public void setCroissanceAnnuelle(BigDecimal croissanceAnnuelle) {
        this.croissanceAnnuelle = croissanceAnnuelle;
    }

    public String getPrelevementsActifs() {
        return prelevementsActifs;
    }

    public void setPrelevementsActifs(String prelevementsActifs) {
        this.prelevementsActifs = prelevementsActifs;
    }

    public String getMontantPrelevementsPrevu() {
        return montantPrelevementsPrevu;
    }

    public void setMontantPrelevementsPrevu(String montantPrelevementsPrevu) {
        this.montantPrelevementsPrevu = montantPrelevementsPrevu;
    }

    public String getPeriodeGraphique() {
        return periodeGraphique;
    }

    public void setPeriodeGraphique(String periodeGraphique) {
        this.periodeGraphique = periodeGraphique;
    }

    public List<OrganisationPerformante> getTopOrganisations() {
        return topOrganisations;
    }

    public void setTopOrganisations(List<OrganisationPerformante> topOrganisations) {
        this.topOrganisations = topOrganisations;
    }

    public int getPaiementsWave() {
        return paiementsWave;
    }

    public void setPaiementsWave(int paiementsWave) {
        this.paiementsWave = paiementsWave;
    }

    public int getPaiementsVirement() {
        return paiementsVirement;
    }

    public void setPaiementsVirement(int paiementsVirement) {
        this.paiementsVirement = paiementsVirement;
    }

    public int getPaiementsEspeces() {
        return paiementsEspeces;
    }

    public void setPaiementsEspeces(int paiementsEspeces) {
        this.paiementsEspeces = paiementsEspeces;
    }

    public FiltresCotisations getFiltres() {
        return filtres;
    }

    public void setFiltres(FiltresCotisations filtres) {
        this.filtres = filtres;
    }

    public List<Organisation> getListeOrganisations() {
        return listeOrganisations;
    }

    public void setListeOrganisations(List<Organisation> listeOrganisations) {
        this.listeOrganisations = listeOrganisations;
    }

    public List<CotisationResponse> getCotisationsFiltrees() {
        return cotisationsFiltrees;
    }

    public void setCotisationsFiltrees(List<CotisationResponse> cotisationsFiltrees) {
        this.cotisationsFiltrees = cotisationsFiltrees;
    }

    public List<CotisationResponse> getCotisationsSelectionnees() {
        return cotisationsSelectionnees;
    }

    public void setCotisationsSelectionnees(List<CotisationResponse> cotisationsSelectionnees) {
        this.cotisationsSelectionnees = cotisationsSelectionnees;
        calculerMontantTotalSelectionne();
    }

    public String getMontantTotalSelectionne() {
        return montantTotalSelectionne;
    }

    public void setMontantTotalSelectionne(String montantTotalSelectionne) {
        this.montantTotalSelectionne = montantTotalSelectionne;
    }

    public int getMembresPrelevementActif() {
        return membresPrelevementActif;
    }

    public void setMembresPrelevementActif(int membresPrelevementActif) {
        this.membresPrelevementActif = membresPrelevementActif;
    }

    public String getMontantPrelevementMensuel() {
        return montantPrelevementMensuel;
    }

    public void setMontantPrelevementMensuel(String montantPrelevementMensuel) {
        this.montantPrelevementMensuel = montantPrelevementMensuel;
    }

    public String getProchainPrelevement() {
        return prochainPrelevement;
    }

    public void setProchainPrelevement(String prochainPrelevement) {
        this.prochainPrelevement = prochainPrelevement;
    }

    public NouvelleCampagne getNouvelleCampagne() {
        return nouvelleCampagne;
    }

    public void setNouvelleCampagne(NouvelleCampagne nouvelleCampagne) {
        this.nouvelleCampagne = nouvelleCampagne;
    }

    private void calculerMontantTotalSelectionne() {
        if (cotisationsSelectionnees != null && !cotisationsSelectionnees.isEmpty()) {
            BigDecimal total = cotisationsSelectionnees.stream()
                    .map(CotisationResponse::getMontantDu)
                    .filter(m -> m != null)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            this.montantTotalSelectionne = formatMontant(total);
        } else {
            this.montantTotalSelectionne = "0 FCFA";
        }
    }

    // Classes internes pour les formulaires et données d'affichage

    /**
     * Classe pour les filtres de recherche
     */
    public static class FiltresCotisations implements Serializable {
        private static final long serialVersionUID = 1L;

        private String organisation = "";
        private String periode = "MOIS";
        private String statut = "";
        private String type = "";
        private String recherche = "";
        private BigDecimal montantMin;
        private BigDecimal montantMax;
        private String retardJours = "";
        private String modePaiement = "";

        // Getters et setters
        public String getOrganisation() {
            return organisation;
        }

        public void setOrganisation(String organisation) {
            this.organisation = organisation;
        }

        public String getPeriode() {
            return periode;
        }

        public void setPeriode(String periode) {
            this.periode = periode;
        }

        public String getStatut() {
            return statut;
        }

        public void setStatut(String statut) {
            this.statut = statut;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getRecherche() {
            return recherche;
        }

        public void setRecherche(String recherche) {
            this.recherche = recherche;
        }

        public BigDecimal getMontantMin() {
            return montantMin;
        }

        public void setMontantMin(BigDecimal montantMin) {
            this.montantMin = montantMin;
        }

        public BigDecimal getMontantMax() {
            return montantMax;
        }

        public void setMontantMax(BigDecimal montantMax) {
            this.montantMax = montantMax;
        }

        public String getRetardJours() {
            return retardJours;
        }

        public void setRetardJours(String retardJours) {
            this.retardJours = retardJours;
        }

        public String getModePaiement() {
            return modePaiement;
        }

        public void setModePaiement(String modePaiement) {
            this.modePaiement = modePaiement;
        }
    }

    /**
     * Classe pour représenter une organisation dans les filtres
     */
    public static class Organisation implements Serializable {
        private static final long serialVersionUID = 1L;

        private UUID id;
        private String nom;

        public UUID getId() {
            return id;
        }

        public void setId(UUID id) {
            this.id = id;
        }

        public String getNom() {
            return nom;
        }

        public void setNom(String nom) {
            this.nom = nom;
        }
    }

    /**
     * Classe pour les organisations performantes (top 5)
     */
    public static class OrganisationPerformante implements Serializable {
        private static final long serialVersionUID = 1L;

        private String nom;
        private int tauxRecouvrement;
        private String montantCollecte;
        private int nombreMembresAJour;
        private int totalMembres;

        public String getNom() {
            return nom;
        }

        public void setNom(String nom) {
            this.nom = nom;
        }

        public int getTauxRecouvrement() {
            return tauxRecouvrement;
        }

        public void setTauxRecouvrement(int tauxRecouvrement) {
            this.tauxRecouvrement = tauxRecouvrement;
        }

        public String getMontantCollecte() {
            return montantCollecte;
        }

        public void setMontantCollecte(String montantCollecte) {
            this.montantCollecte = montantCollecte;
        }

        public int getNombreMembresAJour() {
            return nombreMembresAJour;
        }

        public void setNombreMembresAJour(int nombreMembresAJour) {
            this.nombreMembresAJour = nombreMembresAJour;
        }

        public int getTotalMembres() {
            return totalMembres;
        }

        public void setTotalMembres(int totalMembres) {
            this.totalMembres = totalMembres;
        }
    }

    /**
     * Classe pour le formulaire de nouvelle campagne
     */
    public static class NouvelleCampagne implements Serializable {
        private static final long serialVersionUID = 1L;

        private String nom;
        private String type;
        private BigDecimal montant;
        private LocalDate dateEcheance;
        private String scope = "TOUTES";
        private String description;
        private boolean relanceAutomatique = true;

        public String getNom() {
            return nom;
        }

        public void setNom(String nom) {
            this.nom = nom;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public BigDecimal getMontant() {
            return montant;
        }

        public void setMontant(BigDecimal montant) {
            this.montant = montant;
        }

        public LocalDate getDateEcheance() {
            return dateEcheance;
        }

        public void setDateEcheance(LocalDate dateEcheance) {
            this.dateEcheance = dateEcheance;
        }

        public String getScope() {
            return scope;
        }

        public void setScope(String scope) {
            this.scope = scope;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public boolean isRelanceAutomatique() {
            return relanceAutomatique;
        }

        public void setRelanceAutomatique(boolean relanceAutomatique) {
            this.relanceAutomatique = relanceAutomatique;
        }
    }

    /**
     * Méthode utilitaire pour obtenir les initiales d'un membre depuis
     * CotisationResponse
     */
    public String getInitialesMembre(CotisationResponse cotisation) {
        if (cotisation == null || cotisation.getNomMembre() == null) {
            return "??";
        }
        return getInitiales(cotisation.getNomMembre());
    }

    // Getters et Setters pour les rappels (WOU/DRY)
    public List<MembreEnRetard> getMembresEnRetard() {
        if (membresEnRetard == null || membresEnRetard.isEmpty()) {
            chargerMembresEnRetard();
        }
        return membresEnRetard;
    }

    public void setMembresEnRetard(List<MembreEnRetard> membresEnRetard) {
        this.membresEnRetard = membresEnRetard;
    }

    public List<MembreEnRetard> getMembresSelectionnes() {
        return membresSelectionnes;
    }

    public void setMembresSelectionnes(List<MembreEnRetard> membresSelectionnes) {
        this.membresSelectionnes = membresSelectionnes;
    }

    public int getNombreMembresEnRetard() {
        if (nombreMembresEnRetard == 0 && (membresEnRetard == null || membresEnRetard.isEmpty())) {
            chargerMembresEnRetard();
        }
        return nombreMembresEnRetard;
    }

    public void setNombreMembresEnRetard(int nombreMembresEnRetard) {
        this.nombreMembresEnRetard = nombreMembresEnRetard;
    }

    public int getNombreRappelsEnvoyes() {
        return nombreRappelsEnvoyes;
    }

    public void setNombreRappelsEnvoyes(int nombreRappelsEnvoyes) {
        this.nombreRappelsEnvoyes = nombreRappelsEnvoyes;
    }

    // Classe interne pour les membres en retard (WOU/DRY)
    public static class MembreEnRetard {
        private UUID id;
        private String nomComplet;
        private String numeroMembre;
        private BigDecimal montantDu;
        private int joursRetard;

        public UUID getId() {
            return id;
        }

        public void setId(UUID id) {
            this.id = id;
        }

        public String getNomComplet() {
            return nomComplet;
        }

        public void setNomComplet(String nomComplet) {
            this.nomComplet = nomComplet;
        }

        public String getNumeroMembre() {
            return numeroMembre;
        }

        public void setNumeroMembre(String numeroMembre) {
            this.numeroMembre = numeroMembre;
        }

        public BigDecimal getMontantDu() {
            return montantDu;
        }

        public void setMontantDu(BigDecimal montantDu) {
            this.montantDu = montantDu;
        }

        public int getJoursRetard() {
            return joursRetard;
        }

        public void setJoursRetard(int joursRetard) {
            this.joursRetard = joursRetard;
        }
    }
}
