package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
import dev.lions.unionflow.server.entity.CompteWave;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.TransactionWave;
import dev.lions.unionflow.server.repository.CompteWaveRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.TransactionWaveRepository;
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 l'intégration Wave Mobile Money
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@ApplicationScoped
public class WaveService {

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

  @Inject
  CompteWaveRepository compteWaveRepository;

  @Inject
  TransactionWaveRepository transactionWaveRepository;

  @Inject
  OrganisationRepository organisationRepository;

  @Inject
  MembreRepository membreRepository;

  @Inject
  KeycloakService keycloakService;

  @Inject
  DefaultsService defaultsService;

  /**
   * Crée un nouveau compte Wave
   *
   * @param compteWaveDTO DTO du compte à créer
   * @return DTO du compte créé
   */
  @Transactional
  public CompteWaveDTO creerCompteWave(CompteWaveDTO compteWaveDTO) {
    LOG.infof("Création d'un nouveau compte Wave: %s", compteWaveDTO.getNumeroTelephone());

    // Vérifier l'unicité du numéro de téléphone
    if (compteWaveRepository.findByNumeroTelephone(compteWaveDTO.getNumeroTelephone()).isPresent()) {
      throw new IllegalArgumentException(
          "Un compte Wave existe déjà pour ce numéro: " + compteWaveDTO.getNumeroTelephone());
    }

    CompteWave compteWave = convertToEntity(compteWaveDTO);
    compteWave.setCreePar(keycloakService.getCurrentUserEmail());

    compteWaveRepository.persist(compteWave);
    LOG.infof("Compte Wave créé avec succès: ID=%s, Téléphone=%s", compteWave.getId(), compteWave.getNumeroTelephone());

    return convertToDTO(compteWave);
  }

