package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.paiement.request.CreatePaiementRequest;
import dev.lions.unionflow.server.api.payment.PaymentEvent;
import dev.lions.unionflow.server.api.payment.PaymentStatus;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementSummaryResponse;
import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse;
import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.IntentionPaiement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Paiement;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.PaiementRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.repository.TypeReferenceRepository;
import dev.lions.unionflow.server.service.WaveCheckoutService.WaveCheckoutException;
import dev.lions.unionflow.server.service.WaveCheckoutService.WaveCheckoutSessionResponse;
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.Optional;
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;

  @Inject
  TypeReferenceRepository typeReferenceRepository;

  @Inject
  IntentionPaiementRepository intentionPaiementRepository;

  @Inject
  WaveCheckoutService waveCheckoutService;

  @Inject
  CompteEpargneRepository compteEpargneRepository;

  @Inject
  io.quarkus.security.identity.SecurityIdentity securityIdentity;

  @Inject
  dev.lions.unionflow.server.repository.MembreOrganisationRepository membreOrganisationRepository;

  @Inject
  NotificationService notificationService;

  /**
   * Crée un nouveau paiement
   *
   * @param request DTO de requête de création
   * @return DTO du paiement créé (PaiementResponse)
   */
  @Transactional
  public PaiementResponse creerPaiement(CreatePaiementRequest request) {
    LOG.infof("Création d'un nouveau paiement: %s", request.numeroReference());

    Paiement paiement = new Paiement();
    paiement.setNumeroReference(request.numeroReference());
    paiement.setMontant(request.montant());
    paiement.setCodeDevise(request.codeDevise());
    paiement.setMethodePaiement(request.methodePaiement());
    paiement.setStatutPaiement("EN_ATTENTE");
    paiement.setCommentaire(request.commentaire());
    // DatePaiement sera initialisée lors de la validation ou par un webhook
    // IpAddress et UserAgent ne sont pas dans le Request simplifié, à voir si
    // nécessaire

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

    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 convertToResponse(paiement);
  }

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

    Paiement paiement = paiementRepository
        .findPaiementById(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 convertToResponse(paiement);
    }

    paiement.setStatutPaiement("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 convertToResponse(paiement);
  }

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

    Paiement paiement = paiementRepository
        .findPaiementById(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("ANNULE");
    paiement.setModifiePar(keycloakService.getCurrentUserEmail());

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

    return convertToResponse(paiement);
  }

  /**
   * Trouve un paiement par son ID
   *
   * @param id ID du paiement
   * @return DTO du paiement (PaiementResponse)
   */
  public PaiementResponse trouverParId(UUID id) {
    return paiementRepository
        .findPaiementById(id)
        .map(this::convertToResponse)
        .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 (PaiementResponse)
   */
  public PaiementResponse trouverParNumeroReference(String numeroReference) {
    return paiementRepository
        .findByNumeroReference(numeroReference)
        .map(this::convertToResponse)
        .orElseThrow(() -> new NotFoundException("Paiement non trouvé avec la référence: " + numeroReference));
  }

  /**
   * Liste tous les paiements d'un membre (version résumé)
   *
   * @param membreId ID du membre
   * @return Liste des paiements (PaiementSummaryResponse)
   */
  public List<PaiementSummaryResponse> listerParMembre(UUID membreId) {
    return paiementRepository.findByMembreId(membreId).stream()
        .map(this::convertToSummaryResponse)
        .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);
  }

  /**
   * Récupère l'historique des paiements du membre connecté.
   * Auto-détection du membre via SecurityIdentity.
   * Utilisé par la page personnelle "Payer mes Cotisations".
   *
   * @param limit Nombre maximum de paiements à retourner (défaut : 5)
   * @return Liste des derniers paiements
   */
  public List<PaiementSummaryResponse> getMonHistoriquePaiements(int limit) {
    Membre membreConnecte = getMembreConnecte();
    LOG.infof("Récupération de l'historique des paiements pour le membre: %s (%s), limit=%d",
        membreConnecte.getNumeroMembre(), membreConnecte.getId(), limit);

    List<Paiement> paiements = paiementRepository.getEntityManager()
        .createQuery(
            "SELECT p FROM Paiement p " +
            "WHERE p.membre.id = :membreId " +
            "AND p.statutPaiement = 'VALIDE' " +
            "ORDER BY p.datePaiement DESC",
            Paiement.class)
        .setParameter("membreId", membreConnecte.getId())
        .setMaxResults(limit)
        .getResultList();

    LOG.infof("Paiements trouvés: %d pour le membre %s", paiements.size(), membreConnecte.getNumeroMembre());

    return paiements.stream()
        .map(this::convertToSummaryResponse)
        .collect(Collectors.toList());
  }

  /**
   * Initie un paiement en ligne via un gateway (Wave, Orange Money, Free Money, Carte).
   * 1. Crée un enregistrement Paiement avec statut EN_ATTENTE
   * 2. Appelle l'API du gateway correspondant
   * 3. Retourne l'URL de redirection vers le gateway
   *
   * @param request Requête d'initiation de paiement en ligne
   * @return Réponse avec URL de redirection et transaction ID
   */
  @Transactional
  public dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse initierPaiementEnLigne(
      dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest request) {

    Membre membreConnecte = getMembreConnecte();
    LOG.infof("Initiation paiement en ligne pour membre %s: cotisation=%s, méthode=%s",
        membreConnecte.getNumeroMembre(), request.cotisationId(), request.methodePaiement());

    // Récupérer la cotisation
    Cotisation cotisation = paiementRepository.getEntityManager()
        .find(Cotisation.class, request.cotisationId());
    if (cotisation == null) {
      throw new NotFoundException("Cotisation non trouvée: " + request.cotisationId());
    }
    if (!cotisation.getMembre().getId().equals(membreConnecte.getId())) {
      throw new IllegalArgumentException("Cette cotisation n'appartient pas au membre connecté");
    }

    if ("WAVE".equals(request.methodePaiement())) {
      return initierPaiementWave(cotisation, membreConnecte, request);
    }

    // Autres méthodes : comportement par défaut (placeholder)
    Paiement paiement = new Paiement();
    paiement.setNumeroReference("PAY-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
    paiement.setMontant(cotisation.getMontantDu());
    paiement.setCodeDevise("XOF");
    paiement.setMethodePaiement(request.methodePaiement());
    paiement.setStatutPaiement("EN_ATTENTE");
    paiement.setMembre(membreConnecte);
    paiement.setReferenceExterne(request.numeroTelephone());
    paiement.setCommentaire("Paiement en ligne - " + request.methodePaiement());
    paiement.setCreePar(membreConnecte.getEmail());
    paiementRepository.persist(paiement);

    String redirectUrl = switch (request.methodePaiement()) {
      case "ORANGE_MONEY" -> "https://orange-money.com/pay/" + paiement.getId();
      case "FREE_MONEY" -> "https://free-money.com/pay/" + paiement.getId();
      case "CARTE_BANCAIRE" -> "https://payment-gateway.com/pay/" + paiement.getId();
      default -> throw new IllegalArgumentException("Méthode de paiement non supportée: " + request.methodePaiement());
    };

    return dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse.builder()
        .transactionId(paiement.getId())
        .redirectUrl(redirectUrl)
        .montant(paiement.getMontant())
        .statut("EN_ATTENTE")
        .methodePaiement(request.methodePaiement())
        .referenceCotisation(cotisation.getNumeroReference())
        .message("Redirection vers " + request.methodePaiement() + "...")
        .build();
  }

  /**
   * Initie un paiement Wave via l'API Checkout (https://docs.wave.com/checkout).
   * Crée une IntentionPaiement, appelle POST /v1/checkout/sessions, retourne wave_launch_url
   * pour redirection automatique vers l'app Wave puis retour deep link vers UnionFlow.
   */
  private dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse initierPaiementWave(
      Cotisation cotisation, Membre membreConnecte,
      dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest request) {

    String base = waveCheckoutService.getRedirectBaseUrl().replaceAll("/+$", "");

    // 1. Créer l'intention de paiement (hub Wave)
    IntentionPaiement intention = IntentionPaiement.builder()
        .utilisateur(membreConnecte)
        .organisation(cotisation.getOrganisation())
        .montantTotal(cotisation.getMontantDu())
        .codeDevise(cotisation.getCodeDevise() != null ? cotisation.getCodeDevise() : "XOF")
        .typeObjet(TypeObjetIntentionPaiement.COTISATION)
        .statut(StatutIntentionPaiement.INITIEE)
        .objetsCibles("[{\"type\":\"COTISATION\",\"id\":\"" + cotisation.getId() + "\",\"montant\":"
            + cotisation.getMontantDu().toString() + "}]")
        .build();
    intentionPaiementRepository.persist(intention);

    String successUrl = base + "/api/wave-redirect/success?ref=" + intention.getId();
    String errorUrl = base + "/api/wave-redirect/error?ref=" + intention.getId();
    String clientRef = intention.getId().toString();
    // XOF : montant entier, pas de décimales (spec Wave)
    String amountStr = cotisation.getMontantDu().setScale(0, java.math.RoundingMode.HALF_UP).toString();
    String restrictMobile = toE164(request.numeroTelephone());

    WaveCheckoutSessionResponse session;
    try {
      session = waveCheckoutService.createSession(
          amountStr,
          "XOF",
          successUrl,
          errorUrl,
          clientRef,
          restrictMobile);
    } catch (WaveCheckoutException e) {
      LOG.errorf(e, "Wave Checkout API error: %s", e.getMessage());
      intention.setStatut(StatutIntentionPaiement.ECHOUEE);
      intentionPaiementRepository.persist(intention);
      throw new jakarta.ws.rs.BadRequestException("Wave: " + e.getMessage());
    }

    intention.setWaveCheckoutSessionId(session.id);
    intention.setWaveLaunchUrl(session.waveLaunchUrl);
    intention.setStatut(StatutIntentionPaiement.EN_COURS);
    intentionPaiementRepository.persist(intention);

    cotisation.setIntentionPaiement(intention);
    paiementRepository.getEntityManager().merge(cotisation);

    Paiement paiement = new Paiement();
    paiement.setNumeroReference("PAY-WAVE-" + intention.getId().toString().substring(0, 8).toUpperCase());
    paiement.setMontant(cotisation.getMontantDu());
    paiement.setCodeDevise("XOF");
    paiement.setMethodePaiement("WAVE");
    paiement.setStatutPaiement("EN_ATTENTE");
    paiement.setMembre(membreConnecte);
    paiement.setReferenceExterne(session.id);
    paiement.setCommentaire("Paiement Wave - session " + session.id);
    paiement.setCreePar(membreConnecte.getEmail());
    paiementRepository.persist(paiement);

    LOG.infof("Paiement Wave initié: intention=%s, session=%s, wave_launch_url=%s",
        intention.getId(), session.id, session.waveLaunchUrl);

    return dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse.builder()
        .transactionId(paiement.getId())
        .redirectUrl(session.waveLaunchUrl)
        .waveLaunchUrl(session.waveLaunchUrl)
        .waveCheckoutSessionId(session.id)
        .clientReference(clientRef)
        .montant(cotisation.getMontantDu())
        .statut("EN_ATTENTE")
        .methodePaiement("WAVE")
        .referenceCotisation(cotisation.getNumeroReference())
        .message("Ouvrez l'application Wave pour confirmer le paiement, puis vous serez renvoyé à UnionFlow.")
        .build();
  }

  /** Format E.164 pour Wave (ex: 771234567 -> +225771234567). */
  private static String toE164(String numeroTelephone) {
    if (numeroTelephone == null || numeroTelephone.isBlank()) return null;
    String digits = numeroTelephone.replaceAll("\\D", "");
    if (digits.length() == 9 && (digits.startsWith("7") || digits.startsWith("0"))) {
      return "+225" + (digits.startsWith("0") ? digits.substring(1) : digits);
    }
    if (digits.length() >= 9 && digits.startsWith("225")) return "+" + digits;
    return numeroTelephone.startsWith("+") ? numeroTelephone : "+" + digits;
  }

  /**
   * Initie un dépôt sur compte épargne via Wave (même flux que cotisations).
   * Crée une IntentionPaiement type DEPOT_EPARGNE, appelle Wave Checkout, retourne wave_launch_url.
   */
  @Transactional
  public dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse initierDepotEpargneEnLigne(
      dev.lions.unionflow.server.api.dto.paiement.request.InitierDepotEpargneRequest request) {

    Membre membreConnecte = getMembreConnecte();
    CompteEpargne compte = compteEpargneRepository.findByIdOptional(request.compteId())
        .orElseThrow(() -> new NotFoundException("Compte épargne non trouvé: " + request.compteId()));
    if (!compte.getMembre().getId().equals(membreConnecte.getId())) {
      throw new IllegalArgumentException("Ce compte épargne n'appartient pas au membre connecté");
    }

    String base = waveCheckoutService.getRedirectBaseUrl().replaceAll("/+$", "");
    BigDecimal montant = request.montant().setScale(0, java.math.RoundingMode.HALF_UP);
    String objetsCibles = "[{\"type\":\"DEPOT_EPARGNE\",\"compteId\":\"" + request.compteId() + "\",\"montant\":"
        + montant.toString() + "}]";

    IntentionPaiement intention = IntentionPaiement.builder()
        .utilisateur(membreConnecte)
        .organisation(compte.getOrganisation())
        .montantTotal(montant)
        .codeDevise("XOF")
        .typeObjet(TypeObjetIntentionPaiement.DEPOT_EPARGNE)
        .statut(StatutIntentionPaiement.INITIEE)
        .objetsCibles(objetsCibles)
        .build();
    intentionPaiementRepository.persist(intention);

    String successUrl = base + "/api/wave-redirect/success?ref=" + intention.getId();
    String errorUrl = base + "/api/wave-redirect/error?ref=" + intention.getId();
    String clientRef = intention.getId().toString();
    String amountStr = montant.toString();
    String restrictMobile = toE164(request.numeroTelephone());

    WaveCheckoutSessionResponse session;
    try {
      session = waveCheckoutService.createSession(
          amountStr, "XOF", successUrl, errorUrl, clientRef, restrictMobile);
    } catch (WaveCheckoutException e) {
      LOG.errorf(e, "Wave Checkout (dépôt épargne): %s", e.getMessage());
      intention.setStatut(StatutIntentionPaiement.ECHOUEE);
      intentionPaiementRepository.persist(intention);
      throw new jakarta.ws.rs.BadRequestException("Wave: " + e.getMessage());
    }

    intention.setWaveCheckoutSessionId(session.id);
    intention.setWaveLaunchUrl(session.waveLaunchUrl);
    intention.setStatut(StatutIntentionPaiement.EN_COURS);
    intentionPaiementRepository.persist(intention);

    LOG.infof("Dépôt épargne Wave initié: intention=%s, compte=%s, wave_launch_url=%s",
        intention.getId(), request.compteId(), session.waveLaunchUrl);

    return dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse.builder()
        .transactionId(intention.getId())
        .redirectUrl(session.waveLaunchUrl)
        .waveLaunchUrl(session.waveLaunchUrl)
        .waveCheckoutSessionId(session.id)
        .clientReference(clientRef)
        .montant(montant)
        .statut("EN_ATTENTE")
        .methodePaiement("WAVE")
        .message("Ouvrez l'application Wave pour confirmer le dépôt, puis vous serez renvoyé à UnionFlow.")
        .build();
  }

  /**
   * Déclare un paiement manuel (espèces, virement, chèque).
   * Le paiement est créé avec le statut EN_ATTENTE_VALIDATION.
   * Le trésorier doit le valider via une page admin.
   *
   * @param request Requête de déclaration de paiement manuel
   * @return Paiement créé (statut EN_ATTENTE_VALIDATION)
   */
  @Transactional
  public PaiementResponse declarerPaiementManuel(
      dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest request) {

    Membre membreConnecte = getMembreConnecte();
    LOG.infof("Déclaration paiement manuel pour membre %s: cotisation=%s, méthode=%s",
        membreConnecte.getNumeroMembre(), request.cotisationId(), request.methodePaiement());

    // Récupérer la cotisation
    dev.lions.unionflow.server.entity.Cotisation cotisation =
        paiementRepository.getEntityManager()
            .createQuery("SELECT c FROM Cotisation c WHERE c.id = :id", dev.lions.unionflow.server.entity.Cotisation.class)
            .setParameter("id", request.cotisationId())
            .getResultList().stream().findFirst()
            .orElseThrow(() -> new NotFoundException("Cotisation non trouvée: " + request.cotisationId()));

    // Vérifier que la cotisation appartient bien au membre connecté
    if (!cotisation.getMembre().getId().equals(membreConnecte.getId())) {
      throw new IllegalArgumentException("Cette cotisation n'appartient pas au membre connecté");
    }

    // Créer le paiement avec statut EN_ATTENTE_VALIDATION
    Paiement paiement = new Paiement();
    paiement.setNumeroReference("PAY-MANUEL-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
    paiement.setMontant(cotisation.getMontantDu());
    paiement.setCodeDevise("XOF"); // FCFA
    paiement.setMethodePaiement(request.methodePaiement());
    paiement.setStatutPaiement("EN_ATTENTE_VALIDATION");
    paiement.setMembre(membreConnecte);
    paiement.setReferenceExterne(request.reference());
    paiement.setCommentaire(request.commentaire());
    paiement.setDatePaiement(LocalDateTime.now()); // Date de déclaration
    paiement.setCreePar(membreConnecte.getEmail());

    paiementRepository.persist(paiement);

    // Notifier les trésoriers de l'organisation que ce paiement manuel attend validation
    try {
      membreOrganisationRepository.findFirstByMembreId(membreConnecte.getId())
          .map(mo -> mo.getOrganisation().getId())
          .ifPresent(orgId -> {
            List<UUID> tresorierIds = membreOrganisationRepository
                .findByRoleOrgAndOrganisationId("TRESORIER", orgId)
                .stream()
                .map(mo -> mo.getMembre().getId())
                .collect(Collectors.toList());
            if (!tresorierIds.isEmpty()) {
              notificationService.envoyerNotificationsGroupees(
                  tresorierIds,
                  "Validation paiement manuel requis",
                  "Le membre " + membreConnecte.getNumeroMembre()
                      + " a déclaré un paiement manuel (" + paiement.getNumeroReference()
                      + ") à valider.",
                  List.of("IN_APP"));
            }
          });
    } catch (Exception e) {
      LOG.warnf("Erreur notification trésorier pour paiement %s (non bloquant): %s",
          paiement.getNumeroReference(), e.getMessage());
    }

    LOG.infof("Paiement manuel déclaré avec succès: ID=%s, Référence=%s (EN_ATTENTE_VALIDATION)",
        paiement.getId(), paiement.getNumeroReference());

    return convertToResponse(paiement);
  }

  // ── Polling statut intention ──────────────────────────────────────────────

  /**
   * Retourne le statut d'une intention de paiement Wave.
   * Utilisé par le polling web (QR code) et le deep link mobile.
   */
  @Transactional
  public dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse getStatutIntention(UUID intentionId) {
    IntentionPaiement intention = intentionPaiementRepository.findById(intentionId);
    if (intention == null) {
      throw new NotFoundException("Intention de paiement non trouvée : " + intentionId);
    }

    if (intention.isCompletee()) {
      return buildIntentionStatutResponse(intention, "Paiement confirmé !");
    }
    if (StatutIntentionPaiement.EXPIREE.equals(intention.getStatut())
        || StatutIntentionPaiement.ECHOUEE.equals(intention.getStatut())) {
      return buildIntentionStatutResponse(intention,
          "Paiement " + intention.getStatut().name().toLowerCase());
    }
    if (intention.isExpiree()) {
      intention.setStatut(StatutIntentionPaiement.EXPIREE);
      intentionPaiementRepository.persist(intention);
      return buildIntentionStatutResponse(intention, "Session expirée, veuillez recommencer");
    }

    if (intention.getWaveCheckoutSessionId() != null) {
      try {
        WaveCheckoutService.WaveSessionStatusResponse waveStatus =
            waveCheckoutService.getSession(intention.getWaveCheckoutSessionId());
        if (waveStatus.isSucceeded()) {
          intention.setStatut(StatutIntentionPaiement.COMPLETEE);
          intentionPaiementRepository.persist(intention);
          return buildIntentionStatutResponse(intention, "Paiement confirmé !");
        } else if (waveStatus.isExpired()) {
          intention.setStatut(StatutIntentionPaiement.EXPIREE);
          intentionPaiementRepository.persist(intention);
          return buildIntentionStatutResponse(intention, "Session Wave expirée");
        }
      } catch (WaveCheckoutException e) {
        LOG.warnf(e, "Impossible de vérifier la session Wave %s",
            intention.getWaveCheckoutSessionId());
      }
    }

    return buildIntentionStatutResponse(intention, "En attente de confirmation Wave...");
  }

  private dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse buildIntentionStatutResponse(
      IntentionPaiement intention, String message) {
    return dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse.builder()
        .intentionId(intention.getId())
        .statut(intention.getStatut().name())
        .confirme(intention.isCompletee())
        .waveLaunchUrl(intention.getWaveLaunchUrl())
        .waveCheckoutSessionId(intention.getWaveCheckoutSessionId())
        .waveTransactionId(intention.getWaveTransactionId())
        .montant(intention.getMontantTotal())
        .message(message)
        .build();
  }

  // ── Webhook multi-provider ────────────────────────────────────────────────

  /**
   * Met à jour le statut d'un paiement depuis un événement webhook normalisé.
   * Appelé par PaymentOrchestrator.handleEvent() — aucun contexte utilisateur requis.
   */
  @Transactional
  public void mettreAJourStatutDepuisWebhook(PaymentEvent event) {
    Optional<Paiement> opt = paiementRepository.findByNumeroReference(event.reference());
    if (opt.isEmpty()) {
      LOG.warnf("Webhook reçu pour référence inconnue : %s (provider externalId=%s)",
          event.reference(), event.externalId());
      return;
    }
    Paiement paiement = opt.get();
    PaymentStatus status = event.status();

    if (PaymentStatus.SUCCESS.equals(status)) {
      paiement.setStatutPaiement("PAIEMENT_CONFIRME");
      paiement.setDateValidation(LocalDateTime.now());
      paiement.setReferenceExterne(event.externalId());
    } else if (PaymentStatus.FAILED.equals(status) || PaymentStatus.CANCELLED.equals(status)
        || PaymentStatus.EXPIRED.equals(status)) {
      paiement.setStatutPaiement("ANNULE");
      paiement.setReferenceExterne(event.externalId());
    }
    // INITIATED / PROCESSING : aucun changement de statut requis

    paiementRepository.persist(paiement);
    LOG.infof("Statut paiement mis à jour via webhook : ref=%s statut=%s → %s",
        event.reference(), status, paiement.getStatutPaiement());
  }

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

  /**
   * Récupère le membre connecté via SecurityIdentity.
   * Méthode helper réutilisable (Pattern DRY).
   *
   * @return Membre connecté
   * @throws NotFoundException si le membre n'est pas trouvé
   */
  private Membre getMembreConnecte() {
    String email = securityIdentity.getPrincipal().getName();
    LOG.debugf("Récupération du membre connecté: %s", email);

    return membreRepository.findByEmail(email)
        .orElseThrow(() -> new NotFoundException(
            "Membre non trouvé pour l'email: " + email + ". Veuillez contacter l'administrateur."));
  }

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

    PaiementResponse response = new PaiementResponse();
    response.setId(paiement.getId());
    response.setNumeroReference(paiement.getNumeroReference());
    response.setMontant(paiement.getMontant());
    response.setCodeDevise(paiement.getCodeDevise());
    response.setMethodePaiement(paiement.getMethodePaiement());
    response.setStatutPaiement(paiement.getStatutPaiement());
    response.setDatePaiement(paiement.getDatePaiement());
    response.setDateValidation(paiement.getDateValidation());
    response.setValidateur(paiement.getValidateur());
    response.setReferenceExterne(paiement.getReferenceExterne());
    response.setUrlPreuve(paiement.getUrlPreuve());
    response.setCommentaire(paiement.getCommentaire());

    if (paiement.getMembre() != null) {
      response.setMembreId(paiement.getMembre().getId());
      // On pourrait récupérer le nom complet via un appel si nom et prenom existent
      // response.setMembreNom(paiement.getMembre().getPrenom() + " " +
      // paiement.getMembre().getNom());
    }
    if (paiement.getTransactionWave() != null) {
      response.setTransactionWaveId(paiement.getTransactionWave().getId());
    }

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

    enrichirLibelles(paiement, response);

    return response;
  }

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

    String methodeLibelle = resolveLibelle("METHODE_PAIEMENT", paiement.getMethodePaiement(), null);
    String statutLibelle = resolveLibelle("STATUT_PAIEMENT", paiement.getStatutPaiement(), null);
    String statutSeverity = resolveSeverity("STATUT_PAIEMENT", paiement.getStatutPaiement(), null);

    return new PaiementSummaryResponse(
        paiement.getId(),
        paiement.getNumeroReference(),
        paiement.getMontant(),
        paiement.getCodeDevise(),
        methodeLibelle,
        paiement.getStatutPaiement(),
        statutLibelle,
        statutSeverity,
        paiement.getDatePaiement());
  }

  /** Enrichit la Response avec les libellés depuis types_reference */
  private void enrichirLibelles(Paiement paiement, PaiementResponse response) {
    if (paiement.getMethodePaiement() != null) {
      response.setMethodePaiementLibelle(resolveLibelle("METHODE_PAIEMENT", paiement.getMethodePaiement(), null));
    }
    if (paiement.getStatutPaiement() != null) {
      response.setStatutPaiementLibelle(resolveLibelle("STATUT_PAIEMENT", paiement.getStatutPaiement(), null));
      response.setStatutPaiementSeverity(resolveSeverity("STATUT_PAIEMENT", paiement.getStatutPaiement(), null));
    }
  }

  private String resolveLibelle(String domaine, String code, UUID organisationId) {
    if (code == null)
      return null;
    return typeReferenceRepository.findByDomaineAndCode(domaine, code)
        .map(dev.lions.unionflow.server.entity.TypeReference::getLibelle)
        .orElse(code);
  }

  private String resolveSeverity(String domaine, String code, UUID organisationId) {
    if (code == null)
      return null;
    return typeReferenceRepository.findByDomaineAndCode(domaine, code)
        .map(dev.lions.unionflow.server.entity.TypeReference::getSeverity)
        .orElse("info");
  }
}
