package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Evenement;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.TypedQuery;
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 l'entité Événement avec UUID
 *
 * <p>
 * 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 2.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class EvenementRepository extends BaseRepository<Evenement> {

    public EvenementRepository() {
        super(Evenement.class);
    }

    /**
     * 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) {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.titre = :titre", Evenement.class);
        query.setParameter("titre", titre);
        return query.getResultList().stream().findFirst();
    }

    /**
     * Trouve tous les événements actifs
     *
     * @return la liste des événements actifs
     */
    public List<Evenement> findAllActifs() {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.actif = true", Evenement.class);
        return query.getResultList();
    }

    /**
     * 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) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.actif = true" + orderBy, Evenement.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Compte le nombre d'événements actifs
     *
     * @return le nombre d'événements actifs
     */
    public long countActifs() {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class);
        return query.getSingleResult();
    }

    /**
     * Trouve les événements par statut
     *
     * @param statut le statut recherché
     * @return la liste des événements avec ce statut
     */
    public List<Evenement> findByStatut(String statut) {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.statut = :statut", Evenement.class);
        query.setParameter("statut", statut);
        return query.getResultList();
    }

    /**
     * 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(String statut, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.statut = :statut" + orderBy, Evenement.class);
        query.setParameter("statut", statut);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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(String type) {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.typeEvenement = :type", Evenement.class);
        query.setParameter("type", type);
        return query.getResultList();
    }

    /**
     * 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(String type, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.typeEvenement = :type" + orderBy, Evenement.class);
        query.setParameter("type", type);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les événements par organisation
     *
     * @param organisationId l'UUID de l'organisation
     * @return la liste des événements de cette organisation
     */
    public List<Evenement> findByOrganisation(UUID organisationId) {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId", Evenement.class);
        query.setParameter("organisationId", organisationId);
        return query.getResultList();
    }

    /**
     * Trouve les événements par organisation avec pagination et tri
     *
     * @param organisationId l'UUID 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(UUID organisationId, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId" + orderBy,
                Evenement.class);
        query.setParameter("organisationId", organisationId);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les événements à venir (date de début future)
     *
     * @return la liste des événements à venir
     */
    public List<Evenement> findEvenementsAVenir() {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true",
                Evenement.class);
        query.setParameter("maintenant", LocalDateTime.now());
        return query.getResultList();
    }

    /**
     * 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) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true" + orderBy,
                Evenement.class);
        query.setParameter("maintenant", LocalDateTime.now());
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /** Compte les événements d'une organisation (pour dashboard par org). */
    public long countByOrganisationId(UUID organisationId) {
        if (organisationId == null) return 0L;
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId", Long.class);
        query.setParameter("organisationId", organisationId);
        return query.getSingleResult();
    }

    /** Compte les événements actifs d'une organisation. */
    public long countActifsByOrganisationId(UUID organisationId) {
        if (organisationId == null) return 0L;
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND (e.actif = true OR e.actif IS NULL)",
                Long.class);
        query.setParameter("organisationId", organisationId);
        return query.getSingleResult();
    }

    /** Événements à venir pour une organisation (pour dashboard par org). */
    public List<Evenement> findEvenementsAVenirByOrganisationId(UUID organisationId, Page page, Sort sort) {
        if (organisationId == null) return List.of();
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : " ORDER BY e.dateDebut ASC";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut > :maintenant AND (e.actif = true OR e.actif IS NULL)"
                        + orderBy,
                Evenement.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("maintenant", LocalDateTime.now());
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les événements visibles au public
     *
     * @return la liste des événements publics
     */
    public List<Evenement> findEvenementsPublics() {
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true",
                Evenement.class);
        return query.getResultList();
    }

    /**
     * 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) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Evenement> query = entityManager.createQuery(
                "SELECT e FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true" + orderBy,
                Evenement.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * 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     UUID de l'organisation (optionnel)
     * @param organisateurId     UUID 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,
            String statut,
            String type,
            UUID organisationId,
            UUID organisateurId,
            LocalDateTime dateDebutMin,
            LocalDateTime dateDebutMax,
            Boolean visiblePublic,
            Boolean inscriptionRequise,
            Boolean actif,
            Page page,
            Sort sort) {
        StringBuilder jpql = new StringBuilder("SELECT e FROM Evenement e WHERE 1=1");
        Map<String, Object> params = new HashMap<>();

        if (recherche != null && !recherche.trim().isEmpty()) {
            jpql.append(
                    " AND (LOWER(e.titre) LIKE LOWER(:recherche) OR LOWER(e.description) LIKE LOWER(:recherche) OR LOWER(e.lieu) LIKE LOWER(:recherche))");
            params.put("recherche", "%" + recherche.toLowerCase() + "%");
        }

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

        if (type != null) {
            jpql.append(" AND e.typeEvenement = :type");
            params.put("type", type);
        }

        if (organisationId != null) {
            jpql.append(" AND e.organisation.id = :organisationId");
            params.put("organisationId", organisationId);
        }

        if (organisateurId != null) {
            jpql.append(" AND e.organisateur.id = :organisateurId");
            params.put("organisateurId", organisateurId);
        }

        if (dateDebutMin != null) {
            jpql.append(" AND e.dateDebut >= :dateDebutMin");
            params.put("dateDebutMin", dateDebutMin);
        }

        if (dateDebutMax != null) {
            jpql.append(" AND e.dateDebut <= :dateDebutMax");
            params.put("dateDebutMax", dateDebutMax);
        }

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

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

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

        if (sort != null) {
            jpql.append(" ORDER BY ").append(buildOrderBy(sort));
        }

        TypedQuery<Evenement> query = entityManager.createQuery(jpql.toString(), Evenement.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();
    }

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

        TypedQuery<Long> totalQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e", Long.class);
        stats.put("total", totalQuery.getSingleResult());

        TypedQuery<Long> actifsQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.actif = true", Long.class);
        stats.put("actifs", actifsQuery.getSingleResult());

        TypedQuery<Long> inactifsQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.actif = false", Long.class);
        stats.put("inactifs", inactifsQuery.getSingleResult());

        TypedQuery<Long> aVenirQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut > :maintenant AND e.actif = true",
                Long.class);
        aVenirQuery.setParameter("maintenant", maintenant);
        stats.put("aVenir", aVenirQuery.getSingleResult());

        TypedQuery<Long> enCoursQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.dateDebut <= :maintenant AND (e.dateFin IS NULL OR e.dateFin >= :maintenant) AND e.actif = true",
                Long.class);
        enCoursQuery.setParameter("maintenant", maintenant);
        stats.put("enCours", enCoursQuery.getSingleResult());

        TypedQuery<Long> passesQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE (e.dateFin < :maintenant OR (e.dateFin IS NULL AND e.dateDebut < :maintenant)) AND e.actif = true",
                Long.class);
        passesQuery.setParameter("maintenant", maintenant);
        stats.put("passes", passesQuery.getSingleResult());

        TypedQuery<Long> publicsQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.visiblePublic = true AND e.actif = true",
                Long.class);
        stats.put("publics", publicsQuery.getSingleResult());

        TypedQuery<Long> avecInscriptionQuery = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.inscriptionRequise = true AND e.actif = true",
                Long.class);
        stats.put("avecInscription", avecInscriptionQuery.getSingleResult());

        return stats;
    }

    /**
     * Compte les événements dans une période et organisation
     *
     * @param organisationId UUID de l'organisation
     * @param debut          date de début
     * @param fin            date de fin
     * @return nombre d'événements
     */
    public long countEvenements(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(e) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
                Long.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("debut", debut);
        query.setParameter("fin", fin);
        return query.getSingleResult();
    }

    /**
     * Calcule la moyenne de participants dans une période et organisation
     *
     * @param organisationId UUID de l'organisation
     * @param debut          date de début
     * @param fin            date de fin
     * @return moyenne de participants ou null
     */
    public Double calculerMoyenneParticipants(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        TypedQuery<Double> query = entityManager.createQuery(
                "SELECT AVG(SIZE(e.inscriptions)) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
                Double.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("debut", debut);
        query.setParameter("fin", fin);
        return query.getSingleResult();
    }

    /**
     * Compte le total des participations dans une période et organisation
     *
     * @param organisationId UUID de l'organisation
     * @param debut          date de début
     * @param fin            date de fin
     * @return total des participations
     */
    public Long countTotalParticipations(UUID organisationId, LocalDateTime debut, LocalDateTime fin) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT CAST(COALESCE(SUM(SIZE(e.inscriptions)), 0) AS long) FROM Evenement e WHERE e.organisation.id = :organisationId AND e.dateDebut BETWEEN :debut AND :fin",
                Long.class);
        query.setParameter("organisationId", organisationId);
        query.setParameter("debut", debut);
        query.setParameter("fin", fin);
        return query.getSingleResult();
    }

    /** Construit la clause ORDER BY à partir d'un Sort */
    private String buildOrderBy(Sort sort) {
        if (sort.getColumns().isEmpty()) {
            return "e.dateDebut";
        }
        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("e.").append(column.getName());
            if (column.getDirection() == Sort.Direction.Descending) {
                orderBy.append(" DESC");
            } else {
                orderBy.append(" ASC");
            }
        }
        return orderBy.toString();
    }
}