  /**
   * Met à jour un compte Wave
   *
   * @param id            ID du compte
   * @param compteWaveDTO DTO avec les modifications
   * @return DTO du compte mis à jour
   */
  @Transactional
  public CompteWaveDTO mettreAJourCompteWave(UUID id, CompteWaveDTO compteWaveDTO) {
    LOG.infof("Mise à jour du compte Wave ID: %s", id);

    CompteWave compteWave = compteWaveRepository
        .findCompteWaveById(id)
        .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));

    updateFromDTO(compteWave, compteWaveDTO);
    compteWave.setModifiePar(keycloakService.getCurrentUserEmail());

    compteWaveRepository.persist(compteWave);
    LOG.infof("Compte Wave mis à jour avec succès: ID=%s", id);

    return convertToDTO(compteWave);
  }

  /**
   * Vérifie un compte Wave
   *
   * @param id ID du compte
   * @return DTO du compte vérifié
   */
  @Transactional
  public CompteWaveDTO verifierCompteWave(UUID id) {
    LOG.infof("Vérification du compte Wave ID: %s", id);

    CompteWave compteWave = compteWaveRepository
        .findCompteWaveById(id)
        .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));

    compteWave.setStatutCompte(StatutCompteWave.VERIFIE);
    compteWave.setDateDerniereVerification(LocalDateTime.now());
    compteWave.setModifiePar(keycloakService.getCurrentUserEmail());

    compteWaveRepository.persist(compteWave);
    LOG.infof("Compte Wave vérifié avec succès: ID=%s", id);

    return convertToDTO(compteWave);
  }

  /**
   * Trouve un compte Wave par son ID
   *
   * @param id ID du compte
   * @return DTO du compte
   */
  public CompteWaveDTO trouverCompteWaveParId(UUID id) {
    return compteWaveRepository
        .findCompteWaveById(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));
  }

  /**
   * Trouve un compte Wave par numéro de téléphone
   *
   * @param numeroTelephone Numéro de téléphone
   * @return DTO du compte ou null
   */
  public CompteWaveDTO trouverCompteWaveParTelephone(String numeroTelephone) {
    return compteWaveRepository
        .findByNumeroTelephone(numeroTelephone)
        .map(this::convertToDTO)
        .orElse(null);
  }

  /**
   * Liste tous les comptes Wave d'une organisation
   *
   * @param organisationId ID de l'organisation
   * @return Liste des comptes Wave
   */
  public List<CompteWaveDTO> listerComptesWaveParOrganisation(UUID organisationId) {
    return compteWaveRepository.findByOrganisationId(organisationId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  /**
   * Crée une nouvelle transaction Wave
   *
   * @param transactionWaveDTO DTO de la transaction à créer
   * @return DTO de la transaction créée
   */
  @Transactional
  public TransactionWaveDTO creerTransactionWave(TransactionWaveDTO transactionWaveDTO) {
    LOG.infof("Création d'une nouvelle transaction Wave: %s", transactionWaveDTO.getWaveTransactionId());

    TransactionWave transactionWave = convertToEntity(transactionWaveDTO);
    transactionWave.setCreePar(keycloakService.getCurrentUserEmail());

    transactionWaveRepository.persist(transactionWave);
    LOG.infof(
        "Transaction Wave créée avec succès: ID=%s, WaveTransactionId=%s",
        transactionWave.getId(), transactionWave.getWaveTransactionId());

    return convertToDTO(transactionWave);
  }

  /**
   * Met à jour le statut d'une transaction Wave
   *
   * @param waveTransactionId Identifiant Wave de la transaction
   * @param nouveauStatut     Nouveau statut
   * @return DTO de la transaction mise à jour
   */
  @Transactional
  public TransactionWaveDTO mettreAJourStatutTransaction(
      String waveTransactionId, StatutTransactionWave nouveauStatut) {
    LOG.infof("Mise à jour du statut de la transaction Wave: %s -> %s", waveTransactionId, nouveauStatut);

    TransactionWave transactionWave = transactionWaveRepository
        .findByWaveTransactionId(waveTransactionId)
        .orElseThrow(
            () -> new NotFoundException(
                "Transaction Wave non trouvée avec l'ID: " + waveTransactionId));

    transactionWave.setStatutTransaction(nouveauStatut);
    transactionWave.setDateDerniereTentative(LocalDateTime.now());
    transactionWave.setModifiePar(keycloakService.getCurrentUserEmail());

    transactionWaveRepository.persist(transactionWave);
    LOG.infof("Statut de la transaction Wave mis à jour: %s", waveTransactionId);

    return convertToDTO(transactionWave);
  }

  /**
   * Trouve une transaction Wave par son identifiant Wave
   *
   * @param waveTransactionId Identifiant Wave
   * @return DTO de la transaction
   */
  public TransactionWaveDTO trouverTransactionWaveParId(String waveTransactionId) {
    return transactionWaveRepository
        .findByWaveTransactionId(waveTransactionId)
        .map(this::convertToDTO)
        .orElseThrow(
            () -> new NotFoundException("Transaction Wave non trouvée avec l'ID: " + waveTransactionId));
  }

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

  /** Convertit une entité CompteWave en DTO */
  private CompteWaveDTO convertToDTO(CompteWave compteWave) {
    if (compteWave == null) {
      return null;
    }

    CompteWaveDTO dto = new CompteWaveDTO();
    dto.setId(compteWave.getId());
    dto.setNumeroTelephone(compteWave.getNumeroTelephone());
    dto.setStatutCompte(compteWave.getStatutCompte());
    dto.setWaveAccountId(compteWave.getWaveAccountId());
    dto.setEnvironnement(compteWave.getEnvironnement());
    dto.setDateDerniereVerification(compteWave.getDateDerniereVerification());
    dto.setCommentaire(compteWave.getCommentaire());

    if (compteWave.getOrganisation() != null) {
      dto.setOrganisationId(compteWave.getOrganisation().getId());
    }
    if (compteWave.getMembre() != null) {
      dto.setMembreId(compteWave.getMembre().getId());
    }

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

    return dto;
  }

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

    CompteWave compteWave = new CompteWave();
    compteWave.setNumeroTelephone(dto.getNumeroTelephone());
    compteWave.setStatutCompte(dto.getStatutCompte() != null ? dto.getStatutCompte() : StatutCompteWave.NON_VERIFIE);
    compteWave.setWaveAccountId(dto.getWaveAccountId());
    compteWave.setEnvironnement(dto.getEnvironnement() != null ? dto.getEnvironnement() : "SANDBOX");
    compteWave.setDateDerniereVerification(dto.getDateDerniereVerification());
    compteWave.setCommentaire(dto.getCommentaire());

    // Relations
    if (dto.getOrganisationId() != null) {
      Organisation org = organisationRepository
          .findByIdOptional(dto.getOrganisationId())
          .orElseThrow(
              () -> new NotFoundException(
                  "Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
      compteWave.setOrganisation(org);
    }

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

    return compteWave;
  }

  /** Met à jour une entité CompteWave à partir d'un DTO */
  private void updateFromDTO(CompteWave compteWave, CompteWaveDTO dto) {
    if (dto.getStatutCompte() != null) {
      compteWave.setStatutCompte(dto.getStatutCompte());
    }
    if (dto.getWaveAccountId() != null) {
      compteWave.setWaveAccountId(dto.getWaveAccountId());
    }
    if (dto.getEnvironnement() != null) {
      compteWave.setEnvironnement(dto.getEnvironnement());
    }
    if (dto.getDateDerniereVerification() != null) {
      compteWave.setDateDerniereVerification(dto.getDateDerniereVerification());
    }
    if (dto.getCommentaire() != null) {
      compteWave.setCommentaire(dto.getCommentaire());
    }
  }

  /** Convertit une entité TransactionWave en DTO */
  private TransactionWaveDTO convertToDTO(TransactionWave transactionWave) {
    if (transactionWave == null) {
      return null;
    }

    TransactionWaveDTO dto = new TransactionWaveDTO();
    dto.setId(transactionWave.getId());
    dto.setWaveTransactionId(transactionWave.getWaveTransactionId());
    dto.setWaveRequestId(transactionWave.getWaveRequestId());
    dto.setWaveReference(transactionWave.getWaveReference());
    dto.setTypeTransaction(transactionWave.getTypeTransaction());
    dto.setStatutTransaction(transactionWave.getStatutTransaction());
    dto.setMontant(transactionWave.getMontant());
    dto.setFrais(transactionWave.getFrais());
    dto.setMontantNet(transactionWave.getMontantNet());
    dto.setCodeDevise(transactionWave.getCodeDevise());
    dto.setTelephonePayeur(transactionWave.getTelephonePayeur());
    dto.setTelephoneBeneficiaire(transactionWave.getTelephoneBeneficiaire());
    dto.setMetadonnees(transactionWave.getMetadonnees());
    dto.setNombreTentatives(transactionWave.getNombreTentatives());
    dto.setDateDerniereTentative(transactionWave.getDateDerniereTentative());
    dto.setMessageErreur(transactionWave.getMessageErreur());

    if (transactionWave.getCompteWave() != null) {
      dto.setCompteWaveId(transactionWave.getCompteWave().getId());
    }

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

    return dto;
  }

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

    TransactionWave transactionWave = new TransactionWave();
    transactionWave.setWaveTransactionId(dto.getWaveTransactionId());
    transactionWave.setWaveRequestId(dto.getWaveRequestId());
    transactionWave.setWaveReference(dto.getWaveReference());
    transactionWave.setTypeTransaction(dto.getTypeTransaction());
    transactionWave.setStatutTransaction(
        dto.getStatutTransaction() != null
            ? dto.getStatutTransaction()
            : StatutTransactionWave.INITIALISE);
    transactionWave.setMontant(dto.getMontant());
    transactionWave.setFrais(dto.getFrais());
    transactionWave.setMontantNet(dto.getMontantNet());
    transactionWave.setCodeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : defaultsService.getDevise());
    transactionWave.setTelephonePayeur(dto.getTelephonePayeur());
    transactionWave.setTelephoneBeneficiaire(dto.getTelephoneBeneficiaire());
    transactionWave.setMetadonnees(dto.getMetadonnees());
    transactionWave.setNombreTentatives(dto.getNombreTentatives() != null ? dto.getNombreTentatives() : 0);
    transactionWave.setDateDerniereTentative(dto.getDateDerniereTentative());
    transactionWave.setMessageErreur(dto.getMessageErreur());

    // Relation CompteWave
    if (dto.getCompteWaveId() != null) {
      CompteWave compteWave = compteWaveRepository
          .findCompteWaveById(dto.getCompteWaveId())
          .orElseThrow(
              () -> new NotFoundException(
                  "Compte Wave non trouvé avec l'ID: " + dto.getCompteWaveId()));
      transactionWave.setCompteWave(compteWave);
    }

    return transactionWave;
  }
}
