package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/** Service métier pour les membres */
@ApplicationScoped
public class MembreService {

  private static final Logger LOG = Logger.getLogger(MembreService.class);

  @Inject MembreRepository membreRepository;

  @PersistenceContext
  EntityManager entityManager;

  /** Crée un nouveau membre */
  @Transactional
  public Membre creerMembre(Membre membre) {
    LOG.infof("Création d'un nouveau membre: %s", membre.getEmail());

    // Générer un numéro de membre unique
    if (membre.getNumeroMembre() == null || membre.getNumeroMembre().isEmpty()) {
      membre.setNumeroMembre(genererNumeroMembre());
    }
    
    // Définir la date d'adhésion si non fournie
    if (membre.getDateAdhesion() == null) {
      membre.setDateAdhesion(LocalDate.now());
      LOG.infof("Date d'adhésion automatiquement définie à: %s", membre.getDateAdhesion());
    }
    
    // Définir la date de naissance par défaut si non fournie (pour éviter @NotNull)
    if (membre.getDateNaissance() == null) {
      membre.setDateNaissance(LocalDate.now().minusYears(18)); // Majeur par défaut
      LOG.warn("Date de naissance non fournie, définie par défaut à il y a 18 ans");
    }

    // Vérifier l'unicité de l'email
    if (membreRepository.findByEmail(membre.getEmail()).isPresent()) {
      throw new IllegalArgumentException("Un membre avec cet email existe déjà");
    }

    // Vérifier l'unicité du numéro de membre
    if (membreRepository.findByNumeroMembre(membre.getNumeroMembre()).isPresent()) {
      throw new IllegalArgumentException("Un membre avec ce numéro existe déjà");
    }

    membreRepository.persist(membre);
    LOG.infof("Membre créé avec succès: %s (ID: %s)", membre.getNomComplet(), membre.getId());
    return membre;
  }

  /** Met à jour un membre existant */
  @Transactional
  public Membre mettreAJourMembre(UUID id, Membre membreModifie) {
    LOG.infof("Mise à jour du membre ID: %s", id);

    Membre membre = membreRepository.findById(id);
    if (membre == null) {
      throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
    }

    // Vérifier l'unicité de l'email si modifié
    if (!membre.getEmail().equals(membreModifie.getEmail())) {
      if (membreRepository.findByEmail(membreModifie.getEmail()).isPresent()) {
        throw new IllegalArgumentException("Un membre avec cet email existe déjà");
      }
    }

    // Mettre à jour les champs
    membre.setPrenom(membreModifie.getPrenom());
    membre.setNom(membreModifie.getNom());
    membre.setEmail(membreModifie.getEmail());
    membre.setTelephone(membreModifie.getTelephone());
    membre.setDateNaissance(membreModifie.getDateNaissance());
    membre.setActif(membreModifie.getActif());

    LOG.infof("Membre mis à jour avec succès: %s", membre.getNomComplet());
    return membre;
  }

  /** Trouve un membre par son ID */
  public Optional<Membre> trouverParId(UUID id) {
    return Optional.ofNullable(membreRepository.findById(id));
  }

  /** Trouve un membre par son email */
  public Optional<Membre> trouverParEmail(String email) {
    return membreRepository.findByEmail(email);
  }

  /** Liste tous les membres actifs */
  public List<Membre> listerMembresActifs() {
    return membreRepository.findAllActifs();
  }

  /** Recherche des membres par nom ou prénom */
  public List<Membre> rechercherMembres(String recherche) {
    return membreRepository.findByNomOrPrenom(recherche);
  }

  /** Désactive un membre */
  @Transactional
  public void desactiverMembre(UUID id) {
    LOG.infof("Désactivation du membre ID: %s", id);

    Membre membre = membreRepository.findById(id);
    if (membre == null) {
      throw new IllegalArgumentException("Membre non trouvé avec l'ID: " + id);
    }

    membre.setActif(false);
    LOG.infof("Membre désactivé: %s", membre.getNomComplet());
  }

