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

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.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires complets pour CotisationDTO.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
@DisplayName("Tests CotisationDTO")
class CotisationDTOBasicTest {

  private CotisationDTO cotisation;

  @BeforeEach
  void setUp() {
    cotisation = new CotisationDTO();
  }

  @Test
  @DisplayName("Constructeur par défaut - Initialisation correcte")
  void testConstructeurParDefaut() {
    CotisationDTO newCotisation = new CotisationDTO();

    assertThat(newCotisation.getId()).isNotNull();
    assertThat(newCotisation.getDateCreation()).isNotNull();
    assertThat(newCotisation.isActif()).isTrue();
    assertThat(newCotisation.getVersion()).isEqualTo(0L);
    assertThat(newCotisation.getMontantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
    assertThat(newCotisation.getCodeDevise()).isEqualTo("XOF");
    assertThat(newCotisation.getStatut()).isEqualTo("EN_ATTENTE");
    assertThat(newCotisation.getRecurrente()).isFalse();
    assertThat(newCotisation.getNombreRappels()).isEqualTo(0);
    assertThat(newCotisation.getAnnee()).isEqualTo(LocalDate.now().getYear());
  }

  @Test
  @DisplayName("Test getters/setters principaux")
  void testGettersSettersPrincipaux() {
    // Données de test
    String numeroReference = "COT-2025-001";
    UUID membreId = UUID.randomUUID();
    String nomMembre = "Jean Dupont";
    String numeroMembre = "UF-2025-12345678";
    UUID associationId = UUID.randomUUID();
    String nomAssociation = "Lions Club Dakar";
    String typeCotisation = "MENSUELLE";
    String libelle = "Cotisation mensuelle";
    BigDecimal montantDu = new BigDecimal("25000.00");
    BigDecimal montantPaye = new BigDecimal("25000.00");
    String codeDevise = "XOF";
    String statut = "EN_ATTENTE";
    LocalDate dateEcheance = LocalDate.now().plusDays(30);
    LocalDateTime datePaiement = LocalDateTime.now();

    // Test des setters
    cotisation.setNumeroReference(numeroReference);
    cotisation.setMembreId(membreId);
    cotisation.setNomMembre(nomMembre);
    cotisation.setNumeroMembre(numeroMembre);
    cotisation.setAssociationId(associationId);
    cotisation.setNomAssociation(nomAssociation);
    cotisation.setTypeCotisation(typeCotisation);
    cotisation.setLibelle(libelle);
    cotisation.setMontantDu(montantDu);
    cotisation.setMontantPaye(montantPaye);
    cotisation.setCodeDevise(codeDevise);
    cotisation.setStatut(statut);
    cotisation.setDateEcheance(dateEcheance);
    cotisation.setDatePaiement(datePaiement);

    // Test des getters
    assertThat(cotisation.getNumeroReference()).isEqualTo(numeroReference);
    assertThat(cotisation.getMembreId()).isEqualTo(membreId);
    assertThat(cotisation.getNomMembre()).isEqualTo(nomMembre);
    assertThat(cotisation.getNumeroMembre()).isEqualTo(numeroMembre);
    assertThat(cotisation.getAssociationId()).isEqualTo(associationId);
    assertThat(cotisation.getNomAssociation()).isEqualTo(nomAssociation);
    assertThat(cotisation.getTypeCotisation()).isEqualTo(typeCotisation);
    assertThat(cotisation.getLibelle()).isEqualTo(libelle);
    assertThat(cotisation.getMontantDu()).isEqualTo(montantDu);
    assertThat(cotisation.getMontantPaye()).isEqualTo(montantPaye);
    assertThat(cotisation.getCodeDevise()).isEqualTo(codeDevise);
    assertThat(cotisation.getStatut()).isEqualTo(statut);
    assertThat(cotisation.getDateEcheance()).isEqualTo(dateEcheance);
    assertThat(cotisation.getDatePaiement()).isEqualTo(datePaiement);
  }

  @Test
  @DisplayName("Test méthodes métier")
  void testMethodesMetier() {
    // Test isEnRetard
    cotisation.setDateEcheance(LocalDate.now().minusDays(5));
    cotisation.setStatut("EN_ATTENTE");
    assertThat(cotisation.isEnRetard()).isTrue();

    cotisation.setDateEcheance(LocalDate.now().plusDays(5));
    assertThat(cotisation.isEnRetard()).isFalse();

    // Test getStatutLibelle
    cotisation.setStatut("PAYEE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Payée");

    cotisation.setStatut("EN_ATTENTE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("En attente");

    // Test marquerCommePaye
    cotisation.setStatut("EN_ATTENTE");
    BigDecimal montant = new BigDecimal("25000.00");
    cotisation.marquerCommePaye(montant, "WAVE_MONEY", "TXN987654321");
    assertThat(cotisation.getDatePaiement()).isNotNull();
  }



  @Test
  @DisplayName("Test méthodes métier avancées")
  void testMethodesMetierAvancees() {
    // Test getMontantRestant
    cotisation.setMontantDu(new BigDecimal("100000.00"));
    cotisation.setMontantPaye(new BigDecimal("60000.00"));
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(new BigDecimal("40000.00"));

    // Test avec montant payé supérieur au montant dû
    cotisation.setMontantPaye(new BigDecimal("120000.00"));
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);

    // Test avec montant dû null
    cotisation.setMontantDu(null);
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);

    // Test getPourcentagePaiement
    cotisation.setMontantDu(new BigDecimal("100000.00"));
    cotisation.setMontantPaye(new BigDecimal("75000.00"));
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(75);

    // Test avec montant dû zéro
    cotisation.setMontantDu(BigDecimal.ZERO);
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);

