package dev.lions.unionflow.server.repository;

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

/**
 * Repository pour l'entité Organisation avec UUID
 *
 * @author UnionFlow Team
 * @version 2.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class OrganisationRepository extends BaseRepository<Organisation> {

    public OrganisationRepository() {
        super(Organisation.class);
    }

    /**
     * Trouve une organisation par son email
     *
     * @param email l'email de l'organisation
     * @return Optional contenant l'organisation si trouvée
     */
    public Optional<Organisation> findByEmail(String email) {
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.email = :email", Organisation.class);
        query.setParameter("email", email);
        List<Organisation> list = query.getResultList();
        return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
    }

    /**
     * Trouve une organisation par son nom
     *
     * @param nom le nom de l'organisation
     * @return Optional contenant l'organisation si trouvée
     */
    public Optional<Organisation> findByNom(String nom) {
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.nom = :nom", Organisation.class);
        query.setParameter("nom", nom);
        List<Organisation> list = query.getResultList();
        return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
    }

    /**
     * Trouve une organisation par son numéro d'enregistrement
     *
     * @param numeroEnregistrement le numéro d'enregistrement officiel
     * @return Optional contenant l'organisation si trouvée
     */
    public Optional<Organisation> findByNumeroEnregistrement(String numeroEnregistrement) {
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.numeroEnregistrement = :numeroEnregistrement",
                Organisation.class);
        query.setParameter("numeroEnregistrement", numeroEnregistrement);
        List<Organisation> list = query.getResultList();
        return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
    }

    /**
     * Trouve toutes les organisations actives
     *
     * @return liste des organisations actives
     */
    public List<Organisation> findAllActives() {
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true",
                Organisation.class);
        return query.getResultList();
    }

    /**
     * Trouve toutes les organisations actives avec pagination
     *
     * @param page pagination
     * @param sort tri
     * @return liste paginée des organisations actives
     */
    public List<Organisation> findAllActives(Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true" + orderBy,
                Organisation.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Compte le nombre d'organisations actives
     *
     * @return nombre d'organisations actives
     */
    public long countActives() {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(o) FROM Organisation o WHERE o.statut = 'ACTIVE' AND o.actif = true",
                Long.class);
        return query.getSingleResult();
    }

    /**
     * Trouve les organisations par statut
     *
     * @param statut le statut recherché
     * @param page   pagination
     * @param sort   tri
     * @return liste paginée des organisations avec le statut spécifié
     */
    public List<Organisation> findByStatut(String statut, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.statut = :statut" + orderBy,
                Organisation.class);
        query.setParameter("statut", statut);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations par type
     *
     * @param typeOrganisation le type d'organisation
     * @param page             pagination
     * @param sort             tri
     * @return liste paginée des organisations du type spécifié
     */
    public List<Organisation> findByType(String typeOrganisation, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation" + orderBy,
                Organisation.class);
        query.setParameter("typeOrganisation", typeOrganisation);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations par ville
     *
     * @param ville la ville
     * @param page  pagination
     * @param sort  tri
     * @return liste paginée des organisations de la ville spécifiée
     */
    public List<Organisation> findByVille(String ville, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.ville = :ville" + orderBy,
                Organisation.class);
        query.setParameter("ville", ville);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations par pays
     *
     * @param pays le pays
     * @param page pagination
     * @param sort tri
     * @return liste paginée des organisations du pays spécifié
     */
    public List<Organisation> findByPays(String pays, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.pays = :pays" + orderBy,
                Organisation.class);
        query.setParameter("pays", pays);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations par région
     *
     * @param region la région
     * @param page   pagination
     * @param sort   tri
     * @return liste paginée des organisations de la région spécifiée
     */
    public List<Organisation> findByRegion(String region, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.region = :region" + orderBy,
                Organisation.class);
        query.setParameter("region", region);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations filles d'une organisation parente
     *
     * @param organisationParenteId l'UUID de l'organisation parente
     * @param page                  pagination
     * @param sort                  tri
     * @return liste paginée des organisations filles
     */
    public List<Organisation> findByOrganisationParente(
            UUID organisationParenteId, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.organisationParenteId = :organisationParenteId"
                        + orderBy,
                Organisation.class);
        query.setParameter("organisationParenteId", organisationParenteId);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations racines (sans parent)
     *
     * @param page pagination
     * @param sort tri
     * @return liste paginée des organisations racines
     */
    public List<Organisation> findOrganisationsRacines(Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.organisationParenteId IS NULL" + orderBy,
                Organisation.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Recherche d'organisations par nom ou nom court
     *
     * @param recherche terme de recherche
     * @param page      pagination
     * @param sort      tri
     * @return liste paginée des organisations correspondantes
     */
    public List<Organisation> findByNomOrNomCourt(String recherche, Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE LOWER(o.nom) LIKE LOWER(:recherche) OR LOWER(o.nomCourt) LIKE LOWER(:recherche)"
                        + orderBy,
                Organisation.class);
        query.setParameter("recherche", "%" + recherche + "%");
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Compte le nombre d'organisations correspondantes à une recherche
     *
     * @param recherche terme de recherche
     * @return nombre d'organisations correspondantes
     */
    public long countByNomOrNomCourt(String recherche) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(o) FROM Organisation o WHERE LOWER(o.nom) LIKE LOWER(:recherche) OR LOWER(o.nomCourt) LIKE LOWER(:recherche)",
                Long.class);
        query.setParameter("recherche", "%" + recherche + "%");
        return query.getSingleResult();
    }

    /**
     * Recherche avancée d'organisations
     *
     * @param nom              nom (optionnel)
     * @param typeOrganisation type (optionnel)
     * @param statut           statut (optionnel)
     * @param ville            ville (optionnel)
     * @param region           région (optionnel)
     * @param pays             pays (optionnel)
     * @param page             pagination
     * @return liste filtrée des organisations
     */
    public List<Organisation> rechercheAvancee(
            String nom,
            String typeOrganisation,
            String statut,
            String ville,
            String region,
            String pays,
            Page page) {
        StringBuilder queryBuilder = new StringBuilder("SELECT o FROM Organisation o WHERE 1=1");
        Map<String, Object> parameters = new HashMap<>();

        if (nom != null && !nom.isEmpty()) {
            queryBuilder.append(" AND (LOWER(o.nom) LIKE LOWER(:nom) OR LOWER(o.nomCourt) LIKE LOWER(:nom))");
            parameters.put("nom", "%" + nom.toLowerCase() + "%");
        }

        if (typeOrganisation != null && !typeOrganisation.isEmpty()) {
            queryBuilder.append(" AND o.typeOrganisation = :typeOrganisation");
            parameters.put("typeOrganisation", typeOrganisation);
        }

        if (statut != null && !statut.isEmpty()) {
            queryBuilder.append(" AND o.statut = :statut");
            parameters.put("statut", statut);
        }

        if (ville != null && !ville.isEmpty()) {
            queryBuilder.append(" AND LOWER(o.ville) LIKE LOWER(:ville)");
            parameters.put("ville", "%" + ville.toLowerCase() + "%");
        }

        if (region != null && !region.isEmpty()) {
            queryBuilder.append(" AND LOWER(o.region) LIKE LOWER(:region)");
            parameters.put("region", "%" + region.toLowerCase() + "%");
        }

        if (pays != null && !pays.isEmpty()) {
            queryBuilder.append(" AND LOWER(o.pays) LIKE LOWER(:pays)");
            parameters.put("pays", "%" + pays.toLowerCase() + "%");
        }

        queryBuilder.append(" ORDER BY o.nom ASC");

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

    /**
     * Compte les nouvelles organisations depuis une date donnée
     *
     * @param depuis date de référence
     * @return nombre de nouvelles organisations
     */
    public long countNouvellesOrganisations(LocalDate depuis) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(o) FROM Organisation o WHERE o.dateCreation >= :depuis", Long.class);
        query.setParameter("depuis", depuis.atStartOfDay());
        return query.getSingleResult();
    }

    /**
     * Trouve les organisations publiques (visibles dans l'annuaire)
     *
     * @param page pagination
     * @param sort tri
     * @return liste paginée des organisations publiques
     */
    public List<Organisation> findOrganisationsPubliques(Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.organisationPublique = true AND o.statut = 'ACTIVE' AND o.actif = true"
                        + orderBy,
                Organisation.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Trouve les organisations acceptant de nouveaux membres
     *
     * @param page pagination
     * @param sort tri
     * @return liste paginée des organisations acceptant de nouveaux membres
     */
    public List<Organisation> findOrganisationsOuvertes(Page page, Sort sort) {
        String orderBy = sort != null ? " ORDER BY " + buildOrderBy(sort) : "";
        TypedQuery<Organisation> query = entityManager.createQuery(
                "SELECT o FROM Organisation o WHERE o.accepteNouveauxMembres = true AND o.statut = 'ACTIVE' AND o.actif = true"
                        + orderBy,
                Organisation.class);
        query.setFirstResult(page.index * page.size);
        query.setMaxResults(page.size);
        return query.getResultList();
    }

    /**
     * Compte les organisations par statut
     *
     * @param statut le statut
     * @return nombre d'organisations avec ce statut
     */
    public long countByStatut(String statut) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(o) FROM Organisation o WHERE o.statut = :statut", Long.class);
        query.setParameter("statut", statut);
        return query.getSingleResult();
    }

    /**
     * Compte les organisations par type
     *
     * @param typeOrganisation le type d'organisation
     * @return nombre d'organisations de ce type
     */
    public long countByType(String typeOrganisation) {
        TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(o) FROM Organisation o WHERE o.typeOrganisation = :typeOrganisation",
                Long.class);
        query.setParameter("typeOrganisation", typeOrganisation);
        return query.getSingleResult();
    }

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