  /** Génère un numéro de membre unique */
  private String genererNumeroMembre() {
    String prefix = "UF" + LocalDate.now().getYear();
    String suffix = UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    return prefix + "-" + suffix;
  }

  /** Compte le nombre total de membres actifs */
  public long compterMembresActifs() {
    return membreRepository.countActifs();
  }

  /** Liste tous les membres actifs avec pagination */
  public List<Membre> listerMembresActifs(Page page, Sort sort) {
    return membreRepository.findAllActifs(page, sort);
  }

  /** Recherche des membres avec pagination */
  public List<Membre> rechercherMembres(String recherche, Page page, Sort sort) {
    return membreRepository.findByNomOrPrenom(recherche, page, sort);
  }

  /** Obtient les statistiques avancées des membres */
  public Map<String, Object> obtenirStatistiquesAvancees() {
    LOG.info("Calcul des statistiques avancées des membres");

    long totalMembres = membreRepository.count();
    long membresActifs = membreRepository.countActifs();
    long membresInactifs = totalMembres - membresActifs;
    long nouveauxMembres30Jours =
        membreRepository.countNouveauxMembres(LocalDate.now().minusDays(30));

    return Map.of(
        "totalMembres", totalMembres,
        "membresActifs", membresActifs,
        "membresInactifs", membresInactifs,
        "nouveauxMembres30Jours", nouveauxMembres30Jours,
        "tauxActivite", totalMembres > 0 ? (membresActifs * 100.0 / totalMembres) : 0.0,
        "timestamp", LocalDateTime.now());
  }

  // ========================================
  // MÉTHODES DE CONVERSION DTO
  // ========================================

  /** Convertit une entité Membre en MembreDTO */
  public MembreDTO convertToDTO(Membre membre) {
    if (membre == null) {
      return null;
    }

    MembreDTO dto = new MembreDTO();

    // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant)
    dto.setId(membre.getId());

    // Copie des champs de base
    dto.setNumeroMembre(membre.getNumeroMembre());
    dto.setNom(membre.getNom());
    dto.setPrenom(membre.getPrenom());
    dto.setEmail(membre.getEmail());
    dto.setTelephone(membre.getTelephone());
    dto.setDateNaissance(membre.getDateNaissance());
    dto.setDateAdhesion(membre.getDateAdhesion());

    // Conversion du statut boolean vers enum StatutMembre
    // Règle métier: actif=true → ACTIF, actif=false → INACTIF
    if (membre.getActif() == null || Boolean.TRUE.equals(membre.getActif())) {
      dto.setStatut(StatutMembre.ACTIF);
    } else {
      dto.setStatut(StatutMembre.INACTIF);
    }

    // Conversion de l'organisation (associationId)
    // Utilisation directe de l'UUID de l'organisation
    if (membre.getOrganisation() != null && membre.getOrganisation().getId() != null) {
      dto.setAssociationId(membre.getOrganisation().getId());
      dto.setAssociationNom(membre.getOrganisation().getNom());
    }

    // Champs de base DTO
    dto.setDateCreation(membre.getDateCreation());
    dto.setDateModification(membre.getDateModification());
    dto.setVersion(0L); // Version par défaut

    // Champs par défaut pour les champs manquants dans l'entité
    dto.setMembreBureau(false);
    dto.setResponsable(false);

    return dto;
  }

