package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.CotisationRepository;
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.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * Service métier pour la gestion des cotisations Contient la logique métier et les règles de
 * validation
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@ApplicationScoped
@Slf4j
public class CotisationService {

  @Inject CotisationRepository cotisationRepository;

  @Inject MembreRepository membreRepository;

  /**
   * Récupère toutes les cotisations avec pagination
   *
   * @param page numéro de page (0-based)
   * @param size taille de la page
   * @return liste des cotisations converties en DTO
   */
  public List<CotisationDTO> getAllCotisations(int page, int size) {
    log.debug("Récupération des cotisations - page: {}, size: {}", page, size);

    // Utilisation de EntityManager pour la pagination
    jakarta.persistence.TypedQuery<Cotisation> query = 
        cotisationRepository.getEntityManager().createQuery(
            "SELECT c FROM Cotisation c ORDER BY c.dateEcheance DESC",
            Cotisation.class);
    query.setFirstResult(page * size);
    query.setMaxResults(size);
    List<Cotisation> cotisations = query.getResultList();

    return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /**
   * Récupère une cotisation par son ID
   *
   * @param id identifiant UUID de la cotisation
   * @return DTO de la cotisation
   * @throws NotFoundException si la cotisation n'existe pas
   */
  public CotisationDTO getCotisationById(@NotNull UUID id) {
    log.debug("Récupération de la cotisation avec ID: {}", id);

    Cotisation cotisation =
        cotisationRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));

