package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
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.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Repository pour l'entité Événement
 * 
 * Fournit les méthodes d'accès aux données pour la gestion des événements
 * avec des fonctionnalités de recherche avancées et de filtrage.
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@ApplicationScoped
public class EvenementRepository implements PanacheRepository<Evenement> {

    /**
     * Trouve un événement par son titre (recherche exacte)
     * 
     * @param titre le titre de l'événement
     * @return l'événement trouvé ou Optional.empty()
     */
    public Optional<Evenement> findByTitre(String titre) {
        return find("titre", titre).firstResultOptional();
    }

    /**
     * Trouve tous les événements actifs
     * 
     * @return la liste des événements actifs
     */
    public List<Evenement> findAllActifs() {
        return find("actif", true).list();
    }

    /**
     * Trouve tous les événements actifs avec pagination et tri
     * 
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements actifs
     */
    public List<Evenement> findAllActifs(Page page, Sort sort) {
        return find("actif", sort, true).page(page).list();
    }

    /**
     * Compte le nombre d'événements actifs
     * 
     * @return le nombre d'événements actifs
     */
    public long countActifs() {
        return count("actif", true);
    }

    /**
     * Trouve les événements par statut
     * 
     * @param statut le statut recherché
     * @return la liste des événements avec ce statut
     */
    public List<Evenement> findByStatut(StatutEvenement statut) {
        return find("statut", statut).list();
    }

    /**
     * Trouve les événements par statut avec pagination et tri
     * 
     * @param statut le statut recherché
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements avec ce statut
     */
    public List<Evenement> findByStatut(StatutEvenement statut, Page page, Sort sort) {
        return find("statut", sort, statut).page(page).list();
    }

    /**
     * Trouve les événements par type
     * 
     * @param type le type d'événement recherché
     * @return la liste des événements de ce type
     */
    public List<Evenement> findByType(TypeEvenement type) {
        return find("typeEvenement", type).list();
    }

    /**
     * Trouve les événements par type avec pagination et tri
     * 
     * @param type le type d'événement recherché
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements de ce type
     */
    public List<Evenement> findByType(TypeEvenement type, Page page, Sort sort) {
        return find("typeEvenement", sort, type).page(page).list();
    }

    /**
     * Trouve les événements par organisation
     * 
     * @param organisationId l'ID de l'organisation
     * @return la liste des événements de cette organisation
     */
    public List<Evenement> findByOrganisation(Long organisationId) {
        return find("organisation.id", organisationId).list();
    }

    /**
     * Trouve les événements par organisation avec pagination et tri
     * 
     * @param organisationId l'ID de l'organisation
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements de cette organisation
     */
    public List<Evenement> findByOrganisation(Long organisationId, Page page, Sort sort) {
        return find("organisation.id", sort, organisationId).page(page).list();
    }

    /**
     * Trouve les événements par organisateur
     * 
     * @param organisateurId l'ID de l'organisateur
     * @return la liste des événements organisés par ce membre
     */
    public List<Evenement> findByOrganisateur(Long organisateurId) {
        return find("organisateur.id", organisateurId).list();
    }

    /**
     * Trouve les événements dans une période donnée
     * 
     * @param dateDebut la date de début de la période
     * @param dateFin la date de fin de la période
     * @return la liste des événements dans cette période
     */
    public List<Evenement> findByPeriode(LocalDateTime dateDebut, LocalDateTime dateFin) {
        return find("dateDebut >= ?1 and dateDebut <= ?2", dateDebut, dateFin).list();
    }

    /**
     * Trouve les événements dans une période donnée avec pagination et tri
     * 
     * @param dateDebut la date de début de la période
     * @param dateFin la date de fin de la période
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements dans cette période
     */
    public List<Evenement> findByPeriode(LocalDateTime dateDebut, LocalDateTime dateFin, 
                                       Page page, Sort sort) {
        return find("dateDebut >= ?1 and dateDebut <= ?2", sort, dateDebut, dateFin)
                .page(page).list();
    }