    // Test avec montant payé null
    cotisation.setMontantDu(new BigDecimal("100000.00"));
    cotisation.setMontantPaye(null);
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);

    // Test getJoursRetard
    cotisation.setDateEcheance(LocalDate.now().minusDays(15));
    cotisation.setMontantPaye(BigDecimal.ZERO);
    assertThat(cotisation.getJoursRetard()).isEqualTo(15);

    // Test sans retard
    cotisation.setDateEcheance(LocalDate.now().plusDays(5));
    assertThat(cotisation.getJoursRetard()).isEqualTo(0);

    // Test avec date d'échéance null
    cotisation.setDateEcheance(null);
    assertThat(cotisation.getJoursRetard()).isEqualTo(0);

    // Test isPayeeIntegralement
    cotisation.setMontantDu(new BigDecimal("50000.00"));
    cotisation.setMontantPaye(new BigDecimal("50000.00"));
    assertThat(cotisation.isPayeeIntegralement()).isTrue();

    cotisation.setMontantPaye(new BigDecimal("30000.00"));
    assertThat(cotisation.isPayeeIntegralement()).isFalse();

    // Test isEnRetard
    cotisation.setDateEcheance(LocalDate.now().minusDays(5));
    cotisation.setMontantPaye(BigDecimal.ZERO);
    assertThat(cotisation.isEnRetard()).isTrue();

    cotisation.setMontantPaye(new BigDecimal("50000.00"));
    assertThat(cotisation.isEnRetard()).isFalse();
  }

  @Test
  @DisplayName("Test libellés")
  void testLibelles() {
    // Test getTypeCotisationLibelle
    cotisation.setTypeCotisation("MENSUELLE");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Mensuelle");

    cotisation.setTypeCotisation("TRIMESTRIELLE");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Trimestrielle");

    cotisation.setTypeCotisation("ANNUELLE");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Annuelle");

    cotisation.setTypeCotisation("ADHESION");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Adhésion");

    cotisation.setTypeCotisation(null);
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Non défini");

    // Test getStatutLibelle
    cotisation.setStatut("EN_ATTENTE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("En attente");

    cotisation.setStatut("PAYEE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Payée");

    cotisation.setStatut("PARTIELLEMENT_PAYEE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Partiellement payée");

    cotisation.setStatut("EN_RETARD");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("En retard");

    cotisation.setStatut("ANNULEE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Annulée");

    cotisation.setStatut("REMBOURSEE");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Remboursée");

    // Test getMethodePaiementLibelle
    cotisation.setMethodePaiement("ESPECES");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Espèces");

    cotisation.setMethodePaiement("WAVE_MONEY");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Wave Money");

    cotisation.setMethodePaiement("ORANGE_MONEY");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Orange Money");

    cotisation.setMethodePaiement("VIREMENT");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Virement bancaire");

    cotisation.setMethodePaiement(null);
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Non défini");
  }

  @Test
  @DisplayName("Test mettreAJourStatut")
  void testMettreAJourStatut() {
    cotisation.setMontantDu(new BigDecimal("100000.00"));

    // Test statut EN_RETARD
    cotisation.setMontantPaye(null);
    cotisation.setDateEcheance(LocalDate.now().minusDays(5));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("EN_RETARD");

    // Test statut EN_ATTENTE
    cotisation.setMontantPaye(BigDecimal.ZERO);
    cotisation.setDateEcheance(LocalDate.now().plusDays(5));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("EN_ATTENTE");

    // Test statut PARTIELLEMENT_PAYEE
    cotisation.setMontantPaye(new BigDecimal("50000.00"));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("PARTIELLEMENT_PAYEE");

    // Test statut PAYEE
    cotisation.setMontantPaye(new BigDecimal("100000.00"));
    cotisation.setDatePaiement(null);
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("PAYEE");
    assertThat(cotisation.getDatePaiement()).isNotNull();
  }

  @Test
  @DisplayName("Test constructeur avec paramètres complet")
  void testConstructeurAvecParametresComplet() {
    UUID membreId = UUID.randomUUID();
    String typeCotisation = "MENSUELLE";
    BigDecimal montantDu = new BigDecimal("25000.00");
    LocalDate dateEcheance = LocalDate.of(2025, 1, 31);

    CotisationDTO newCotisation = new CotisationDTO(membreId, typeCotisation, montantDu, dateEcheance);

    assertThat(newCotisation.getMembreId()).isEqualTo(membreId);
    assertThat(newCotisation.getTypeCotisation()).isEqualTo(typeCotisation);
    assertThat(newCotisation.getMontantDu()).isEqualTo(montantDu);
    assertThat(newCotisation.getDateEcheance()).isEqualTo(dateEcheance);
    assertThat(newCotisation.getNumeroReference()).isNotNull();
    assertThat(newCotisation.getNumeroReference()).startsWith("COT-");
    assertThat(newCotisation.getNumeroReference()).contains(String.valueOf(LocalDate.now().getYear()));
    // Vérifier que les valeurs par défaut sont toujours appliquées
    assertThat(newCotisation.getMontantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
    assertThat(newCotisation.getCodeDevise()).isEqualTo("XOF");
    assertThat(newCotisation.getStatut()).isEqualTo("EN_ATTENTE");
  }

  @Test
  @DisplayName("Test propriétés complémentaires complètes")
  void testProprietesComplementairesCompletes() {
    // Test des propriétés supplémentaires
    String description = "Description de la cotisation";
    String periode = "Janvier 2025";
    Integer annee = 2025;
    Integer mois = 1;
    String observations = "Observations importantes";
    Boolean recurrente = true;
    Integer nombreRappels = 2;
    LocalDateTime dateDernierRappel = LocalDateTime.now().minusDays(5);
    UUID validePar = UUID.randomUUID();
    String nomValidateur = "Admin User";
    String methodePaiement = "WAVE_MONEY";
    String referencePaiement = "WM123456789";

    // Test des setters
    cotisation.setDescription(description);
    cotisation.setPeriode(periode);
    cotisation.setAnnee(annee);
    cotisation.setMois(mois);
    cotisation.setObservations(observations);
    cotisation.setRecurrente(recurrente);
    cotisation.setNombreRappels(nombreRappels);
    cotisation.setDateDernierRappel(dateDernierRappel);
    cotisation.setValidePar(validePar);
    cotisation.setNomValidateur(nomValidateur);
    cotisation.setMethodePaiement(methodePaiement);
    cotisation.setReferencePaiement(referencePaiement);

    // Test des getters
    assertThat(cotisation.getDescription()).isEqualTo(description);
    assertThat(cotisation.getPeriode()).isEqualTo(periode);
    assertThat(cotisation.getAnnee()).isEqualTo(annee);
    assertThat(cotisation.getMois()).isEqualTo(mois);
    assertThat(cotisation.getObservations()).isEqualTo(observations);
    assertThat(cotisation.getRecurrente()).isEqualTo(recurrente);
    assertThat(cotisation.getNombreRappels()).isEqualTo(nombreRappels);
    assertThat(cotisation.getDateDernierRappel()).isEqualTo(dateDernierRappel);
    assertThat(cotisation.getValidePar()).isEqualTo(validePar);
    assertThat(cotisation.getNomValidateur()).isEqualTo(nomValidateur);
    assertThat(cotisation.getMethodePaiement()).isEqualTo(methodePaiement);
    assertThat(cotisation.getReferencePaiement()).isEqualTo(referencePaiement);
  }

  @Test
  @DisplayName("Test cas limites et valeurs nulles")
  void testCasLimitesValeursNulles() {
    // Test avec valeurs nulles
    cotisation.setMontantDu(null);
    cotisation.setMontantPaye(null);
    cotisation.setDateEcheance(null);
    cotisation.setTypeCotisation(null);
    cotisation.setStatut(null);
    cotisation.setMethodePaiement(null);

    // Test getMontantRestant avec valeurs nulles
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);

    // Test getPourcentagePaiement avec valeurs nulles
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);

    // Test getJoursRetard avec date null
    assertThat(cotisation.getJoursRetard()).isEqualTo(0);

    // Test isEnRetard avec date null
    assertThat(cotisation.isEnRetard()).isFalse();

    // Test libellés avec valeurs nulles
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Non défini");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("Non défini");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Non défini");

    // Test avec valeurs par défaut inconnues
    cotisation.setTypeCotisation("TYPE_INCONNU");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("TYPE_INCONNU");

    cotisation.setStatut("STATUT_INCONNU");
    assertThat(cotisation.getStatutLibelle()).isEqualTo("STATUT_INCONNU");

    cotisation.setMethodePaiement("METHODE_INCONNUE");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("METHODE_INCONNUE");
  }

  @Test
  @DisplayName("Test tous les types de cotisation")
  void testTousTypesLibelles() {
    // Test tous les types de cotisation
    cotisation.setTypeCotisation("SEMESTRIELLE");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Semestrielle");

    cotisation.setTypeCotisation("EXCEPTIONNELLE");
    assertThat(cotisation.getTypeCotisationLibelle()).isEqualTo("Exceptionnelle");

    // Test toutes les méthodes de paiement
    cotisation.setMethodePaiement("CHEQUE");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Chèque");

    cotisation.setMethodePaiement("FREE_MONEY");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Free Money");

    cotisation.setMethodePaiement("CARTE_BANCAIRE");
    assertThat(cotisation.getMethodePaiementLibelle()).isEqualTo("Carte bancaire");
  }

  @Test
  @DisplayName("Test toString complet")
  void testToStringComplet() {
    cotisation.setNumeroReference("COT-2025-001");
    cotisation.setNomMembre("Jean Dupont");
    cotisation.setTypeCotisation("MENSUELLE");
    cotisation.setMontantDu(new BigDecimal("25000.00"));
    cotisation.setMontantPaye(new BigDecimal("25000.00"));
    cotisation.setStatut("PAYEE");
    cotisation.setDateEcheance(LocalDate.of(2025, 1, 31));
    cotisation.setPeriode("Janvier 2025");

    String result = cotisation.toString();
    assertThat(result).isNotNull();
    assertThat(result).contains("CotisationDTO");
    assertThat(result).contains("numeroReference='COT-2025-001'");
    assertThat(result).contains("nomMembre='Jean Dupont'");
    assertThat(result).contains("typeCotisation='MENSUELLE'");
    assertThat(result).contains("montantDu=25000.00");
    assertThat(result).contains("montantPaye=25000.00");
    assertThat(result).contains("statut='PAYEE'");
    assertThat(result).contains("dateEcheance=2025-01-31");
    assertThat(result).contains("periode='Janvier 2025'");
    // Vérifier que le toString contient les informations de base
    assertThat(result).contains("id=");
    assertThat(result).contains("dateCreation=");
  }

  @Test
  @DisplayName("Test branches supplémentaires getPourcentagePaiement")
  void testBranchesSupplementairesPourcentagePaiement() {
    // Test avec montantDu null
    cotisation.setMontantDu(null);
    cotisation.setMontantPaye(BigDecimal.valueOf(100));
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);

    // Test avec montantDu zéro
    cotisation.setMontantDu(BigDecimal.ZERO);
    cotisation.setMontantPaye(BigDecimal.valueOf(100));
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);

    // Test avec montantPaye null
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(null);
    assertThat(cotisation.getPourcentagePaiement()).isEqualTo(0);
  }

  @Test
  @DisplayName("Test branches supplémentaires mettreAJourStatut")
  void testBranchesSupplementairesMettreAJourStatut() {
    // Test avec montantPaye null et en retard
    cotisation.setMontantPaye(null);
    cotisation.setDateEcheance(LocalDate.now().minusDays(5));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("EN_RETARD");

    // Test avec montantPaye zéro et pas en retard
    cotisation.setMontantPaye(BigDecimal.ZERO);
    cotisation.setDateEcheance(LocalDate.now().plusDays(5));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("EN_ATTENTE");

    // Test avec paiement intégral sans date de paiement
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(BigDecimal.valueOf(1000));
    cotisation.setDatePaiement(null);
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("PAYEE");
    assertThat(cotisation.getDatePaiement()).isNotNull();

    // Test avec paiement intégral avec date de paiement déjà définie
    LocalDateTime datePaiementExistante = LocalDateTime.now().minusDays(1);
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(BigDecimal.valueOf(1000));
    cotisation.setDatePaiement(datePaiementExistante);
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("PAYEE");
    assertThat(cotisation.getDatePaiement()).isEqualTo(datePaiementExistante); // Ne doit pas changer

    // Test avec paiement partiel
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(BigDecimal.valueOf(500));
    cotisation.mettreAJourStatut();
    assertThat(cotisation.getStatut()).isEqualTo("PARTIELLEMENT_PAYEE");
  }

  @Test
  @DisplayName("Test branches supplémentaires isPayeeIntegralement")
  void testBranchesSupplementairesIsPayeeIntegralement() {
    // Test avec montantPaye null
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(null);
    assertThat(cotisation.isPayeeIntegralement()).isFalse();

    // Test avec montantDu null
    cotisation.setMontantDu(null);
    cotisation.setMontantPaye(BigDecimal.valueOf(1000));
    assertThat(cotisation.isPayeeIntegralement()).isFalse();

    // Test avec les deux null
    cotisation.setMontantDu(null);
    cotisation.setMontantPaye(null);
    assertThat(cotisation.isPayeeIntegralement()).isFalse();
  }

  @Test
  @DisplayName("Test branches supplémentaires getMontantRestant")
  void testBranchesSupplementairesGetMontantRestant() {
    // Test avec montantPaye null
    cotisation.setMontantDu(BigDecimal.valueOf(1000));
    cotisation.setMontantPaye(null);
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(BigDecimal.valueOf(1000));

    // Test avec montant restant négatif (surpaiement)
    cotisation.setMontantDu(BigDecimal.valueOf(500));
    cotisation.setMontantPaye(BigDecimal.valueOf(1000));
    assertThat(cotisation.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);
  }
}