    return convertToDTO(cotisation);
  }

  /**
   * Récupère une cotisation par son numéro de référence
   *
   * @param numeroReference numéro de référence unique
   * @return DTO de la cotisation
   * @throws NotFoundException si la cotisation n'existe pas
   */
  public CotisationDTO getCotisationByReference(@NotNull String numeroReference) {
    log.debug("Récupération de la cotisation avec référence: {}", numeroReference);

    Cotisation cotisation =
        cotisationRepository
            .findByNumeroReference(numeroReference)
            .orElseThrow(
                () ->
                    new NotFoundException(
                        "Cotisation non trouvée avec la référence: " + numeroReference));

    return convertToDTO(cotisation);
  }

  /**
   * Crée une nouvelle cotisation
   *
   * @param cotisationDTO données de la cotisation à créer
   * @return DTO de la cotisation créée
   */
  @Transactional
  public CotisationDTO createCotisation(@Valid CotisationDTO cotisationDTO) {
    log.info("Création d'une nouvelle cotisation pour le membre: {}", cotisationDTO.getMembreId());

    // Validation du membre - UUID direct maintenant
    Membre membre =
        membreRepository
            .findByIdOptional(cotisationDTO.getMembreId())
            .orElseThrow(
                () ->
                    new NotFoundException(
                        "Membre non trouvé avec l'ID: " + cotisationDTO.getMembreId()));

    // Conversion DTO vers entité
    Cotisation cotisation = convertToEntity(cotisationDTO);
    cotisation.setMembre(membre);

    // Génération automatique du numéro de référence si absent
    if (cotisation.getNumeroReference() == null || cotisation.getNumeroReference().isEmpty()) {
      cotisation.setNumeroReference(Cotisation.genererNumeroReference());
    }

    // Validation des règles métier
    validateCotisationRules(cotisation);

    // Persistance
    cotisationRepository.persist(cotisation);

    log.info(
        "Cotisation créée avec succès - ID: {}, Référence: {}",
        cotisation.getId(),
        cotisation.getNumeroReference());

    return convertToDTO(cotisation);
  }

  /**
   * Met à jour une cotisation existante
   *
   * @param id identifiant UUID de la cotisation
   * @param cotisationDTO nouvelles données
   * @return DTO de la cotisation mise à jour
   */
  @Transactional
  public CotisationDTO updateCotisation(@NotNull UUID id, @Valid CotisationDTO cotisationDTO) {
    log.info("Mise à jour de la cotisation avec ID: {}", id);

    Cotisation cotisationExistante =
        cotisationRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));

    // Mise à jour des champs modifiables
    updateCotisationFields(cotisationExistante, cotisationDTO);

    // Validation des règles métier
    validateCotisationRules(cotisationExistante);

    log.info("Cotisation mise à jour avec succès - ID: {}", id);

    return convertToDTO(cotisationExistante);
  }

  /**
   * Supprime (désactive) une cotisation
   *
   * @param id identifiant UUID de la cotisation
   */
  @Transactional
  public void deleteCotisation(@NotNull UUID id) {
    log.info("Suppression de la cotisation avec ID: {}", id);

    Cotisation cotisation =
        cotisationRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Cotisation non trouvée avec l'ID: " + id));

    // Vérification si la cotisation peut être supprimée
    if ("PAYEE".equals(cotisation.getStatut())) {
      throw new IllegalStateException("Impossible de supprimer une cotisation déjà payée");
    }

    cotisation.setStatut("ANNULEE");

    log.info("Cotisation supprimée avec succès - ID: {}", id);
  }

  /**
   * Récupère les cotisations d'un membre
   *
   * @param membreId identifiant UUID du membre
   * @param page numéro de page
   * @param size taille de la page
   * @return liste des cotisations du membre
   */
  public List<CotisationDTO> getCotisationsByMembre(@NotNull UUID membreId, int page, int size) {
    log.debug("Récupération des cotisations du membre: {}", membreId);

    // Vérification de l'existence du membre
    if (!membreRepository.findByIdOptional(membreId).isPresent()) {
      throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
    }

    List<Cotisation> cotisations =
        cotisationRepository.findByMembreId(
            membreId, Page.of(page, size), Sort.by("dateEcheance").descending());

    return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /**
   * Récupère les cotisations par statut
   *
   * @param statut statut recherché
   * @param page numéro de page
   * @param size taille de la page
   * @return liste des cotisations avec le statut spécifié
   */
  public List<CotisationDTO> getCotisationsByStatut(@NotNull String statut, int page, int size) {
    log.debug("Récupération des cotisations avec statut: {}", statut);

    List<Cotisation> cotisations = cotisationRepository.findByStatut(statut, Page.of(page, size));

    return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /**
   * Récupère les cotisations en retard
   *
   * @param page numéro de page
   * @param size taille de la page
   * @return liste des cotisations en retard
   */
  public List<CotisationDTO> getCotisationsEnRetard(int page, int size) {
    log.debug("Récupération des cotisations en retard");

    List<Cotisation> cotisations =
        cotisationRepository.findCotisationsEnRetard(LocalDate.now(), Page.of(page, size));

    return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /**
   * Recherche avancée de cotisations
   *
   * @param membreId identifiant du membre (optionnel)
   * @param statut statut (optionnel)
   * @param typeCotisation type (optionnel)
   * @param annee année (optionnel)
   * @param mois mois (optionnel)
   * @param page numéro de page
   * @param size taille de la page
   * @return liste filtrée des cotisations
   */
  public List<CotisationDTO> rechercherCotisations(
      UUID membreId,
      String statut,
      String typeCotisation,
      Integer annee,
      Integer mois,
      int page,
      int size) {
    log.debug("Recherche avancée de cotisations avec filtres");

    List<Cotisation> cotisations =
        cotisationRepository.rechercheAvancee(
            membreId, statut, typeCotisation, annee, mois, Page.of(page, size));

    return cotisations.stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  /**
   * Récupère les statistiques des cotisations
   *
   * @return map contenant les statistiques
   */
  public Map<String, Object> getStatistiquesCotisations() {
    log.debug("Calcul des statistiques des cotisations");

    long totalCotisations = cotisationRepository.count();
    long cotisationsPayees = cotisationRepository.compterParStatut("PAYEE");
    long cotisationsEnRetard =
        cotisationRepository
            .findCotisationsEnRetard(LocalDate.now(), Page.of(0, Integer.MAX_VALUE))
            .size();

    return Map.of(
        "totalCotisations", totalCotisations,
        "cotisationsPayees", cotisationsPayees,
        "cotisationsEnRetard", cotisationsEnRetard,
        "tauxPaiement",
            totalCotisations > 0 ? (cotisationsPayees * 100.0 / totalCotisations) : 0.0);
  }

  /** Convertit une entité Cotisation en DTO */
  private CotisationDTO convertToDTO(Cotisation cotisation) {
    if (cotisation == null) {
      return null;
    }

    CotisationDTO dto = new CotisationDTO();

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

    // Conversion du membre associé
    if (cotisation.getMembre() != null) {
      dto.setMembreId(cotisation.getMembre().getId());
      dto.setNomMembre(cotisation.getMembre().getNomComplet());
      dto.setNumeroMembre(cotisation.getMembre().getNumeroMembre());

      // Conversion de l'organisation du membre (associationId)
      if (cotisation.getMembre().getOrganisation() != null
          && cotisation.getMembre().getOrganisation().getId() != null) {
        dto.setAssociationId(cotisation.getMembre().getOrganisation().getId());
        dto.setNomAssociation(cotisation.getMembre().getOrganisation().getNom());
      }
    }

    // Propriétés de la cotisation
    dto.setTypeCotisation(cotisation.getTypeCotisation());
    dto.setMontantDu(cotisation.getMontantDu());
    dto.setMontantPaye(cotisation.getMontantPaye());
    dto.setCodeDevise(cotisation.getCodeDevise());
    dto.setStatut(cotisation.getStatut());
    dto.setDateEcheance(cotisation.getDateEcheance());
    dto.setDatePaiement(cotisation.getDatePaiement());
    dto.setDescription(cotisation.getDescription());
    dto.setPeriode(cotisation.getPeriode());
    dto.setAnnee(cotisation.getAnnee());
    dto.setMois(cotisation.getMois());
    dto.setObservations(cotisation.getObservations());
    dto.setRecurrente(cotisation.getRecurrente());
    dto.setNombreRappels(cotisation.getNombreRappels());
    dto.setDateDernierRappel(cotisation.getDateDernierRappel());

    // Conversion du validateur
    dto.setValidePar(
        cotisation.getValideParId() != null
            ? cotisation.getValideParId()
            : null);
    dto.setNomValidateur(cotisation.getNomValidateur());

    dto.setMethodePaiement(cotisation.getMethodePaiement());
    dto.setReferencePaiement(cotisation.getReferencePaiement());
    dto.setDateCreation(cotisation.getDateCreation());
    dto.setDateModification(cotisation.getDateModification());

    // Propriétés héritées de BaseDTO
    dto.setActif(true); // Les cotisations sont toujours actives
    dto.setVersion(0L); // Version par défaut

    return dto;
  }

  /** Convertit un DTO en entité Cotisation */
  private Cotisation convertToEntity(CotisationDTO dto) {
    return Cotisation.builder()
        .numeroReference(dto.getNumeroReference())
        .typeCotisation(dto.getTypeCotisation())
        .montantDu(dto.getMontantDu())
        .montantPaye(dto.getMontantPaye() != null ? dto.getMontantPaye() : BigDecimal.ZERO)
        .codeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF")
        .statut(dto.getStatut() != null ? dto.getStatut() : "EN_ATTENTE")
        .dateEcheance(dto.getDateEcheance())
        .datePaiement(dto.getDatePaiement())
        .description(dto.getDescription())
        .periode(dto.getPeriode())
        .annee(dto.getAnnee())
        .mois(dto.getMois())
        .observations(dto.getObservations())
        .recurrente(dto.getRecurrente() != null ? dto.getRecurrente() : false)
        .nombreRappels(dto.getNombreRappels() != null ? dto.getNombreRappels() : 0)
        .dateDernierRappel(dto.getDateDernierRappel())
        .methodePaiement(dto.getMethodePaiement())
        .referencePaiement(dto.getReferencePaiement())
        .build();
  }

  /** Met à jour les champs d'une cotisation existante */
  private void updateCotisationFields(Cotisation cotisation, CotisationDTO dto) {
    if (dto.getTypeCotisation() != null) {
      cotisation.setTypeCotisation(dto.getTypeCotisation());
    }
    if (dto.getMontantDu() != null) {
      cotisation.setMontantDu(dto.getMontantDu());
    }
    if (dto.getMontantPaye() != null) {
      cotisation.setMontantPaye(dto.getMontantPaye());
    }
    if (dto.getStatut() != null) {
      cotisation.setStatut(dto.getStatut());
    }
    if (dto.getDateEcheance() != null) {
      cotisation.setDateEcheance(dto.getDateEcheance());
    }
    if (dto.getDatePaiement() != null) {
      cotisation.setDatePaiement(dto.getDatePaiement());
    }
    if (dto.getDescription() != null) {
      cotisation.setDescription(dto.getDescription());
    }
    if (dto.getObservations() != null) {
      cotisation.setObservations(dto.getObservations());
    }
    if (dto.getMethodePaiement() != null) {
      cotisation.setMethodePaiement(dto.getMethodePaiement());
    }
    if (dto.getReferencePaiement() != null) {
      cotisation.setReferencePaiement(dto.getReferencePaiement());
    }
  }

  /** Valide les règles métier pour une cotisation */
  private void validateCotisationRules(Cotisation cotisation) {
    // Validation du montant
    if (cotisation.getMontantDu().compareTo(BigDecimal.ZERO) <= 0) {
      throw new IllegalArgumentException("Le montant dû doit être positif");
    }

    // Validation de la date d'échéance
    if (cotisation.getDateEcheance().isBefore(LocalDate.now().minusYears(1))) {
      throw new IllegalArgumentException("La date d'échéance ne peut pas être antérieure à un an");
    }

    // Validation du montant payé
    if (cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) > 0) {
      throw new IllegalArgumentException("Le montant payé ne peut pas dépasser le montant dû");
    }

    // Validation de la cohérence statut/paiement
    if ("PAYEE".equals(cotisation.getStatut())
        && cotisation.getMontantPaye().compareTo(cotisation.getMontantDu()) < 0) {
      throw new IllegalArgumentException(
          "Une cotisation marquée comme payée doit avoir un montant payé égal au montant dû");
    }
  }
}
