package dev.lions.unionflow.server.api.dto.finance.response;

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

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires pour AdhesionResponse.
 * Couvre toutes les méthodes utilitaires et toutes les branches des switch (statut, méthode paiement).
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests AdhesionResponse")
class AdhesionResponseTest {

  @Nested
  @DisplayName("isPayeeIntegralement")
  class IsPayeeIntegralement {

    @Test
    @DisplayName("retourne false quand montantPaye ou fraisAdhesion null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.isPayeeIntegralement()).isFalse();
      r.setFraisAdhesion(BigDecimal.TEN);
      assertThat(r.isPayeeIntegralement()).isFalse();
      r.setMontantPaye(BigDecimal.TEN);
      r.setFraisAdhesion(null);
      assertThat(r.isPayeeIntegralement()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand montantPaye >= fraisAdhesion")
    void testPaye() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(100)).build();
      assertThat(r.isPayeeIntegralement()).isTrue();
      r.setMontantPaye(BigDecimal.valueOf(150));
      assertThat(r.isPayeeIntegralement()).isTrue();
    }

    @Test
    @DisplayName("retourne false quand montantPaye < fraisAdhesion")
    void testNonPaye() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(50)).build();
      assertThat(r.isPayeeIntegralement()).isFalse();
    }
  }

  @Nested
  @DisplayName("isEnAttentePaiement")
  class IsEnAttentePaiement {

    @Test
    @DisplayName("retourne true quand APPROUVEE et pas payé intégralement")
    void testEnAttente() {
      AdhesionResponse r = AdhesionResponse.builder()
          .statut("APPROUVEE")
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(50)).build();
      assertThat(r.isEnAttentePaiement()).isTrue();
    }

    @Test
    @DisplayName("retourne false quand payé intégralement")
    void testPaye() {
      AdhesionResponse r = AdhesionResponse.builder()
          .statut("APPROUVEE")
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(100)).build();
      assertThat(r.isEnAttentePaiement()).isFalse();
    }

    @Test
    @DisplayName("retourne false quand statut différent de APPROUVEE")
    void testAutreStatut() {
      AdhesionResponse r = AdhesionResponse.builder()
          .statut("EN_ATTENTE")
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.ZERO).build();
      assertThat(r.isEnAttentePaiement()).isFalse();
    }
  }

  @Nested
  @DisplayName("getMontantRestant")
  class GetMontantRestant {

    @Test
    @DisplayName("retourne 0 quand fraisAdhesion null")
    void testFraisNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);
    }

    @Test
    @DisplayName("retourne fraisAdhesion quand montantPaye null")
    void testMontantPayeNull() {
      AdhesionResponse r = AdhesionResponse.builder().fraisAdhesion(BigDecimal.valueOf(100)).build();
      assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.valueOf(100));
    }

    @Test
    @DisplayName("retourne frais - payé si positif")
    void testRestant() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(30)).build();
      assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.valueOf(70));
    }

    @Test
    @DisplayName("retourne 0 quand payé >= frais")
    void testZeroRestant() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(100)).build();
      assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);
      r.setMontantPaye(BigDecimal.valueOf(150));
      assertThat(r.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);
    }
  }

  @Nested
  @DisplayName("getPourcentagePaiement")
  class GetPourcentagePaiement {

    @Test
    @DisplayName("retourne 0 quand fraisAdhesion null ou zéro")
    void testZero() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getPourcentagePaiement()).isEqualTo(0);
      r.setFraisAdhesion(BigDecimal.ZERO);
      r.setMontantPaye(BigDecimal.TEN);
      assertThat(r.getPourcentagePaiement()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne 0 quand montantPaye null")
    void testMontantNull() {
      AdhesionResponse r = AdhesionResponse.builder().fraisAdhesion(BigDecimal.valueOf(100)).build();
      assertThat(r.getPourcentagePaiement()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne le pourcentage arrondi")
    void testPourcentage() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(50)).build();
      assertThat(r.getPourcentagePaiement()).isEqualTo(50);
      r.setMontantPaye(BigDecimal.valueOf(100));
      assertThat(r.getPourcentagePaiement()).isEqualTo(100);
    }
  }

  @Nested
  @DisplayName("getJoursDepuisDemande")
  class GetJoursDepuisDemande {

    @Test
    @DisplayName("retourne 0 quand dateDemande null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getJoursDepuisDemande()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne les jours entre dateDemande et aujourd'hui")
    void testJours() {
      AdhesionResponse r = AdhesionResponse.builder()
          .dateDemande(LocalDate.now().minusDays(5)).build();
      assertThat(r.getJoursDepuisDemande()).isEqualTo(5);
    }
  }

  @Nested
  @DisplayName("getStatutLibelle")
  class GetStatutLibelle {

    @Test
    @DisplayName("retourne Non défini quand statut null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getStatutLibelle()).isEqualTo("Non défini");
    }

    @Test
    @DisplayName("retourne le libellé pour chaque statut connu")
    void testStatuts() {
      assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutLibelle()).isEqualTo("En attente");
      assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutLibelle()).isEqualTo("Approuvée");
      assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutLibelle()).isEqualTo("Rejetée");
      assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutLibelle()).isEqualTo("Annulée");
      assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutLibelle()).isEqualTo("En paiement");
      assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutLibelle()).isEqualTo("Payée");
    }

    @Test
    @DisplayName("retourne statut tel quel pour valeur inconnue")
    void testDefault() {
      AdhesionResponse r = AdhesionResponse.builder().statut("AUTRE").build();
      assertThat(r.getStatutLibelle()).isEqualTo("AUTRE");
    }
  }

  @Nested
  @DisplayName("getStatutSeverity")
  class GetStatutSeverity {

    @Test
    @DisplayName("retourne secondary quand statut null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getStatutSeverity()).isEqualTo("secondary");
    }

    @Test
    @DisplayName("retourne success pour APPROUVEE et PAYEE")
    void testSuccess() {
      assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutSeverity()).isEqualTo("success");
      assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutSeverity()).isEqualTo("success");
    }

    @Test
    @DisplayName("retourne warning pour EN_ATTENTE et EN_PAIEMENT")
    void testWarning() {
      assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutSeverity()).isEqualTo("warning");
      assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutSeverity()).isEqualTo("warning");
    }

    @Test
    @DisplayName("retourne danger pour REJETEE")
    void testDanger() {
      assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutSeverity()).isEqualTo("danger");
    }

    @Test
    @DisplayName("retourne secondary pour ANNULEE et défaut")
    void testSecondary() {
      assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutSeverity()).isEqualTo("secondary");
      assertThat(AdhesionResponse.builder().statut("INCONNU").build().getStatutSeverity()).isEqualTo("secondary");
    }
  }

  @Nested
  @DisplayName("getStatutIcon")
  class GetStatutIcon {

    @Test
    @DisplayName("retourne pi-circle quand statut null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getStatutIcon()).isEqualTo("pi-circle");
    }

    @Test
    @DisplayName("retourne l'icône pour chaque statut")
    void testIcons() {
      assertThat(AdhesionResponse.builder().statut("APPROUVEE").build().getStatutIcon()).isEqualTo("pi-check");
      assertThat(AdhesionResponse.builder().statut("PAYEE").build().getStatutIcon()).isEqualTo("pi-check");
      assertThat(AdhesionResponse.builder().statut("EN_ATTENTE").build().getStatutIcon()).isEqualTo("pi-clock");
      assertThat(AdhesionResponse.builder().statut("EN_PAIEMENT").build().getStatutIcon()).isEqualTo("pi-credit-card");
      assertThat(AdhesionResponse.builder().statut("REJETEE").build().getStatutIcon()).isEqualTo("pi-times");
      assertThat(AdhesionResponse.builder().statut("ANNULEE").build().getStatutIcon()).isEqualTo("pi-ban");
    }

    @Test
    @DisplayName("retourne pi-circle pour défaut")
    void testDefault() {
      assertThat(AdhesionResponse.builder().statut("INCONNU").build().getStatutIcon()).isEqualTo("pi-circle");
    }
  }

  @Nested
  @DisplayName("getMethodePaiementLibelle")
  class GetMethodePaiementLibelle {

    @Test
    @DisplayName("retourne Non défini quand methodePaiement null")
    void testNull() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getMethodePaiementLibelle()).isEqualTo("Non défini");
    }

    @Test
    @DisplayName("retourne le libellé pour chaque méthode")
    void testMethodes() {
      assertThat(AdhesionResponse.builder().methodePaiement("ESPECES").build().getMethodePaiementLibelle()).isEqualTo("Espèces");
      assertThat(AdhesionResponse.builder().methodePaiement("VIREMENT").build().getMethodePaiementLibelle()).isEqualTo("Virement bancaire");
      assertThat(AdhesionResponse.builder().methodePaiement("CHEQUE").build().getMethodePaiementLibelle()).isEqualTo("Chèque");
      assertThat(AdhesionResponse.builder().methodePaiement("WAVE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Wave Money");
      assertThat(AdhesionResponse.builder().methodePaiement("ORANGE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Orange Money");
      assertThat(AdhesionResponse.builder().methodePaiement("FREE_MONEY").build().getMethodePaiementLibelle()).isEqualTo("Free Money");
      assertThat(AdhesionResponse.builder().methodePaiement("CARTE_BANCAIRE").build().getMethodePaiementLibelle()).isEqualTo("Carte bancaire");
    }

    @Test
    @DisplayName("retourne methodePaiement tel quel pour valeur inconnue")
    void testDefault() {
      AdhesionResponse r = AdhesionResponse.builder().methodePaiement("AUTRE").build();
      assertThat(r.getMethodePaiementLibelle()).isEqualTo("AUTRE");
    }
  }

  @Nested
  @DisplayName("Dates formatées")
  class DatesFormatees {

    @Test
    @DisplayName("getDateDemandeFormatee")
    void testDateDemande() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getDateDemandeFormatee()).isEmpty();
      r.setDateDemande(LocalDate.of(2026, 2, 15));
      assertThat(r.getDateDemandeFormatee()).isEqualTo("15/02/2026");
    }

    @Test
    @DisplayName("getDateApprobationFormatee")
    void testDateApprobation() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getDateApprobationFormatee()).isEmpty();
      r.setDateApprobation(LocalDate.of(2026, 2, 16));
      assertThat(r.getDateApprobationFormatee()).isEqualTo("16/02/2026");
    }

    @Test
    @DisplayName("getDatePaiementFormatee")
    void testDatePaiement() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getDatePaiementFormatee()).isEmpty();
      r.setDatePaiement(LocalDateTime.of(2026, 2, 16, 14, 30));
      assertThat(r.getDatePaiementFormatee()).isEqualTo("16/02/2026 14:30");
    }
  }

  @Nested
  @DisplayName("Montants formatés")
  class MontantsFormates {

    @Test
    @DisplayName("getFraisAdhesionFormatte")
    void testFrais() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getFraisAdhesionFormatte()).isEqualTo("0 FCFA");
      r.setFraisAdhesion(BigDecimal.valueOf(50000));
      assertThat(r.getFraisAdhesionFormatte()).contains("50");
      assertThat(r.getFraisAdhesionFormatte()).endsWith(" FCFA");
    }

    @Test
    @DisplayName("getMontantPayeFormatte")
    void testMontantPaye() {
      AdhesionResponse r = new AdhesionResponse();
      assertThat(r.getMontantPayeFormatte()).isEqualTo("0 FCFA");
      r.setMontantPaye(BigDecimal.valueOf(25000));
      assertThat(r.getMontantPayeFormatte()).contains("25");
      assertThat(r.getMontantPayeFormatte()).endsWith(" FCFA");
    }

    @Test
    @DisplayName("getMontantRestantFormatte")
    void testMontantRestant() {
      AdhesionResponse r = AdhesionResponse.builder()
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.valueOf(30)).build();
      assertThat(r.getMontantRestantFormatte()).contains("70");
      assertThat(r.getMontantRestantFormatte()).endsWith(" FCFA");
    }
  }

  @Nested
  @DisplayName("Builder et getters")
  class BuilderGetters {

    @Test
    @DisplayName("tous les champs sont accessibles")
    void testBuilder() {
      LocalDate dateDemande = LocalDate.of(2026, 1, 10);
      AdhesionResponse r = AdhesionResponse.builder()
          .numeroReference("REF-001")
          .membreId(UUID.randomUUID())
          .numeroMembre("M1")
          .nomMembre("Dupont")
          .emailMembre("d@test.com")
          .organisationId(UUID.randomUUID())
          .nomOrganisation("Org")
          .dateDemande(dateDemande)
          .fraisAdhesion(BigDecimal.valueOf(100))
          .montantPaye(BigDecimal.ZERO)
          .codeDevise("XOF")
          .statut("EN_ATTENTE")
          .build();
      r.setId(UUID.randomUUID());
      assertThat(r.getNumeroReference()).isEqualTo("REF-001");
      assertThat(r.getNomMembre()).isEqualTo("Dupont");
      assertThat(r.getDateDemande()).isEqualTo(dateDemande);
      assertThat(r.getStatut()).isEqualTo("EN_ATTENTE");
    }
  }
}
