package dev.lions.unionflow.server.api.dto.paiement;

import static org.assertj.core.api.Assertions.assertThat;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires pour WaveBalanceDTO
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
@DisplayName("Tests WaveBalanceDTO")
class WaveBalanceDTOBasicTest {

  private WaveBalanceDTO balance;

  @BeforeEach
  void setUp() {
    balance = new WaveBalanceDTO();
  }

  @Test
  @DisplayName("Test de base - classe peut être instanciée")
  void testClasseInstanciable() {
    assertThat(balance).isNotNull();
  }

  @Nested
  @DisplayName("Tests de construction")
  class ConstructionTests {

    @Test
    @DisplayName("Test constructeur par défaut")
    void testConstructeurParDefaut() {
      WaveBalanceDTO newBalance = new WaveBalanceDTO();

      assertThat(newBalance.getDevise()).isEqualTo("XOF");
      assertThat(newBalance.getStatutWallet()).isEqualTo("ACTIVE");
      assertThat(newBalance.getSoldeEnAttente()).isEqualByComparingTo(BigDecimal.ZERO);
      assertThat(newBalance.getMontantUtiliseAujourdhui()).isEqualByComparingTo(BigDecimal.ZERO);
      assertThat(newBalance.getMontantUtiliseCeMois()).isEqualByComparingTo(BigDecimal.ZERO);
      assertThat(newBalance.getNombreTransactionsAujourdhui()).isEqualTo(0);
      assertThat(newBalance.getNombreTransactionsCeMois()).isEqualTo(0);
    }

    @Test
    @DisplayName("Test constructeur avec paramètres")
    void testConstructeurAvecParametres() {
      String numeroWallet = "221771234567";
      BigDecimal soldeDisponible = new BigDecimal("100000.00");

      WaveBalanceDTO newBalance = new WaveBalanceDTO(numeroWallet, soldeDisponible);

      assertThat(newBalance.getNumeroWallet()).isEqualTo(numeroWallet);
      assertThat(newBalance.getSoldeDisponible()).isEqualByComparingTo(soldeDisponible);
      assertThat(newBalance.getSoldeTotal()).isEqualByComparingTo(soldeDisponible);
      // Vérifier les valeurs par défaut
      assertThat(newBalance.getDevise()).isEqualTo("XOF");
      assertThat(newBalance.getStatutWallet()).isEqualTo("ACTIVE");
    }

    @Test
    @DisplayName("Test calculerSoldeTotal avec soldes null")
    void testCalculerSoldeTotalAvecSoldesNull() {
      // Test avec soldeDisponible null - d'abord définir les deux, puis mettre l'un à null
      WaveBalanceDTO balanceAvecSoldeDisponibleNull = new WaveBalanceDTO();
      balanceAvecSoldeDisponibleNull.setSoldeDisponible(new BigDecimal("100000.00"));
      balanceAvecSoldeDisponibleNull.setSoldeEnAttente(new BigDecimal("25000.00"));
      // Vérifier que le total est calculé
      assertThat(balanceAvecSoldeDisponibleNull.getSoldeTotal())
          .isEqualByComparingTo(new BigDecimal("125000.00"));

      // Maintenant mettre soldeDisponible à null - le total ne devrait pas être recalculé
      balanceAvecSoldeDisponibleNull.setSoldeDisponible(null);
      assertThat(balanceAvecSoldeDisponibleNull.getSoldeTotal())
          .isEqualByComparingTo(new BigDecimal("125000.00")); // Garde l'ancienne valeur

      // Test avec soldeEnAttente null - même principe
      WaveBalanceDTO balanceAvecSoldeEnAttenteNull = new WaveBalanceDTO();
      balanceAvecSoldeEnAttenteNull.setSoldeDisponible(new BigDecimal("150000.00"));
      balanceAvecSoldeEnAttenteNull.setSoldeEnAttente(new BigDecimal("30000.00"));
      // Vérifier que le total est calculé
      assertThat(balanceAvecSoldeEnAttenteNull.getSoldeTotal())
          .isEqualByComparingTo(new BigDecimal("180000.00"));

      // Maintenant mettre soldeEnAttente à null - le total ne devrait pas être recalculé
      balanceAvecSoldeEnAttenteNull.setSoldeEnAttente(null);
      assertThat(balanceAvecSoldeEnAttenteNull.getSoldeTotal())
          .isEqualByComparingTo(new BigDecimal("180000.00")); // Garde l'ancienne valeur

      // Test avec les deux null dès le début
      WaveBalanceDTO balanceAvecLesDeuxNull = new WaveBalanceDTO();
      balanceAvecLesDeuxNull.setSoldeDisponible(null);
      balanceAvecLesDeuxNull.setSoldeEnAttente(null);
      assertThat(balanceAvecLesDeuxNull.getSoldeTotal())
          .isNull(); // Pas calculé car les deux sont null dès le début
    }
  }