    /**
     * Trouve les événements à venir (date de début future)
     * 
     * @return la liste des événements à venir
     */
    public List<Evenement> findEvenementsAVenir() {
        return find("dateDebut > ?1 and actif = true", LocalDateTime.now()).list();
    }

    /**
     * Trouve les événements à venir avec pagination et tri
     * 
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements à venir
     */
    public List<Evenement> findEvenementsAVenir(Page page, Sort sort) {
        return find("dateDebut > ?1 and actif = true", sort, LocalDateTime.now())
                .page(page).list();
    }

    /**
     * Trouve les événements en cours
     * 
     * @return la liste des événements en cours
     */
    public List<Evenement> findEvenementsEnCours() {
        LocalDateTime maintenant = LocalDateTime.now();
        return find("dateDebut <= ?1 and (dateFin is null or dateFin >= ?1) and actif = true", 
                   maintenant).list();
    }

    /**
     * Trouve les événements passés
     * 
     * @return la liste des événements passés
     */
    public List<Evenement> findEvenementsPasses() {
        return find("(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", 
                   LocalDateTime.now()).list();
    }

    /**
     * Trouve les événements passés avec pagination et tri
     * 
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements passés
     */
    public List<Evenement> findEvenementsPasses(Page page, Sort sort) {
        return find("(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", 
                   sort, LocalDateTime.now()).page(page).list();
    }

    /**
     * Trouve les événements visibles au public
     * 
     * @return la liste des événements publics
     */
    public List<Evenement> findEvenementsPublics() {
        return find("visiblePublic = true and actif = true").list();
    }

    /**
     * Trouve les événements visibles au public avec pagination et tri
     * 
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements publics
     */
    public List<Evenement> findEvenementsPublics(Page page, Sort sort) {
        return find("visiblePublic = true and actif = true", sort).page(page).list();
    }

    /**
     * Trouve les événements ouverts aux inscriptions
     * 
     * @return la liste des événements ouverts aux inscriptions
     */
    public List<Evenement> findEvenementsOuvertsInscription() {
        LocalDateTime maintenant = LocalDateTime.now();
        return find("inscriptionRequise = true and actif = true and dateDebut > ?1 and " +
                   "(dateLimiteInscription is null or dateLimiteInscription > ?1) and " +
                   "(statut = 'PLANIFIE' or statut = 'CONFIRME')", maintenant).list();
    }

    /**
     * Recherche d'événements par titre ou description (recherche partielle)
     * 
     * @param recherche le terme de recherche
     * @return la liste des événements correspondants
     */
    public List<Evenement> findByTitreOrDescription(String recherche) {
        return find("lower(titre) like ?1 or lower(description) like ?1",
                   "%" + recherche.toLowerCase() + "%").list();
    }

    /**
     * Recherche d'événements par titre ou description avec pagination et tri
     * 
     * @param recherche le terme de recherche
     * @param page la page demandée
     * @param sort le tri à appliquer
     * @return la liste paginée des événements correspondants
     */
    public List<Evenement> findByTitreOrDescription(String recherche, Page page, Sort sort) {
        return find("lower(titre) like ?1 or lower(description) like ?1",
                   sort, "%" + recherche.toLowerCase() + "%").page(page).list();
    }

    /**
     * Compte les événements créés depuis une date donnée
     * 
     * @param depuis la date de référence
     * @return le nombre d'événements créés depuis cette date
     */
    public long countNouveauxEvenements(LocalDateTime depuis) {
        return count("dateCreation >= ?1", depuis);
    }

