package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Cotisation;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.TypedQuery;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * Repository pour la gestion des cotisations avec UUID
 *
 * @author UnionFlow Team
 * @version 2.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class CotisationRepository extends BaseRepository<Cotisation> {

    public CotisationRepository() {
        super(Cotisation.class);
    }

    /**
     * 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) {
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.numeroReference = :numeroReference",
            Cotisation.class);
        query.setParameter("numeroReference", numeroReference);
        return query.getResultStream().findFirst();
    }

    /**
     * Trouve toutes les cotisations d'un membre
     *
     * @param membreId l'UUID du membre
     * @param page pagination
     * @param sort tri
     * @return liste paginée des cotisations
     */
    public List<Cotisation> findByMembreId(UUID membreId, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.membre.id = :membreId" + orderBy,
            Cotisation.class);
        query.setParameter("membreId", membreId);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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) {
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.statut = :statut ORDER BY c.dateEcheance DESC",
            Cotisation.class);
        query.setParameter("statut", statut);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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) {
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.dateEcheance < :dateReference AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' ORDER BY c.dateEcheance ASC",
            Cotisation.class);
        query.setParameter("dateReference", dateReference);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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) {
        TypedQuery<Cotisation> query;
        if (mois != null) {
            query = entityManager.createQuery(
                "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois ORDER BY c.dateEcheance DESC",
                Cotisation.class);
            query.setParameter("annee", annee);
            query.setParameter("mois", mois);
        } else {
            query = entityManager.createQuery(
                "SELECT c FROM Cotisation c WHERE c.annee = :annee ORDER BY c.mois DESC, c.dateEcheance DESC",
                Cotisation.class);
            query.setParameter("annee", annee);
        }
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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) {
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.typeCotisation = :typeCotisation ORDER BY c.dateEcheance DESC",
            Cotisation.class);
        query.setParameter("typeCotisation", typeCotisation);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Recherche avancée avec filtres multiples
     *
     * @param membreId UUID 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(
        UUID membreId, String statut, String typeCotisation, Integer annee, Integer mois, Page page) {
        StringBuilder jpql = new StringBuilder("SELECT c FROM Cotisation c WHERE 1=1");
        Map<String, Object> params = new HashMap<>();

        if (membreId != null) {
            jpql.append(" AND c.membre.id = :membreId");
            params.put("membreId", membreId);
        }

        if (statut != null && !statut.isEmpty()) {
            jpql.append(" AND c.statut = :statut");
            params.put("statut", statut);
        }

        if (typeCotisation != null && !typeCotisation.isEmpty()) {
            jpql.append(" AND c.typeCotisation = :typeCotisation");
            params.put("typeCotisation", typeCotisation);
        }

        if (annee != null) {
            jpql.append(" AND c.annee = :annee");
            params.put("annee", annee);
        }

        if (mois != null) {
            jpql.append(" AND c.mois = :mois");
            params.put("mois", mois);
        }

        jpql.append(" ORDER BY c.dateEcheance DESC");

        TypedQuery<Cotisation> query = entityManager.createQuery(jpql.toString(), Cotisation.class);
        for (Map.Entry<String, Object> param : params.entrySet()) {
            query.setParameter(param.getKey(), param.getValue());
        }
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Calcule le total des montants dus pour un membre
     *
     * @param membreId UUID du membre
     * @return montant total dû
     */
    public BigDecimal calculerTotalMontantDu(UUID membreId) {
        TypedQuery<BigDecimal> query = entityManager.createQuery(
            "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
            BigDecimal.class);
        query.setParameter("membreId", membreId);
        BigDecimal result = query.getSingleResult();
        return result != null ? result : BigDecimal.ZERO;
    }

    /**
     * Calcule le total des montants payés pour un membre
     *
     * @param membreId UUID du membre
     * @return montant total payé
     */
    public BigDecimal calculerTotalMontantPaye(UUID membreId) {
        TypedQuery<BigDecimal> query = entityManager.createQuery(
            "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.id = :membreId",
            BigDecimal.class);
        query.setParameter("membreId", membreId);
        BigDecimal result = query.getSingleResult();
        return result != null ? result : BigDecimal.ZERO;
    }

    /**
     * Compte les cotisations par statut
     *
     * @param statut le statut
     * @return nombre de cotisations
     */
    public long compterParStatut(String statut) {
        TypedQuery<Long> query = entityManager.createQuery(
            "SELECT COUNT(c) FROM Cotisation c WHERE c.statut = :statut", Long.class);
        query.setParameter("statut", statut);
        return query.getSingleResult();
    }

    /**
     * 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);
        TypedQuery<Cotisation> query = entityManager.createQuery(
            "SELECT c FROM Cotisation c WHERE c.dateEcheance <= :dateRappel AND c.statut != 'PAYEE' AND c.statut != 'ANNULEE' AND c.nombreRappels < :nombreMaxRappels ORDER BY c.dateEcheance ASC",
            Cotisation.class);
        query.setParameter("dateRappel", dateRappel);
        query.setParameter("nombreMaxRappels", nombreMaxRappels);
        return query.getResultList();
    }

    /**
     * Met à jour le nombre de rappels pour une cotisation
     *
     * @param cotisationId UUID de la cotisation
     * @return true si mise à jour réussie
     */
    public boolean incrementerNombreRappels(UUID cotisationId) {
        Cotisation cotisation = findById(cotisationId);
        if (cotisation != null) {
            cotisation.setNombreRappels(
                cotisation.getNombreRappels() != null ? cotisation.getNombreRappels() + 1 : 1);
            cotisation.setDateDernierRappel(LocalDateTime.now());
            update(cotisation);
            return true;
        }
        return false;
    }

    /**
     * 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
            ? "SELECT c FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois"
            : "SELECT c FROM Cotisation c WHERE c.annee = :annee";

        TypedQuery<Long> countQuery;
        TypedQuery<BigDecimal> montantTotalQuery;
        TypedQuery<BigDecimal> montantPayeQuery;
        TypedQuery<Long> payeesQuery;

        if (mois != null) {
            countQuery = entityManager.createQuery(
                "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
                Long.class);
            montantTotalQuery = entityManager.createQuery(
                "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
                BigDecimal.class);
            montantPayeQuery = entityManager.createQuery(
                "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois",
                BigDecimal.class);
            payeesQuery = entityManager.createQuery(
                "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.mois = :mois AND c.statut = 'PAYEE'",
                Long.class);

            countQuery.setParameter("annee", annee);
            countQuery.setParameter("mois", mois);
            montantTotalQuery.setParameter("annee", annee);
            montantTotalQuery.setParameter("mois", mois);
            montantPayeQuery.setParameter("annee", annee);
            montantPayeQuery.setParameter("mois", mois);
            payeesQuery.setParameter("annee", annee);
            payeesQuery.setParameter("mois", mois);
        } else {
            countQuery = entityManager.createQuery(
                "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee", Long.class);
            montantTotalQuery = entityManager.createQuery(
                "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.annee = :annee",
                BigDecimal.class);
            montantPayeQuery = entityManager.createQuery(
                "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.annee = :annee",
                BigDecimal.class);
            payeesQuery = entityManager.createQuery(
                "SELECT COUNT(c) FROM Cotisation c WHERE c.annee = :annee AND c.statut = 'PAYEE'",
                Long.class);

            countQuery.setParameter("annee", annee);
            montantTotalQuery.setParameter("annee", annee);
            montantPayeQuery.setParameter("annee", annee);
            payeesQuery.setParameter("annee", annee);
        }

        Long totalCotisations = countQuery.getSingleResult();
        BigDecimal montantTotal = montantTotalQuery.getSingleResult();
        BigDecimal montantPaye = montantPayeQuery.getSingleResult();
        Long cotisationsPayees = payeesQuery.getSingleResult();

        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(
        UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        TypedQuery<BigDecimal> query = entityManager.createQuery(
            "SELECT COALESCE(SUM(c.montantPaye), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'PAYEE' AND c.datePaiement BETWEEN :debut AND :fin",
            BigDecimal.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("debut", debut);
        query.setParameter("fin", fin);
        BigDecimal result = query.getSingleResult();
        return result != null ? result : BigDecimal.ZERO;
    }

    /** Somme des montants en attente dans une période */
    public BigDecimal sumMontantsEnAttente(
        UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        TypedQuery<BigDecimal> query = entityManager.createQuery(
            "SELECT COALESCE(SUM(c.montantDu), 0) FROM Cotisation c WHERE c.membre.organisation.id = :organisationId AND c.statut = 'EN_ATTENTE' AND c.dateCreation BETWEEN :debut AND :fin",
            BigDecimal.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("debut", debut);
        query.setParameter("fin", fin);
        BigDecimal result = query.getSingleResult();
        return result != null ? result : BigDecimal.ZERO;
    }

    /** Construit la clause ORDER BY à partir d'un Sort */
    private String buildOrderBy(Sort sort) {
        if (sort == null || sort.getColumns().isEmpty()) {
            return "c.dateEcheance DESC";
        }
        StringBuilder orderBy = new StringBuilder();
        for (int i = 0; i < sort.getColumns().size(); i++) {
            if (i > 0) {
                orderBy.append(", ");
            }
            Sort.Column column = sort.getColumns().get(i);
            orderBy.append("c.").append(column.getName());
            if (column.getDirection() == Sort.Direction.Descending) {
                orderBy.append(" DESC");
            } else {
                orderBy.append(" ASC");
            }
        }
        return orderBy.toString();
    }
}