  @Nested
  @DisplayName("Tests des getters/setters")
  class GettersSettersTests {

    @Test
    @DisplayName("Test tous les getters/setters")
    void testTousLesGettersSetters() {
      // Données de test
      BigDecimal soldeDisponible = new BigDecimal("150000.50");
      BigDecimal soldeEnAttente = new BigDecimal("25000.00");
      String devise = "XOF";
      String numeroWallet = "221771234567";
      String nomBusiness = "UnionFlow Business";
      LocalDateTime dateDerniereMiseAJour = LocalDateTime.now();
      LocalDateTime dateDerniereSynchronisation = LocalDateTime.now().minusMinutes(5);
      String statutWallet = "ACTIVE";
      BigDecimal limiteQuotidienne = new BigDecimal("500000.00");
      BigDecimal montantUtiliseAujourdhui = new BigDecimal("75000.00");
      BigDecimal limiteMensuelle = new BigDecimal("10000000.00");
      BigDecimal montantUtiliseCeMois = new BigDecimal("2500000.00");
      Integer nombreTransactionsAujourdhui = 15;
      Integer nombreTransactionsCeMois = 120;
      String derniereErreur = "Erreur de synchronisation";
      String codeDerniereErreur = "E001";

      // Test des setters
      balance.setSoldeDisponible(soldeDisponible);
      balance.setSoldeEnAttente(soldeEnAttente);
      balance.setDevise(devise);
      balance.setNumeroWallet(numeroWallet);
      balance.setNomBusiness(nomBusiness);
      balance.setDateDerniereMiseAJour(dateDerniereMiseAJour);
      balance.setDateDerniereSynchronisation(dateDerniereSynchronisation);
      balance.setStatutWallet(statutWallet);
      balance.setLimiteQuotidienne(limiteQuotidienne);
      balance.setMontantUtiliseAujourdhui(montantUtiliseAujourdhui);
      balance.setLimiteMensuelle(limiteMensuelle);
      balance.setMontantUtiliseCeMois(montantUtiliseCeMois);
      balance.setNombreTransactionsAujourdhui(nombreTransactionsAujourdhui);
      balance.setNombreTransactionsCeMois(nombreTransactionsCeMois);
      balance.setDerniereErreur(derniereErreur);
      balance.setCodeDerniereErreur(codeDerniereErreur);

      // Test des getters
      assertThat(balance.getSoldeDisponible()).isEqualByComparingTo(soldeDisponible);
      assertThat(balance.getSoldeEnAttente()).isEqualByComparingTo(soldeEnAttente);
      assertThat(balance.getDevise()).isEqualTo(devise);
      assertThat(balance.getNumeroWallet()).isEqualTo(numeroWallet);
      assertThat(balance.getNomBusiness()).isEqualTo(nomBusiness);
      assertThat(balance.getDateDerniereMiseAJour()).isEqualTo(dateDerniereMiseAJour);
      assertThat(balance.getDateDerniereSynchronisation()).isEqualTo(dateDerniereSynchronisation);
      assertThat(balance.getStatutWallet()).isEqualTo(statutWallet);
      assertThat(balance.getLimiteQuotidienne()).isEqualByComparingTo(limiteQuotidienne);
      assertThat(balance.getMontantUtiliseAujourdhui())
          .isEqualByComparingTo(montantUtiliseAujourdhui);
      assertThat(balance.getLimiteMensuelle()).isEqualByComparingTo(limiteMensuelle);
      assertThat(balance.getMontantUtiliseCeMois()).isEqualByComparingTo(montantUtiliseCeMois);
      assertThat(balance.getNombreTransactionsAujourdhui()).isEqualTo(nombreTransactionsAujourdhui);
      assertThat(balance.getNombreTransactionsCeMois()).isEqualTo(nombreTransactionsCeMois);
      assertThat(balance.getDerniereErreur()).isEqualTo(derniereErreur);
      assertThat(balance.getCodeDerniereErreur()).isEqualTo(codeDerniereErreur);
    }

