package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Cotisation;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Repository pour la gestion des cotisations
 * Utilise Panache pour simplifier les opérations JPA
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@ApplicationScoped
public class CotisationRepository implements PanacheRepository<Cotisation> {

    /**
     * Trouve une cotisation par son numéro de référence
     * 
     * @param numeroReference le numéro de référence unique
     * @return Optional contenant la cotisation si trouvée
     */
    public Optional<Cotisation> findByNumeroReference(String numeroReference) {
        return find("numeroReference = ?1", numeroReference).firstResultOptional();
    }

    /**
     * Trouve toutes les cotisations d'un membre
     * 
     * @param membreId l'identifiant du membre
     * @param page pagination
     * @param sort tri
     * @return liste paginée des cotisations
     */
    public List<Cotisation> findByMembreId(Long membreId, Page page, Sort sort) {
        return find("membre.id = ?1", membreId)
                .page(page)
                .list();
    }

    /**
     * Trouve les cotisations par statut
     * 
     * @param statut le statut recherché
     * @param page pagination
     * @return liste paginée des cotisations
     */
    public List<Cotisation> findByStatut(String statut, Page page) {
        return find("statut = ?1", Sort.by("dateEcheance").descending(), statut)
                .page(page)
                .list();
    }

    /**
     * Trouve les cotisations en retard
     * 
     * @param dateReference date de référence (généralement aujourd'hui)
     * @param page pagination
     * @return liste des cotisations en retard
     */
    public List<Cotisation> findCotisationsEnRetard(LocalDate dateReference, Page page) {
        return find("dateEcheance < ?1 and statut != 'PAYEE' and statut != 'ANNULEE'", 
                   Sort.by("dateEcheance").ascending(), dateReference)
                .page(page)
                .list();
    }

    /**
     * Trouve les cotisations par période (année/mois)
     * 
     * @param annee l'année
     * @param mois le mois (optionnel)
     * @param page pagination
     * @return liste des cotisations de la période
     */
    public List<Cotisation> findByPeriode(Integer annee, Integer mois, Page page) {
        if (mois != null) {
            return find("annee = ?1 and mois = ?2", Sort.by("dateEcheance").descending(), annee, mois)
                    .page(page)
                    .list();
        } else {
            return find("annee = ?1", Sort.by("mois", "dateEcheance").descending(), annee)
                    .page(page)
                    .list();
        }
    }

    /**
     * Trouve les cotisations par type
     * 
     * @param typeCotisation le type de cotisation
     * @param page pagination
     * @return liste des cotisations du type spécifié
     */
    public List<Cotisation> findByType(String typeCotisation, Page page) {
        return find("typeCotisation = ?1", Sort.by("dateEcheance").descending(), typeCotisation)
                .page(page)
                .list();
    }

    /**
     * Recherche avancée avec filtres multiples
     * 
     * @param membreId identifiant du membre (optionnel)
     * @param statut statut (optionnel)
     * @param typeCotisation type (optionnel)
     * @param annee année (optionnel)
     * @param mois mois (optionnel)
     * @param page pagination
     * @return liste filtrée des cotisations
     */
    public List<Cotisation> rechercheAvancee(Long membreId, String statut, String typeCotisation, 
                                           Integer annee, Integer mois, Page page) {
        StringBuilder query = new StringBuilder("1=1");
        Map<String, Object> params = new java.util.HashMap<>();
        
        if (membreId != null) {
            query.append(" and membre.id = :membreId");
            params.put("membreId", membreId);
        }
        
        if (statut != null && !statut.isEmpty()) {
            query.append(" and statut = :statut");
            params.put("statut", statut);
        }
        
        if (typeCotisation != null && !typeCotisation.isEmpty()) {
            query.append(" and typeCotisation = :typeCotisation");
            params.put("typeCotisation", typeCotisation);
        }
        
        if (annee != null) {
            query.append(" and annee = :annee");
            params.put("annee", annee);
        }
        
        if (mois != null) {
            query.append(" and mois = :mois");
            params.put("mois", mois);
        }
        
        return find(query.toString(), Sort.by("dateEcheance").descending(), params)
                .page(page)
                .list();
    }

    /**
     * Calcule le total des montants dus pour un membre
     * 
     * @param membreId identifiant du membre
     * @return montant total dû
     */
    public BigDecimal calculerTotalMontantDu(Long membreId) {
        return find("select sum(c.montantDu) from Cotisation c where c.membre.id = ?1", membreId)
                .project(BigDecimal.class)
                .firstResult();
    }

