package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO;
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Paiement;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.PaiementRepository;
import dev.lions.unionflow.server.service.KeycloakService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/**
 * Service métier pour la gestion des paiements
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@ApplicationScoped
public class PaiementService {

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

  @Inject PaiementRepository paiementRepository;

  @Inject MembreRepository membreRepository;

  @Inject KeycloakService keycloakService;

  /**
   * Crée un nouveau paiement
   *
   * @param paiementDTO DTO du paiement à créer
   * @return DTO du paiement créé
   */
  @Transactional
  public PaiementDTO creerPaiement(PaiementDTO paiementDTO) {
    LOG.infof("Création d'un nouveau paiement: %s", paiementDTO.getNumeroReference());

    Paiement paiement = convertToEntity(paiementDTO);
    paiement.setCreePar(keycloakService.getCurrentUserEmail());

    paiementRepository.persist(paiement);
    LOG.infof("Paiement créé avec succès: ID=%s, Référence=%s", paiement.getId(), paiement.getNumeroReference());

    return convertToDTO(paiement);
  }

  /**
   * Met à jour un paiement existant
   *
   * @param id ID du paiement
   * @param paiementDTO DTO avec les modifications
   * @return DTO du paiement mis à jour
   */
  @Transactional
  public PaiementDTO mettreAJourPaiement(UUID id, PaiementDTO paiementDTO) {
    LOG.infof("Mise à jour du paiement ID: %s", id);

    Paiement paiement =
        paiementRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));

    if (!paiement.peutEtreModifie()) {
      throw new IllegalStateException("Le paiement ne peut plus être modifié (statut finalisé)");
    }

    updateFromDTO(paiement, paiementDTO);
    paiement.setModifiePar(keycloakService.getCurrentUserEmail());

    paiementRepository.persist(paiement);
    LOG.infof("Paiement mis à jour avec succès: ID=%s", id);

    return convertToDTO(paiement);
  }

  /**
   * Valide un paiement
   *
   * @param id ID du paiement
   * @return DTO du paiement validé
   */
  @Transactional
  public PaiementDTO validerPaiement(UUID id) {
    LOG.infof("Validation du paiement ID: %s", id);

    Paiement paiement =
        paiementRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));

    if (paiement.isValide()) {
      LOG.warnf("Le paiement ID=%s est déjà validé", id);
      return convertToDTO(paiement);
    }

    paiement.setStatutPaiement(StatutPaiement.VALIDE);
    paiement.setDateValidation(LocalDateTime.now());
    paiement.setValidateur(keycloakService.getCurrentUserEmail());
    paiement.setModifiePar(keycloakService.getCurrentUserEmail());

    paiementRepository.persist(paiement);
    LOG.infof("Paiement validé avec succès: ID=%s", id);

    return convertToDTO(paiement);
  }

  /**
   * Annule un paiement
   *
   * @param id ID du paiement
   * @return DTO du paiement annulé
   */
  @Transactional
  public PaiementDTO annulerPaiement(UUID id) {
    LOG.infof("Annulation du paiement ID: %s", id);

    Paiement paiement =
        paiementRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));

    if (!paiement.peutEtreModifie()) {
      throw new IllegalStateException("Le paiement ne peut plus être annulé (statut finalisé)");
    }

    paiement.setStatutPaiement(StatutPaiement.ANNULE);
    paiement.setModifiePar(keycloakService.getCurrentUserEmail());

    paiementRepository.persist(paiement);
    LOG.infof("Paiement annulé avec succès: ID=%s", id);

    return convertToDTO(paiement);
  }

  /**
   * Trouve un paiement par son ID
   *
   * @param id ID du paiement
   * @return DTO du paiement
   */
  public PaiementDTO trouverParId(UUID id) {
    return paiementRepository
        .findByIdOptional(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));
  }

  /**
   * Trouve un paiement par son numéro de référence
   *
   * @param numeroReference Numéro de référence
   * @return DTO du paiement
   */
  public PaiementDTO trouverParNumeroReference(String numeroReference) {
    return paiementRepository
        .findByNumeroReference(numeroReference)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec la référence: " + numeroReference));
  }

  /**
   * Liste tous les paiements d'un membre
   *
   * @param membreId ID du membre
   * @return Liste des paiements
   */
  public List<PaiementDTO> listerParMembre(UUID membreId) {
    return paiementRepository.findByMembreId(membreId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  /**
   * Calcule le montant total des paiements validés dans une période
   *
   * @param dateDebut Date de début
   * @param dateFin Date de fin
   * @return Montant total
   */
  public BigDecimal calculerMontantTotalValides(LocalDateTime dateDebut, LocalDateTime dateFin) {
    return paiementRepository.calculerMontantTotalValides(dateDebut, dateFin);
  }

  // ========================================
  // MÉTHODES PRIVÉES
  // ========================================

  /** Convertit une entité en DTO */
  private PaiementDTO convertToDTO(Paiement paiement) {
    if (paiement == null) {
      return null;
    }

    PaiementDTO dto = new PaiementDTO();
    dto.setId(paiement.getId());
    dto.setNumeroReference(paiement.getNumeroReference());
    dto.setMontant(paiement.getMontant());
    dto.setCodeDevise(paiement.getCodeDevise());
    dto.setMethodePaiement(paiement.getMethodePaiement());
    dto.setStatutPaiement(paiement.getStatutPaiement());
    dto.setDatePaiement(paiement.getDatePaiement());
    dto.setDateValidation(paiement.getDateValidation());
    dto.setValidateur(paiement.getValidateur());
    dto.setReferenceExterne(paiement.getReferenceExterne());
    dto.setUrlPreuve(paiement.getUrlPreuve());
    dto.setCommentaire(paiement.getCommentaire());
    dto.setIpAddress(paiement.getIpAddress());
    dto.setUserAgent(paiement.getUserAgent());

    if (paiement.getMembre() != null) {
      dto.setMembreId(paiement.getMembre().getId());
    }
    if (paiement.getTransactionWave() != null) {
      dto.setTransactionWaveId(paiement.getTransactionWave().getId());
    }

    dto.setDateCreation(paiement.getDateCreation());
    dto.setDateModification(paiement.getDateModification());
    dto.setActif(paiement.getActif());

    return dto;
  }

  /** Convertit un DTO en entité */
  private Paiement convertToEntity(PaiementDTO dto) {
    if (dto == null) {
      return null;
    }

    Paiement paiement = new Paiement();
    paiement.setNumeroReference(dto.getNumeroReference());
    paiement.setMontant(dto.getMontant());
    paiement.setCodeDevise(dto.getCodeDevise());
    paiement.setMethodePaiement(dto.getMethodePaiement());
    paiement.setStatutPaiement(dto.getStatutPaiement() != null ? dto.getStatutPaiement() : StatutPaiement.EN_ATTENTE);
    paiement.setDatePaiement(dto.getDatePaiement());
    paiement.setDateValidation(dto.getDateValidation());
    paiement.setValidateur(dto.getValidateur());
    paiement.setReferenceExterne(dto.getReferenceExterne());
    paiement.setUrlPreuve(dto.getUrlPreuve());
    paiement.setCommentaire(dto.getCommentaire());
    paiement.setIpAddress(dto.getIpAddress());
    paiement.setUserAgent(dto.getUserAgent());

    // Relation Membre
    if (dto.getMembreId() != null) {
      Membre membre =
          membreRepository
              .findByIdOptional(dto.getMembreId())
              .orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
      paiement.setMembre(membre);
    }

    // Relation TransactionWave sera gérée par WaveService

    return paiement;
  }

  /** Met à jour une entité à partir d'un DTO */
  private void updateFromDTO(Paiement paiement, PaiementDTO dto) {
    if (dto.getMontant() != null) {
      paiement.setMontant(dto.getMontant());
    }
    if (dto.getCodeDevise() != null) {
      paiement.setCodeDevise(dto.getCodeDevise());
    }
    if (dto.getMethodePaiement() != null) {
      paiement.setMethodePaiement(dto.getMethodePaiement());
    }
    if (dto.getStatutPaiement() != null) {
      paiement.setStatutPaiement(dto.getStatutPaiement());
    }
    if (dto.getDatePaiement() != null) {
      paiement.setDatePaiement(dto.getDatePaiement());
    }
    if (dto.getDateValidation() != null) {
      paiement.setDateValidation(dto.getDateValidation());
    }
    if (dto.getValidateur() != null) {
      paiement.setValidateur(dto.getValidateur());
    }
    if (dto.getReferenceExterne() != null) {
      paiement.setReferenceExterne(dto.getReferenceExterne());
    }
    if (dto.getUrlPreuve() != null) {
      paiement.setUrlPreuve(dto.getUrlPreuve());
    }
    if (dto.getCommentaire() != null) {
      paiement.setCommentaire(dto.getCommentaire());
    }
    if (dto.getIpAddress() != null) {
      paiement.setIpAddress(dto.getIpAddress());
    }
    if (dto.getUserAgent() != null) {
      paiement.setUserAgent(dto.getUserAgent());
    }
  }
}