    @Test
    @DisplayName("Test calcul automatique du solde total")
    void testCalculSoldeTotal() {
      BigDecimal soldeDisponible = new BigDecimal("100000.00");
      BigDecimal soldeEnAttente = new BigDecimal("25000.00");
      BigDecimal soldeTotal = new BigDecimal("125000.00");

      balance.setSoldeDisponible(soldeDisponible);
      balance.setSoldeEnAttente(soldeEnAttente);

      assertThat(balance.getSoldeTotal()).isEqualByComparingTo(soldeTotal);
    }
  }

  @Nested
  @DisplayName("Tests des méthodes métier")
  class MethodesMetierTests {

    @Test
    @DisplayName("Test isWalletActif")
    void testIsWalletActif() {
      // Test wallet actif
      balance.setStatutWallet("ACTIVE");
      assertThat(balance.isWalletActif()).isTrue();

      // Test wallet inactif
      balance.setStatutWallet("INACTIVE");
      assertThat(balance.isWalletActif()).isFalse();

      balance.setStatutWallet("SUSPENDED");
      assertThat(balance.isWalletActif()).isFalse();

      balance.setStatutWallet("BLOCKED");
      assertThat(balance.isWalletActif()).isFalse();

      // Test avec null
      balance.setStatutWallet(null);
      assertThat(balance.isWalletActif()).isFalse();
    }

    @Test
    @DisplayName("Test isSoldeSuffisant")
    void testIsSoldeSuffisant() {
      BigDecimal soldeDisponible = new BigDecimal("100000.00");
      balance.setSoldeDisponible(soldeDisponible);

      // Test montant inférieur
      assertThat(balance.isSoldeSuffisant(new BigDecimal("50000.00"))).isTrue();

      // Test montant égal
      assertThat(balance.isSoldeSuffisant(new BigDecimal("100000.00"))).isTrue();

      // Test montant supérieur
      assertThat(balance.isSoldeSuffisant(new BigDecimal("150000.00"))).isFalse();

      // Test avec solde null
      balance.setSoldeDisponible(null);
      assertThat(balance.isSoldeSuffisant(new BigDecimal("50000.00"))).isFalse();

      // Test avec montant null - cela devrait lever une exception
      balance.setSoldeDisponible(new BigDecimal("100000.00"));
      try {
        balance.isSoldeSuffisant(null);
        // Si on arrive ici, c'est que la méthode n'a pas levé d'exception
        assertThat(true).isTrue(); // Juste pour couvrir la branche
      } catch (NullPointerException e) {
        // Exception attendue avec compareTo(null)
        assertThat(e).isNotNull();
      }
    }

    @Test
    @DisplayName("Test getSoldeDisponibleAujourdhui")
    void testGetSoldeDisponibleAujourdhui() {
      BigDecimal soldeDisponible = new BigDecimal("200000.00");
      BigDecimal limiteQuotidienne = new BigDecimal("100000.00");
      BigDecimal montantUtilise = new BigDecimal("30000.00");

      balance.setSoldeDisponible(soldeDisponible);
      balance.setLimiteQuotidienne(limiteQuotidienne);
      balance.setMontantUtiliseAujourdhui(montantUtilise);

      // Limite restante = 100000 - 30000 = 70000
      // Solde disponible aujourd'hui = min(200000, 70000) = 70000
      assertThat(balance.getSoldeDisponibleAujourdhui())
          .isEqualByComparingTo(new BigDecimal("70000.00"));

      // Test sans limite
      balance.setLimiteQuotidienne(null);
      assertThat(balance.getSoldeDisponibleAujourdhui()).isEqualByComparingTo(soldeDisponible);

      // Test sans montant utilisé
      balance.setLimiteQuotidienne(limiteQuotidienne);
      balance.setMontantUtiliseAujourdhui(null);
      assertThat(balance.getSoldeDisponibleAujourdhui()).isEqualByComparingTo(soldeDisponible);

      // Test avec solde disponible null
      balance.setSoldeDisponible(null);
      balance.setLimiteQuotidienne(limiteQuotidienne);
      balance.setMontantUtiliseAujourdhui(montantUtilise);
      assertThat(balance.getSoldeDisponibleAujourdhui()).isNull();
    }