    /**
     * Trouve les événements nécessitant une inscription avec places disponibles
     *
     * @return la liste des événements avec places disponibles
     */
    public List<Evenement> findEvenementsAvecPlacesDisponibles() {
        LocalDateTime maintenant = LocalDateTime.now();
        return find("inscriptionRequise = true and actif = true and dateDebut > ?1 and " +
                   "(dateLimiteInscription is null or dateLimiteInscription > ?1) and " +
                   "(capaciteMax is null or " +
                   "(select count(i) from InscriptionEvenement i where i.evenement = this and i.statut = 'CONFIRMEE') < capaciteMax)",
                   maintenant).list();
    }

    /**
     * Recherche avancée d'événements avec filtres multiples
     *
     * @param recherche terme de recherche (titre, description)
     * @param statut statut de l'événement (optionnel)
     * @param type type d'événement (optionnel)
     * @param organisationId ID de l'organisation (optionnel)
     * @param organisateurId ID de l'organisateur (optionnel)
     * @param dateDebutMin date de début minimum (optionnel)
     * @param dateDebutMax date de début maximum (optionnel)
     * @param visiblePublic visibilité publique (optionnel)
     * @param inscriptionRequise inscription requise (optionnel)
     * @param actif statut actif (optionnel)
     * @param page pagination
     * @param sort tri
     * @return la liste paginée des événements correspondants aux critères
     */
    public List<Evenement> rechercheAvancee(String recherche,
                                           StatutEvenement statut,
                                           TypeEvenement type,
                                           Long organisationId,
                                           Long organisateurId,
                                           LocalDateTime dateDebutMin,
                                           LocalDateTime dateDebutMax,
                                           Boolean visiblePublic,
                                           Boolean inscriptionRequise,
                                           Boolean actif,
                                           Page page,
                                           Sort sort) {
        StringBuilder query = new StringBuilder("1=1");
        Map<String, Object> params = new java.util.HashMap<>();

        if (recherche != null && !recherche.trim().isEmpty()) {
            query.append(" and (lower(titre) like :recherche or lower(description) like :recherche or lower(lieu) like :recherche)");
            params.put("recherche", "%" + recherche.toLowerCase() + "%");
        }

        if (statut != null) {
            query.append(" and statut = :statut");
            params.put("statut", statut);
        }

        if (type != null) {
            query.append(" and typeEvenement = :type");
            params.put("type", type);
        }

        if (organisationId != null) {
            query.append(" and organisation.id = :organisationId");
            params.put("organisationId", organisationId);
        }

        if (organisateurId != null) {
            query.append(" and organisateur.id = :organisateurId");
            params.put("organisateurId", organisateurId);
        }

        if (dateDebutMin != null) {
            query.append(" and dateDebut >= :dateDebutMin");
            params.put("dateDebutMin", dateDebutMin);
        }

        if (dateDebutMax != null) {
            query.append(" and dateDebut <= :dateDebutMax");
            params.put("dateDebutMax", dateDebutMax);
        }

        if (visiblePublic != null) {
            query.append(" and visiblePublic = :visiblePublic");
            params.put("visiblePublic", visiblePublic);
        }

        if (inscriptionRequise != null) {
            query.append(" and inscriptionRequise = :inscriptionRequise");
            params.put("inscriptionRequise", inscriptionRequise);
        }

        if (actif != null) {
            query.append(" and actif = :actif");
            params.put("actif", actif);
        }

        return find(query.toString(), sort, params).page(page).list();
    }

