package dev.lions.unionflow.client.view;

import dev.lions.unionflow.client.dto.CotisationDTO;
import dev.lions.unionflow.client.dto.MembreDTO;
import dev.lions.unionflow.client.dto.AssociationDTO;
import dev.lions.unionflow.client.dto.WaveCheckoutSessionDTO;
import dev.lions.unionflow.client.service.CotisationService;
import dev.lions.unionflow.client.service.AssociationService;
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 jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import org.eclipse.microprofile.rest.client.inject.RestClient;
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.stream.Collectors;
import java.util.logging.Logger;

/**
 * Bean JSF pour la gestion administrative des cotisations
 * Refactorisé pour utiliser directement CotisationDTO et se connecter au backend
 * 
 * @author UnionFlow Team
 * @version 2.0
 */
@Named("cotisationsGestionBean")
@SessionScoped
public class CotisationsGestionBean implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private static final Logger LOGGER = Logger.getLogger(CotisationsGestionBean.class.getName());
    
    @Inject
    @RestClient
    private CotisationService cotisationService;
    
    @Inject
    @RestClient
    private AssociationService associationService;
    
    @Inject
    @RestClient
    private MembreService membreService;
    
    @Inject
    @RestClient
    private WaveService waveService;
    
    @Inject
    @RestClient
    private NotificationClientService notificationService;
    
    @Inject
    @RestClient
    private ExportClientService exportService;
    
    @Inject
    private UserSession userSession;
    
    // 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 String 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 CotisationDTO
    private List<CotisationDTO> cotisationsFiltrees;
    private List<CotisationDTO> cotisationsSelectionnees;
    private String montantTotalSelectionne;
    
    // Wave Money
    private int membresPrelevementActif;
    private String montantPrelevementMensuel;
    private String prochainPrelevement;
    
    // Nouvelle campagne
    private NouvelleCampagne nouvelleCampagne;
    
    @PostConstruct
    public void init() {
        chargerKPIs();
        initializeFiltres();
        chargerCotisations();
        chargerTopOrganisations();
        chargerRepartitionMethodes();
        initializeNouvelleCampagne();
    }
    
    /**
     * Charge les KPIs depuis le backend
     */
    private void chargerKPIs() {
        try {
            Map<String, Object> statsBackend = cotisationService.obtenirStatistiques();
            List<CotisationDTO> 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(CotisationDTO::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);
            this.objectifMensuel = formatMontant(totalCollecte.multiply(new BigDecimal("1.15")));
            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);
            this.revenus2024 = formatMontant(totalCollecte.multiply(new BigDecimal("12")));
            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) {
            LOGGER.severe("Erreur lors du calcul des KPIs: " + e.getMessage());
            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 = "0%";
        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 String calculerCroissanceAnnuelle(List<CotisationDTO> 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) {
                BigDecimal croissance = montantAnneeActuelle.subtract(montantAnneePrecedente)
                    .multiply(BigDecimal.valueOf(100))
                    .divide(montantAnneePrecedente, 1, java.math.RoundingMode.HALF_UP);
                return (croissance.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "") + croissance + "%";
            }
            return montantAnneeActuelle.compareTo(BigDecimal.ZERO) > 0 ? "+100%" : "0%";
        } catch (Exception e) {
            LOGGER.severe("Erreur calcul croissance: " + e.getMessage());
            return "N/A";
        }
    }
    
    /**
     * Charge les informations Wave Money
     */
    private void chargerInfosWave(List<CotisationDTO> 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) {
            LOGGER.severe("Erreur chargement infos Wave: " + e.getMessage());
            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());
    }
    
    private void initializeFiltres() {
        this.filtres = new FiltresCotisations();
        this.listeOrganisations = new ArrayList<>();
        try {
            List<AssociationDTO> associations = associationService.listerToutes(0, 1000);
            for (AssociationDTO assoc : associations) {
                Organisation org = new Organisation();
                org.setId(assoc.getId());
                org.setNom(assoc.getNom());
                listeOrganisations.add(org);
            }
        } catch (Exception e) {
            LOGGER.severe("Erreur lors du chargement des organisations: " + e.getMessage());
        }
    }
    
    /**
     * Charge les cotisations depuis le backend
     */
    private void chargerCotisations() {
        this.cotisationsFiltrees = new ArrayList<>();
        try {
            this.cotisationsFiltrees = cotisationService.listerToutes(0, 1000);
            LOGGER.info("Chargement de " + cotisationsFiltrees.size() + " cotisations");
        } catch (Exception e) {
            LOGGER.severe("Erreur lors du chargement des cotisations: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de charger les cotisations: " + e.getMessage()));
        }
    }
    
    /**
     * Calcule les top organisations depuis les données réelles
     */
    private void chargerTopOrganisations() {
        this.topOrganisations = new ArrayList<>();
        try {
            List<AssociationDTO> associations = associationService.listerToutes(0, 1000);
            List<CotisationDTO> cotisationsDTO = cotisationService.listerToutes(0, 1000);
            
            for (AssociationDTO assoc : associations.stream().limit(5).collect(Collectors.toList())) {
                List<CotisationDTO> cotisationsOrg = cotisationsDTO.stream()
                    .filter(c -> c.getAssociationId() != null && c.getAssociationId().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) {
            LOGGER.severe("Erreur lors du chargement des top organisations: " + e.getMessage());
        }
    }
    
    /**
     * Calcule la répartition par méthode de paiement depuis les données réelles
     */
    private void chargerRepartitionMethodes() {
        try {
            List<CotisationDTO> 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) {
            LOGGER.severe("Erreur lors du calcul de la répartition: " + e.getMessage());
            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 {
            LOGGER.info("Création de la campagne: " + nouvelleCampagne.getNom());
            
            // Récupérer les membres selon le scope sélectionné
            List<MembreDTO> 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 associationId = UUID.fromString(nouvelleCampagne.getScope());
                    membres = membreService.listerParAssociation(associationId);
                } catch (IllegalArgumentException e) {
                    membres = membreService.listerActifs();
                }
            }
            
            int cotisationsCreees = 0;
            for (MembreDTO membre : membres) {
                CotisationDTO nouvelleCot = new CotisationDTO();
                nouvelleCot.setMembreId(membre.getId());
                nouvelleCot.setNomMembre(membre.getNom() + " " + membre.getPrenom());
                nouvelleCot.setNumeroMembre(membre.getNumeroMembre());
                nouvelleCot.setTypeCotisation(nouvelleCampagne.getType());
                nouvelleCot.setMontantDu(nouvelleCampagne.getMontant());
                nouvelleCot.setMontantPaye(BigDecimal.ZERO);
                nouvelleCot.setDateEcheance(nouvelleCampagne.getDateEcheance());
                nouvelleCot.setStatut("EN_ATTENTE");
                nouvelleCot.setDescription(nouvelleCampagne.getDescription() != null 
                    ? nouvelleCampagne.getDescription() 
                    : "Campagne: " + nouvelleCampagne.getNom());
                
                if (membre.getAssociationId() != null) {
                    nouvelleCot.setAssociationId(membre.getAssociationId());
                }
                
                cotisationService.creer(nouvelleCot);
                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(
                        "RAPPEL_COTISATION",
                        "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) {
                    LOGGER.warning("Impossible d'envoyer les notifications: " + e.getMessage());
                }
            }
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Campagne", 
                    "Campagne créée avec succès: " + cotisationsCreees + " cotisation(s)"));
            initializeNouvelleCampagne();
            chargerCotisations();
            chargerKPIs();
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de la création de la campagne: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de créer la campagne: " + e.getMessage()));
        }
    }
    
    /**
     * Envoie des relances groupées à tous les membres en retard
     */
    public void relancesGroupees() {
        try {
            LOGGER.info("Envoi de relances groupées");
            
            // Récupérer les cotisations en retard
            List<CotisationDTO> cotisationsEnRetard = cotisationService.obtenirEnRetard(0, 1000);
            
            if (cotisationsEnRetard.isEmpty()) {
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "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);
            
            notificationService.envoyerNotificationGroupe(
                "RAPPEL_COTISATION",
                "Rappel: Cotisation(s) en retard",
                "Vous avez des cotisations en retard. Montant total dû: " + formatMontant(montantTotalRetard) 
                    + ". Veuillez régulariser votre situation.",
                destinataires
            );
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Relances", 
                    destinataires.size() + " relance(s) envoyée(s)"));
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'envoi des relances: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'envoyer les relances: " + e.getMessage()));
        }
    }
    
    /**
     * Exporte toutes les cotisations en CSV
     */
    public void exporterTout() {
        try {
            LOGGER.info("Export global des cotisations");
            
            byte[] csvData = exportService.exporterCotisationsCSV(
                filtres.getStatut(),
                filtres.getType(),
                filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty() 
                    ? UUID.fromString(filtres.getOrganisation()) : null
            );
            
            telechargerFichier(csvData, "cotisations-export.csv", "text/csv");
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Export", 
                    "Export terminé avec succès"));
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'export: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'exporter les cotisations: " + e.getMessage()));
        }
    }
    
    /**
     * Applique les filtres en utilisant la recherche backend
     */
    public void appliquerFiltres() {
        try {
            // Trouver l'ID de l'organisation
            UUID associationId = null;
            if (filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty()) {
                for (Organisation org : listeOrganisations) {
                    if (org.getId().toString().equals(filtres.getOrganisation())) {
                        associationId = org.getId();
                        break;
                    }
                }
            }
            
            final UUID associationIdFinal = associationId; // 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 (associationIdFinal != null) {
                cotisationsFiltrees = cotisationsFiltrees.stream()
                    .filter(c -> c.getAssociationId() != null && c.getAssociationId().equals(associationIdFinal))
                    .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());
            }
            
            LOGGER.info("Filtres appliqués: " + cotisationsFiltrees.size() + " cotisation(s) trouvée(s)");
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'application des filtres: " + e.getMessage());
            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 {
            LOGGER.info("Export Excel de " + cotisationsFiltrees.size() + " cotisations");
            
            if (cotisationsFiltrees.isEmpty()) {
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                        "Aucune cotisation à exporter"));
                return;
            }
            
            List<UUID> ids = cotisationsFiltrees.stream()
                .map(CotisationDTO::getId)
                .filter(id -> id != null)
                .collect(Collectors.toList());
            
            byte[] csvData = exportService.exporterCotisationsSelectionneesCSV(ids);
            
            telechargerFichier(csvData, "cotisations-filtrees.csv", "text/csv");
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Export", 
                    "Export de " + cotisationsFiltrees.size() + " cotisation(s) terminé"));
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'export Excel: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'exporter: " + e.getMessage()));
        }
    }
    
    // Actions sur cotisations individuelles
    
    /**
     * Enregistre un paiement pour une cotisation via le backend
     */
    public void enregistrerPaiement(CotisationDTO cotisation) {
        if (cotisation == null) {
            return;
        }
        
        try {
            cotisation.setStatut("PAYEE");
            cotisation.setMontantPaye(cotisation.getMontantDu());
            cotisation.setDatePaiement(LocalDateTime.now());
            
            cotisationService.modifier(cotisation.getId(), cotisation);
            
            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", 
                    "Paiement enregistré"));
            LOGGER.info("Paiement enregistré pour: " + cotisation.getNumeroMembre());
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'enregistrement du paiement: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'enregistrer le paiement: " + e.getMessage()));
        }
    }
    
    /**
     * Génère un reçu pour une cotisation
     */
    public void genererRecu(CotisationDTO cotisation) {
        if (cotisation == null || cotisation.getId() == null) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Cotisation invalide"));
            return;
        }
        
        try {
            LOGGER.info("Génération reçu pour: " + cotisation.getNumeroMembre());
            
            byte[] recu = exportService.genererRecu(cotisation.getId());
            
            telechargerFichier(recu, "recu-" + cotisation.getNumeroReference() + ".txt", "text/plain");
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Reçu", 
                    "Reçu généré pour " + cotisation.getNomMembre()));
        } catch (Exception e) {
            LOGGER.severe("Erreur génération reçu: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de générer le reçu: " + e.getMessage()));
        }
    }
    
    /**
     * Envoie un rappel pour une cotisation
     */
    public void envoyerRappel(CotisationDTO cotisation) {
        if (cotisation == null || cotisation.getMembreId() == null) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Cotisation invalide"));
            return;
        }
        
        try {
            LOGGER.info("Envoi rappel pour: " + cotisation.getNumeroMembre());
            
            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"));
            }
            
            notificationService.envoyerNotificationGroupe(
                "RAPPEL_COTISATION",
                "Rappel de cotisation",
                message,
                List.of(cotisation.getMembreId().toString())
            );
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Rappel", 
                    "Rappel envoyé à " + cotisation.getNomMembre()));
        } catch (Exception e) {
            LOGGER.severe("Erreur envoi rappel: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'envoyer le rappel: " + e.getMessage()));
        }
    }
    
    /**
     * Affiche les détails d'une cotisation
     */
    public void voirDetails(CotisationDTO cotisation) {
        // Navigation vers la page de détails
        LOGGER.info("Affichage détails pour: " + cotisation.getNumeroMembre());
    }
    
    // Actions groupées
    
    /**
     * Marque plusieurs cotisations comme payées via le backend
     */
    public void marquerPayeesGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Aucune cotisation sélectionnée"));
            return;
        }
        
        try {
            int compteur = 0;
            for (CotisationDTO cotisation : cotisationsSelectionnees) {
                cotisation.setStatut("PAYEE");
                cotisation.setMontantPaye(cotisation.getMontantDu());
                cotisation.setDatePaiement(LocalDateTime.now());
                cotisationService.modifier(cotisation.getId(), cotisation);
                compteur++;
            }
            
            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();
            cotisationsSelectionnees.clear();
            calculerMontantTotalSelectionne();
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", 
                    compteur + " cotisation(s) marquée(s) comme payée(s)"));
            LOGGER.info("Marquage " + compteur + " cotisations comme payées");
        } catch (Exception e) {
            LOGGER.severe("Erreur lors du marquage groupé: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de marquer les cotisations: " + e.getMessage()));
        }
    }
    
    /**
     * Envoie des relances groupées pour les cotisations sélectionnées
     */
    public void envoyerRelancesGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Aucune cotisation sélectionnée"));
            return;
        }
        
        try {
            LOGGER.info("Envoi relances pour " + cotisationsSelectionnees.size() + " cotisations");
            
            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);
            
            notificationService.envoyerNotificationGroupe(
                "RAPPEL_COTISATION",
                "Rappel de paiement",
                "Vous avez des cotisations en attente de paiement. Montant: " + formatMontant(montantTotal),
                destinataires
            );
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Relances", 
                    destinataires.size() + " relance(s) envoyée(s)"));
        } catch (Exception e) {
            LOGGER.severe("Erreur envoi relances groupées: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'envoyer les relances: " + e.getMessage()));
        }
    }
    
    /**
     * Génère des reçus groupés pour les cotisations sélectionnées
     */
    public void genererRecusGroupes() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Aucune cotisation sélectionnée"));
            return;
        }
        
        try {
            LOGGER.info("Génération reçus pour " + cotisationsSelectionnees.size() + " cotisations");
            
            List<UUID> ids = cotisationsSelectionnees.stream()
                .map(CotisationDTO::getId)
                .filter(id -> id != null)
                .collect(Collectors.toList());
            
            byte[] recus = exportService.genererRecusGroupes(ids);
            
            telechargerFichier(recus, "recus-groupes.txt", "text/plain");
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Reçus", 
                    cotisationsSelectionnees.size() + " reçu(s) généré(s)"));
        } catch (Exception e) {
            LOGGER.severe("Erreur génération reçus groupés: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de générer les reçus: " + e.getMessage()));
        }
    }
    
    /**
     * Annule plusieurs cotisations via le backend
     */
    public void annulerCotisationsGroupees() {
        if (cotisationsSelectionnees == null || cotisationsSelectionnees.isEmpty()) {
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", 
                    "Aucune cotisation sélectionnée"));
            return;
        }
        
        try {
            int compteur = 0;
            for (CotisationDTO cotisation : cotisationsSelectionnees) {
                // Vérifier que la cotisation peut être annulée (pas payée)
                if (!"PAYEE".equals(cotisation.getStatut())) {
                    cotisationService.supprimer(cotisation.getId());
                    compteur++;
                }
            }
            
            chargerCotisations();
            chargerKPIs();
            appliquerFiltres();
            cotisationsSelectionnees.clear();
            calculerMontantTotalSelectionne();
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", 
                    compteur + " cotisation(s) annulée(s)"));
            LOGGER.info("Annulation " + compteur + " cotisations");
        } catch (Exception e) {
            LOGGER.severe("Erreur lors de l'annulation groupée: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible d'annuler les cotisations: " + e.getMessage()));
        }
    }
    
    // Wave Money
    
    /**
     * Lance les prélèvements Wave Money pour les cotisations en attente
     */
    public void lancerPrelevements() {
        try {
            LOGGER.info("Lancement des prélèvements Wave Money");
            
            // Récupérer les cotisations en attente avec Wave comme méthode
            List<CotisationDTO> 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()) {
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Info", 
                        "Aucune cotisation Wave en attente"));
                return;
            }
            
            int prelevementsLances = 0;
            for (CotisationDTO 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 = waveService.creerSessionPaiement(
                        cotisation.getMontantDu(),
                        "XOF",
                        successUrl,
                        errorUrl,
                        cotisation.getNumeroReference(),
                        "Cotisation: " + cotisation.getTypeCotisation(),
                        cotisation.getAssociationId(),
                        cotisation.getMembreId()
                    );
                    
                    if (session != null && session.getWaveSessionId() != null) {
                        // Mettre à jour la cotisation avec l'ID de session Wave
                        cotisation.setWaveSessionId(session.getWaveSessionId());
                        cotisation.setMethodePaiement("WAVE_MONEY");
                        cotisationService.modifier(cotisation.getId(), cotisation);
                        prelevementsLances++;
                    }
                } catch (Exception e) {
                    LOGGER.warning("Erreur prélèvement Wave pour " + cotisation.getNumeroReference() + ": " + e.getMessage());
                }
            }
            
            chargerCotisations();
            chargerKPIs();
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Wave", 
                    prelevementsLances + " prélèvement(s) Wave Money lancé(s)"));
        } catch (Exception e) {
            LOGGER.severe("Erreur lancement prélèvements Wave: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de lancer les prélèvements: " + e.getMessage()));
        }
    }
    
    /**
     * Teste la connexion à l'API Wave Money
     */
    public void testerAPIWave() {
        try {
            LOGGER.info("Test de l'API Wave Money");
            
            Map<String, Object> result = waveService.testerConnexion();
            
            if (result != null && Boolean.TRUE.equals(result.get("success"))) {
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Wave", 
                        "Connexion Wave API réussie"));
            } else {
                String message = result != null ? String.valueOf(result.get("message")) : "Erreur inconnue";
                FacesContext.getCurrentInstance().addMessage(null, 
                    new FacesMessage(FacesMessage.SEVERITY_WARN, "Wave", 
                        "Test Wave: " + message));
            }
        } catch (Exception e) {
            LOGGER.severe("Erreur test API Wave: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de tester l'API Wave: " + e.getMessage()));
        }
    }
    
    /**
     * Affiche l'historique des prélèvements Wave
     */
    public void voirHistoriquePrelevements() {
        try {
            LOGGER.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());
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Historique", 
                    cotisationsFiltrees.size() + " paiement(s) Wave Money trouvé(s)"));
        } catch (Exception e) {
            LOGGER.severe("Erreur historique prélèvements: " + e.getMessage());
        }
    }
    
    // Actions rapides
    
    /**
     * Génère un rapport mensuel des cotisations
     */
    public void genererRapportMensuel() {
        try {
            LOGGER.info("Génération rapport mensuel");
            
            int annee = LocalDate.now().getYear();
            int mois = LocalDate.now().getMonthValue();
            
            UUID associationId = null;
            if (filtres.getOrganisation() != null && !filtres.getOrganisation().isEmpty()) {
                try {
                    associationId = UUID.fromString(filtres.getOrganisation());
                } catch (IllegalArgumentException e) {
                    // Ignorer si pas un UUID valide
                }
            }
            
            byte[] rapport = exportService.genererRapportMensuel(annee, mois, associationId);
            
            telechargerFichier(rapport, "rapport-mensuel-" + annee + "-" + String.format("%02d", mois) + ".txt", "text/plain");
            
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_INFO, "Rapport", 
                    "Rapport mensuel généré"));
        } catch (Exception e) {
            LOGGER.severe("Erreur génération rapport: " + e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", 
                    "Impossible de générer le rapport: " + e.getMessage()));
        }
    }
    
    /**
     * Configure les relances automatiques
     */
    public void configurerRelancesAuto() {
        LOGGER.info("Configuration relances automatiques");
        // Navigation vers la page de configuration
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_INFO, "Configuration", 
                "Ouvrez les paramètres d'administration pour configurer les relances automatiques"));
    }
    
    /**
     * Gère les types de cotisations
     */
    public void gererTypesCotisations() {
        LOGGER.info("Gestion des types de cotisations");
        // Navigation vers la page de gestion des types
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_INFO, "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 {
            FacesContext fc = 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) {
            LOGGER.severe("Erreur téléchargement fichier: " + e.getMessage());
            throw new RuntimeException("Erreur lors du téléchargement", e);
        }
    }
    
    /**
     * Retourne au tableau de bord
     */
    public String tableauDeBord() {
        return "/pages/secure/dashboard?faces-redirect=true";
    }
    
    /**
     * Actualise toutes les données depuis le backend
     */
    public void actualiser() {
        chargerKPIs();
        chargerCotisations();
        chargerTopOrganisations();
        chargerRepartitionMethodes();
        appliquerFiltres();
        FacesContext.getCurrentInstance().addMessage(null, 
            new FacesMessage(FacesMessage.SEVERITY_INFO, "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 String getCroissanceAnnuelle() { return croissanceAnnuelle; }
    public void setCroissanceAnnuelle(String 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<CotisationDTO> getCotisationsFiltrees() { return cotisationsFiltrees; }
    public void setCotisationsFiltrees(List<CotisationDTO> cotisationsFiltrees) { this.cotisationsFiltrees = cotisationsFiltrees; }
    
    public List<CotisationDTO> getCotisationsSelectionnees() { return cotisationsSelectionnees; }
    public void setCotisationsSelectionnees(List<CotisationDTO> 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(CotisationDTO::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 CotisationDTO
     */
    public String getInitialesMembre(CotisationDTO cotisation) {
        if (cotisation == null || cotisation.getNomMembre() == null) {
            return "??";
        }
        return getInitiales(cotisation.getNomMembre());
    }
}