    /**
     * Calcule le total des montants payés pour un membre
     * 
     * @param membreId identifiant du membre
     * @return montant total payé
     */
    public BigDecimal calculerTotalMontantPaye(Long membreId) {
        return find("select sum(c.montantPaye) from Cotisation c where c.membre.id = ?1", membreId)
                .project(BigDecimal.class)
                .firstResult();
    }

    /**
     * Compte les cotisations par statut
     * 
     * @param statut le statut
     * @return nombre de cotisations
     */
    public long compterParStatut(String statut) {
        return count("statut = ?1", statut);
    }

    /**
     * Trouve les cotisations nécessitant un rappel
     * 
     * @param joursAvantEcheance nombre de jours avant échéance
     * @param nombreMaxRappels nombre maximum de rappels déjà envoyés
     * @return liste des cotisations à rappeler
     */
    public List<Cotisation> findCotisationsAuRappel(int joursAvantEcheance, int nombreMaxRappels) {
        LocalDate dateRappel = LocalDate.now().plusDays(joursAvantEcheance);
        return find("dateEcheance <= ?1 and statut != 'PAYEE' and statut != 'ANNULEE' and nombreRappels < ?2", 
                   Sort.by("dateEcheance").ascending(), dateRappel, nombreMaxRappels)
                .list();
    }

    /**
     * Met à jour le nombre de rappels pour une cotisation
     * 
     * @param cotisationId identifiant de la cotisation
     * @return nombre de lignes mises à jour
     */
    public int incrementerNombreRappels(Long cotisationId) {
        return update("nombreRappels = nombreRappels + 1, dateDernierRappel = ?1 where id = ?2", 
                     LocalDateTime.now(), cotisationId);
    }

    /**
     * Statistiques des cotisations par période
     * 
     * @param annee l'année
     * @param mois le mois (optionnel)
     * @return map avec les statistiques
     */
    public Map<String, Object> getStatistiquesPeriode(Integer annee, Integer mois) {
        String baseQuery = mois != null ? 
            "from Cotisation c where c.annee = ?1 and c.mois = ?2" :
            "from Cotisation c where c.annee = ?1";
        
        Object[] params = mois != null ? new Object[]{annee, mois} : new Object[]{annee};
        
        Long totalCotisations = mois != null ?
            count("annee = ?1 and mois = ?2", params) :
            count("annee = ?1", params);
            
        BigDecimal montantTotal = find("select sum(c.montantDu) " + baseQuery, params)
                .project(BigDecimal.class)
                .firstResult();
                
        BigDecimal montantPaye = find("select sum(c.montantPaye) " + baseQuery, params)
                .project(BigDecimal.class)
                .firstResult();
        
        Long cotisationsPayees = mois != null ?
            count("annee = ?1 and mois = ?2 and statut = 'PAYEE'", annee, mois) :
            count("annee = ?1 and statut = 'PAYEE'", annee);
        
        return Map.of(
            "totalCotisations", totalCotisations != null ? totalCotisations : 0L,
            "montantTotal", montantTotal != null ? montantTotal : BigDecimal.ZERO,
            "montantPaye", montantPaye != null ? montantPaye : BigDecimal.ZERO,
            "cotisationsPayees", cotisationsPayees != null ? cotisationsPayees : 0L,
            "tauxPaiement", totalCotisations != null && totalCotisations > 0 ? 
                (cotisationsPayees != null ? cotisationsPayees : 0L) * 100.0 / totalCotisations : 0.0
        );
    }

    /**
     * Somme des montants payés dans une période
     */
    public BigDecimal sumMontantsPayes(java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        return find("SELECT COALESCE(SUM(c.montant), 0) FROM Cotisation c WHERE c.organisation.id = ?1 and c.statut = 'PAYEE' and c.datePaiement between ?2 and ?3",
                   organisationId, debut, fin)
               .project(BigDecimal.class)
               .firstResult();
    }

    /**
     * Somme des montants en attente dans une période
     */
    public BigDecimal sumMontantsEnAttente(java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        return find("SELECT COALESCE(SUM(c.montant), 0) FROM Cotisation c WHERE c.organisation.id = ?1 and c.statut = 'EN_ATTENTE' and c.dateCreation between ?2 and ?3",
                   organisationId, debut, fin)
               .project(BigDecimal.class)
               .firstResult();
    }
}