    /**
     * Compte les résultats de la recherche avancée
     *
     * @param recherche terme de recherche (titre, description)
     * @param statut statut de l'événement (optionnel)
     * @param type type d'événement (optionnel)
     * @param organisationId ID de l'organisation (optionnel)
     * @param organisateurId ID de l'organisateur (optionnel)
     * @param dateDebutMin date de début minimum (optionnel)
     * @param dateDebutMax date de début maximum (optionnel)
     * @param visiblePublic visibilité publique (optionnel)
     * @param inscriptionRequise inscription requise (optionnel)
     * @param actif statut actif (optionnel)
     * @return le nombre d'événements correspondants aux critères
     */
    public long countRechercheAvancee(String recherche,
                                    StatutEvenement statut,
                                    TypeEvenement type,
                                    Long organisationId,
                                    Long organisateurId,
                                    LocalDateTime dateDebutMin,
                                    LocalDateTime dateDebutMax,
                                    Boolean visiblePublic,
                                    Boolean inscriptionRequise,
                                    Boolean actif) {
        StringBuilder query = new StringBuilder("1=1");
        Map<String, Object> params = new java.util.HashMap<>();

        if (recherche != null && !recherche.trim().isEmpty()) {
            query.append(" and (lower(titre) like :recherche or lower(description) like :recherche or lower(lieu) like :recherche)");
            params.put("recherche", "%" + recherche.toLowerCase() + "%");
        }

        if (statut != null) {
            query.append(" and statut = :statut");
            params.put("statut", statut);
        }

        if (type != null) {
            query.append(" and typeEvenement = :type");
            params.put("type", type);
        }

        if (organisationId != null) {
            query.append(" and organisation.id = :organisationId");
            params.put("organisationId", organisationId);
        }

        if (organisateurId != null) {
            query.append(" and organisateur.id = :organisateurId");
            params.put("organisateurId", organisateurId);
        }

        if (dateDebutMin != null) {
            query.append(" and dateDebut >= :dateDebutMin");
            params.put("dateDebutMin", dateDebutMin);
        }

        if (dateDebutMax != null) {
            query.append(" and dateDebut <= :dateDebutMax");
            params.put("dateDebutMax", dateDebutMax);
        }

        if (visiblePublic != null) {
            query.append(" and visiblePublic = :visiblePublic");
            params.put("visiblePublic", visiblePublic);
        }

        if (inscriptionRequise != null) {
            query.append(" and inscriptionRequise = :inscriptionRequise");
            params.put("inscriptionRequise", inscriptionRequise);
        }

        if (actif != null) {
            query.append(" and actif = :actif");
            params.put("actif", actif);
        }

        return count(query.toString(), params);
    }

    /**
     * Obtient les statistiques des événements
     *
     * @return une map contenant les statistiques
     */
    public Map<String, Long> getStatistiques() {
        Map<String, Long> stats = new java.util.HashMap<>();

        stats.put("total", count());
        stats.put("actifs", count("actif", true));
        stats.put("inactifs", count("actif", false));
        stats.put("aVenir", count("dateDebut > ?1 and actif = true", LocalDateTime.now()));
        stats.put("enCours", count("dateDebut <= ?1 and (dateFin is null or dateFin >= ?1) and actif = true", LocalDateTime.now()));
        stats.put("passes", count("(dateFin < ?1 or (dateFin is null and dateDebut < ?1)) and actif = true", LocalDateTime.now()));
        stats.put("publics", count("visiblePublic = true and actif = true"));
        stats.put("avecInscription", count("inscriptionRequise = true and actif = true"));

        return stats;
    }

    /**
     * Compte les événements dans une période et organisation
     */
    public long countEvenements(java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        return count("organisation.id = ?1 and dateDebut between ?2 and ?3",
                    organisationId, debut, fin);
    }

    /**
     * Calcule la moyenne de participants dans une période et organisation
     */
    public Double calculerMoyenneParticipants(java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        return find("SELECT AVG(e.nombreParticipants) FROM Evenement e WHERE e.organisation.id = ?1 and e.dateDebut between ?2 and ?3",
                   organisationId, debut, fin)
               .project(Double.class)
               .firstResult();
    }

    /**
     * Compte le total des participations dans une période et organisation
     */
    public long countTotalParticipations(java.util.UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        Long result = find("SELECT COALESCE(SUM(e.nombreParticipants), 0) FROM Evenement e WHERE e.organisation.id = ?1 and e.dateDebut between ?2 and ?3",
                          organisationId, debut, fin)
                     .project(Long.class)
                     .firstResult();
        return result != null ? result : 0L;
    }
}