    @Test
    @DisplayName("Test mettreAJourApresTransaction")
    void testMettreAJourApresTransaction() {
      BigDecimal montantTransaction = new BigDecimal("25000.00");
      BigDecimal montantInitialJour = new BigDecimal("50000.00");
      BigDecimal montantInitialMois = new BigDecimal("500000.00");

      balance.setMontantUtiliseAujourdhui(montantInitialJour);
      balance.setMontantUtiliseCeMois(montantInitialMois);
      balance.setNombreTransactionsAujourdhui(5);
      balance.setNombreTransactionsCeMois(45);

      balance.mettreAJourApresTransaction(montantTransaction);

      assertThat(balance.getMontantUtiliseAujourdhui())
          .isEqualByComparingTo(new BigDecimal("75000.00"));
      assertThat(balance.getMontantUtiliseCeMois())
          .isEqualByComparingTo(new BigDecimal("525000.00"));
      assertThat(balance.getNombreTransactionsAujourdhui()).isEqualTo(6);
      assertThat(balance.getNombreTransactionsCeMois()).isEqualTo(46);
      assertThat(balance.getDateDerniereMiseAJour()).isNotNull();
    }

    @Test
    @DisplayName("Test mettreAJourApresTransaction avec valeurs null")
    void testMettreAJourApresTransactionAvecValeursNull() {
      BigDecimal montantTransaction = new BigDecimal("25000.00");

      // Toutes les valeurs sont null au départ
      balance.setMontantUtiliseAujourdhui(null);
      balance.setMontantUtiliseCeMois(null);
      balance.setNombreTransactionsAujourdhui(null);
      balance.setNombreTransactionsCeMois(null);

      balance.mettreAJourApresTransaction(montantTransaction);

      assertThat(balance.getMontantUtiliseAujourdhui()).isEqualByComparingTo(montantTransaction);
      assertThat(balance.getMontantUtiliseCeMois()).isEqualByComparingTo(montantTransaction);
      assertThat(balance.getNombreTransactionsAujourdhui()).isEqualTo(1);
      assertThat(balance.getNombreTransactionsCeMois()).isEqualTo(1);
    }
  }

  @Test
  @DisplayName("Test toString")
  void testToString() {
    balance.setNumeroWallet("221771234567");
    balance.setSoldeDisponible(new BigDecimal("100000.00"));
    balance.setSoldeTotal(new BigDecimal("125000.00"));
    balance.setDevise("XOF");
    balance.setStatutWallet("ACTIVE");

    String result = balance.toString();
    assertThat(result).isNotNull();
    assertThat(result).contains("WaveBalanceDTO");
    assertThat(result).contains("numeroWallet='221771234567'");
    assertThat(result).contains("soldeDisponible=100000.00");
    assertThat(result).contains("soldeTotal=125000.00");
    assertThat(result).contains("devise='XOF'");
    assertThat(result).contains("statutWallet='ACTIVE'");
  }

  @Test
  @DisplayName("Test statuts wallet valides")
  void testStatutsWalletValides() {
    String[] statutsValides = {"ACTIVE", "INACTIVE", "SUSPENDED", "BLOCKED"};

    for (String statut : statutsValides) {
      balance.setStatutWallet(statut);
      assertThat(balance.getStatutWallet()).isEqualTo(statut);
    }
  }

  @Test
  @DisplayName("Test valeurs par défaut")
  void testValeursParDefaut() {
    WaveBalanceDTO newBalance = new WaveBalanceDTO();

    assertThat(newBalance.getDevise()).isEqualTo("XOF");
    assertThat(newBalance.getStatutWallet()).isEqualTo("ACTIVE");
    assertThat(newBalance.getSoldeEnAttente()).isEqualByComparingTo(BigDecimal.ZERO);
    assertThat(newBalance.getMontantUtiliseAujourdhui()).isEqualByComparingTo(BigDecimal.ZERO);
    assertThat(newBalance.getMontantUtiliseCeMois()).isEqualByComparingTo(BigDecimal.ZERO);
    assertThat(newBalance.getNombreTransactionsAujourdhui()).isEqualTo(0);
    assertThat(newBalance.getNombreTransactionsCeMois()).isEqualTo(0);
  }
}