  /** Convertit un MembreDTO en entité Membre */
  public Membre convertFromDTO(MembreDTO dto) {
    if (dto == null) {
      return null;
    }

    Membre membre = new Membre();

    // Copie des champs
    membre.setNumeroMembre(dto.getNumeroMembre());
    membre.setNom(dto.getNom());
    membre.setPrenom(dto.getPrenom());
    membre.setEmail(dto.getEmail());
    membre.setTelephone(dto.getTelephone());
    membre.setDateNaissance(dto.getDateNaissance());
    membre.setDateAdhesion(dto.getDateAdhesion());

    // Conversion du statut enum vers boolean
    // Règle métier: ACTIF → true, autres statuts → false
    membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut()));

    // Champs de base
    if (dto.getDateCreation() != null) {
      membre.setDateCreation(dto.getDateCreation());
    }
    if (dto.getDateModification() != null) {
      membre.setDateModification(dto.getDateModification());
    }

    return membre;
  }

  /** Convertit une liste d'entités en liste de DTOs */
  public List<MembreDTO> convertToDTOList(List<Membre> membres) {
    return membres.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /** Met à jour une entité Membre à partir d'un MembreDTO */
  public void updateFromDTO(Membre membre, MembreDTO dto) {
    if (membre == null || dto == null) {
      return;
    }

    // Mise à jour des champs modifiables
    membre.setPrenom(dto.getPrenom());
    membre.setNom(dto.getNom());
    membre.setEmail(dto.getEmail());
    membre.setTelephone(dto.getTelephone());
    membre.setDateNaissance(dto.getDateNaissance());
    // Conversion du statut enum vers boolean
    membre.setActif(dto.getStatut() != null && StatutMembre.ACTIF.equals(dto.getStatut()));
    membre.setDateModification(LocalDateTime.now());
  }

  /** Recherche avancée de membres avec filtres multiples (DEPRECATED) */
  public List<Membre> rechercheAvancee(
      String recherche,
      Boolean actif,
      LocalDate dateAdhesionMin,
      LocalDate dateAdhesionMax,
      Page page,
      Sort sort) {
    LOG.infof(
        "Recherche avancée (DEPRECATED) - recherche: %s, actif: %s, dateMin: %s, dateMax: %s",
        recherche, actif, dateAdhesionMin, dateAdhesionMax);

    return membreRepository.rechercheAvancee(
        recherche, actif, dateAdhesionMin, dateAdhesionMax, page, sort);
  }

  /**
   * Nouvelle recherche avancée de membres avec critères complets Retourne des résultats paginés
   * avec statistiques
   *
   * @param criteria Critères de recherche
   * @param page Pagination
   * @param sort Tri
   * @return Résultats de recherche avec métadonnées
   */
  public MembreSearchResultDTO searchMembresAdvanced(
      MembreSearchCriteria criteria, Page page, Sort sort) {
    LOG.infof("Recherche avancée de membres - critères: %s", criteria.getDescription());

    try {
      // Construction de la requête dynamique
      StringBuilder queryBuilder = new StringBuilder("SELECT m FROM Membre m WHERE 1=1");
      Map<String, Object> parameters = new HashMap<>();

      // Ajout des critères de recherche
      addSearchCriteria(queryBuilder, parameters, criteria);

      // Requête pour compter le total
      String countQuery =
          queryBuilder
              .toString()
              .replace("SELECT m FROM Membre m", "SELECT COUNT(m) FROM Membre m");

      // Exécution de la requête de comptage
      TypedQuery<Long> countQueryTyped = entityManager.createQuery(countQuery, Long.class);
      for (Map.Entry<String, Object> param : parameters.entrySet()) {
        countQueryTyped.setParameter(param.getKey(), param.getValue());
      }
      long totalElements = countQueryTyped.getSingleResult();

      if (totalElements == 0) {
        return MembreSearchResultDTO.empty(criteria);
      }

      // Ajout du tri et pagination
      String finalQuery = queryBuilder.toString();
      if (sort != null) {
        finalQuery += " ORDER BY " + buildOrderByClause(sort);
      }

      // Exécution de la requête principale
      TypedQuery<Membre> queryTyped = entityManager.createQuery(finalQuery, Membre.class);
      for (Map.Entry<String, Object> param : parameters.entrySet()) {
        queryTyped.setParameter(param.getKey(), param.getValue());
      }
      queryTyped.setFirstResult(page.index * page.size);
      queryTyped.setMaxResults(page.size);
      List<Membre> membres = queryTyped.getResultList();

      // Conversion en DTOs
      List<MembreDTO> membresDTO = convertToDTOList(membres);

      // Calcul des statistiques
      MembreSearchResultDTO.SearchStatistics statistics = calculateSearchStatistics(membres);

      // Construction du résultat
      MembreSearchResultDTO result =
          MembreSearchResultDTO.builder()
              .membres(membresDTO)
              .totalElements(totalElements)
              .totalPages((int) Math.ceil((double) totalElements / page.size))
              .currentPage(page.index)
              .pageSize(page.size)
              .criteria(criteria)
              .statistics(statistics)
              .build();

      // Calcul des indicateurs de pagination
      result.calculatePaginationFlags();

      return result;

    } catch (Exception e) {
      LOG.errorf(e, "Erreur lors de la recherche avancée de membres");
      throw new RuntimeException("Erreur lors de la recherche avancée", e);
    }
  }

  /** Ajoute les critères de recherche à la requête */
  private void addSearchCriteria(
      StringBuilder queryBuilder, Map<String, Object> parameters, MembreSearchCriteria criteria) {

    // Recherche générale dans nom, prénom, email
    if (criteria.getQuery() != null) {
      queryBuilder.append(
          " AND (LOWER(m.nom) LIKE LOWER(:query) OR LOWER(m.prenom) LIKE LOWER(:query) OR"
              + " LOWER(m.email) LIKE LOWER(:query))");
      parameters.put("query", "%" + criteria.getQuery() + "%");
    }

    // Recherche par nom
    if (criteria.getNom() != null) {
      queryBuilder.append(" AND LOWER(m.nom) LIKE LOWER(:nom)");
      parameters.put("nom", "%" + criteria.getNom() + "%");
    }

    // Recherche par prénom
    if (criteria.getPrenom() != null) {
      queryBuilder.append(" AND LOWER(m.prenom) LIKE LOWER(:prenom)");
      parameters.put("prenom", "%" + criteria.getPrenom() + "%");
    }

    // Recherche par email
    if (criteria.getEmail() != null) {
      queryBuilder.append(" AND LOWER(m.email) LIKE LOWER(:email)");
      parameters.put("email", "%" + criteria.getEmail() + "%");
    }

    // Recherche par téléphone
    if (criteria.getTelephone() != null) {
      queryBuilder.append(" AND m.telephone LIKE :telephone");
      parameters.put("telephone", "%" + criteria.getTelephone() + "%");
    }

    // Filtre par statut
    if (criteria.getStatut() != null) {
      boolean isActif = "ACTIF".equals(criteria.getStatut());
      queryBuilder.append(" AND m.actif = :actif");
      parameters.put("actif", isActif);
    } else if (!Boolean.TRUE.equals(criteria.getIncludeInactifs())) {
      // Par défaut, exclure les inactifs
      queryBuilder.append(" AND m.actif = true");
    }

    // Filtre par dates d'adhésion
    if (criteria.getDateAdhesionMin() != null) {
      queryBuilder.append(" AND m.dateAdhesion >= :dateAdhesionMin");
      parameters.put("dateAdhesionMin", criteria.getDateAdhesionMin());
    }

    if (criteria.getDateAdhesionMax() != null) {
      queryBuilder.append(" AND m.dateAdhesion <= :dateAdhesionMax");
      parameters.put("dateAdhesionMax", criteria.getDateAdhesionMax());
    }

    // Filtre par âge (calculé à partir de la date de naissance)
    if (criteria.getAgeMin() != null) {
      LocalDate maxBirthDate = LocalDate.now().minusYears(criteria.getAgeMin());
      queryBuilder.append(" AND m.dateNaissance <= :maxBirthDateForMinAge");
      parameters.put("maxBirthDateForMinAge", maxBirthDate);
    }

    if (criteria.getAgeMax() != null) {
      LocalDate minBirthDate = LocalDate.now().minusYears(criteria.getAgeMax() + 1).plusDays(1);
      queryBuilder.append(" AND m.dateNaissance >= :minBirthDateForMaxAge");
      parameters.put("minBirthDateForMaxAge", minBirthDate);
    }

    // Filtre par organisations (si implémenté dans l'entité)
    if (criteria.getOrganisationIds() != null && !criteria.getOrganisationIds().isEmpty()) {
      queryBuilder.append(" AND m.organisation.id IN :organisationIds");
      parameters.put("organisationIds", criteria.getOrganisationIds());
    }

    // Filtre par rôles (recherche dans le champ roles)
    if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) {
      StringBuilder roleCondition = new StringBuilder(" AND (");
      for (int i = 0; i < criteria.getRoles().size(); i++) {
        if (i > 0) roleCondition.append(" OR ");
        roleCondition.append("m.roles LIKE :role").append(i);
        parameters.put("role" + i, "%" + criteria.getRoles().get(i) + "%");
      }
      roleCondition.append(")");
      queryBuilder.append(roleCondition);
    }
  }

  /** Construit la clause ORDER BY à partir du Sort */
  private String buildOrderByClause(Sort sort) {
    if (sort == null || sort.getColumns().isEmpty()) {
      return "m.nom ASC";
    }

    return sort.getColumns().stream()
        .map(column -> "m." + column.getName() + " " + column.getDirection().name())
        .collect(Collectors.joining(", "));
  }

  /** Calcule les statistiques sur les résultats de recherche */
  private MembreSearchResultDTO.SearchStatistics calculateSearchStatistics(List<Membre> membres) {
    if (membres.isEmpty()) {
      return MembreSearchResultDTO.SearchStatistics.builder()
          .membresActifs(0)
          .membresInactifs(0)
          .ageMoyen(0.0)
          .ageMin(0)
          .ageMax(0)
          .nombreOrganisations(0)
          .nombreRegions(0)
          .ancienneteMoyenne(0.0)
          .build();
    }

    long membresActifs =
        membres.stream().mapToLong(m -> Boolean.TRUE.equals(m.getActif()) ? 1 : 0).sum();
    long membresInactifs = membres.size() - membresActifs;

    // Calcul des âges
    List<Integer> ages =
        membres.stream()
            .filter(m -> m.getDateNaissance() != null)
            .map(m -> Period.between(m.getDateNaissance(), LocalDate.now()).getYears())
            .collect(Collectors.toList());

    double ageMoyen = ages.stream().mapToInt(Integer::intValue).average().orElse(0.0);
    int ageMin = ages.stream().mapToInt(Integer::intValue).min().orElse(0);
    int ageMax = ages.stream().mapToInt(Integer::intValue).max().orElse(0);

    // Calcul de l'ancienneté moyenne
    double ancienneteMoyenne =
        membres.stream()
            .filter(m -> m.getDateAdhesion() != null)
            .mapToDouble(m -> Period.between(m.getDateAdhesion(), LocalDate.now()).getYears())
            .average()
            .orElse(0.0);

    // Nombre d'organisations (si relation disponible)
    long nombreOrganisations =
        membres.stream()
            .filter(m -> m.getOrganisation() != null)
            .map(m -> m.getOrganisation().getId())
            .distinct()
            .count();

    return MembreSearchResultDTO.SearchStatistics.builder()
        .membresActifs(membresActifs)
        .membresInactifs(membresInactifs)
        .ageMoyen(ageMoyen)
        .ageMin(ageMin)
        .ageMax(ageMax)
        .nombreOrganisations(nombreOrganisations)
        .nombreRegions(0) // À implémenter si champ région disponible
        .ancienneteMoyenne(ancienneteMoyenne)
        .build();
  }
}
