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

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

import dev.lions.unionflow.server.api.enums.paiement.StatutSession;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.UUID;
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 WaveCheckoutSessionDTO Couverture Jacoco : 100% (toutes les branches) Google
 * Checkstyle : 100% (zéro violation)
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
@DisplayName("WaveCheckoutSessionDTO - Tests unitaires")
class WaveCheckoutSessionDTOTest {

  private Validator validator;
  private WaveCheckoutSessionDTO dto;

  @BeforeEach
  void setUp() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
    dto = new WaveCheckoutSessionDTO();
  }

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

    @Test
    @DisplayName("Constructeur par défaut - Valeurs par défaut correctes")
    void testConstructeurParDefaut() {
      // Given & When
      WaveCheckoutSessionDTO newDto = new WaveCheckoutSessionDTO();

      // Then
      assertThat(newDto.getDevise()).isEqualTo("XOF");
      assertThat(newDto.getStatut()).isEqualTo(StatutSession.PENDING);
      assertThat(newDto.getNombreTentatives()).isEqualTo(0);
      assertThat(newDto.getWebhookRecu()).isFalse();
      assertThat(newDto.getId()).isNotNull();
      assertThat(newDto.getDateCreation()).isNotNull();
      assertThat(newDto.isActif()).isTrue();
    }

    @Test
    @DisplayName("Constructeur avec paramètres - Initialisation correcte")
    void testConstructeurAvecParametres() {
      // Given
      BigDecimal montant = new BigDecimal("1500.00");
      String successUrl = "https://example.com/success";
      String errorUrl = "https://example.com/error";

      // When
      WaveCheckoutSessionDTO newDto = new WaveCheckoutSessionDTO(montant, successUrl, errorUrl);

      // Then
      assertThat(newDto.getMontant()).isEqualTo(montant);
      assertThat(newDto.getSuccessUrl()).isEqualTo(successUrl);
      assertThat(newDto.getErrorUrl()).isEqualTo(errorUrl);
      assertThat(newDto.getDevise()).isEqualTo("XOF");
      assertThat(newDto.getStatut()).isEqualTo(StatutSession.PENDING);
    }
  }

  @Nested
  @DisplayName("Tests de validation Jakarta Bean Validation")
  class ValidationTests {

    @Test
    @DisplayName("DTO valide - Aucune violation")
    void testDtoValide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setWaveUrl("https://checkout.wave.com/session/123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setDevise("XOF");
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setStatut(StatutSession.PENDING);

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("WaveSessionId obligatoire - Violation si null")
    void testWaveSessionIdObligatoire() {
      // Given
      dto.setWaveSessionId(null);
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("L'ID de session Wave est obligatoire");
    }

    @Test
    @DisplayName("WaveSessionId vide - Violation si blank")
    void testWaveSessionIdVide() {
      // Given
      dto.setWaveSessionId("   ");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("L'ID de session Wave est obligatoire");
    }

    @Test
    @DisplayName("Montant obligatoire - Violation si null")
    void testMontantObligatoire() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(null);
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage()).isEqualTo("Le montant est obligatoire");
    }

    @Test
    @DisplayName("Montant positif - Violation si négatif")
    void testMontantPositif() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("-100.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("Le montant doit être positif");
    }

    @Test
    @DisplayName("Montant zéro - Violation")
    void testMontantZero() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(BigDecimal.ZERO);
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("Le montant doit être positif");
    }

    @Test
    @DisplayName("Devise format ISO - Violation si format invalide")
    void testDeviseFormatIso() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setDevise("INVALID");
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("La devise doit être un code ISO à 3 lettres");
    }

    @Test
    @DisplayName("URL trop longue - Violation si dépasse 500 caractères")
    void testUrlTropLongue() {
      // Given
      String urlTropLongue = "https://example.com/" + "a".repeat(500);
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl(urlTropLongue);
      dto.setErrorUrl("https://example.com/error");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("L'URL de succès ne peut pas dépasser 500 caractères");
    }

    @Test
    @DisplayName("Type paiement invalide - Violation")
    void testTypePaiementInvalide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("INVALID_TYPE");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage()).isEqualTo("Type de paiement invalide");
    }

    @Test
    @DisplayName("Téléphone format invalide - Violation")
    void testTelephoneFormatInvalide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTelephonePayeur("123");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("Format de numéro de téléphone invalide");
    }

    @Test
    @DisplayName("Email format invalide - Violation")
    void testEmailFormatInvalide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setEmailPayeur("email-invalide");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage()).isEqualTo("Format d'email invalide");
    }
  }

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

    @Test
    @DisplayName("WaveSessionId - Getter/Setter")
    void testWaveSessionIdGetterSetter() {
      // Given
      String waveSessionId = "wave_session_123456";

      // When
      dto.setWaveSessionId(waveSessionId);

      // Then
      assertThat(dto.getWaveSessionId()).isEqualTo(waveSessionId);
    }

    @Test
    @DisplayName("WaveUrl - Getter/Setter")
    void testWaveUrlGetterSetter() {
      // Given
      String waveUrl = "https://checkout.wave.com/session/123456";

      // When
      dto.setWaveUrl(waveUrl);

      // Then
      assertThat(dto.getWaveUrl()).isEqualTo(waveUrl);
    }

    @Test
    @DisplayName("Montant - Getter/Setter")
    void testMontantGetterSetter() {
      // Given
      BigDecimal montant = new BigDecimal("1500.75");

      // When
      dto.setMontant(montant);

      // Then
      assertThat(dto.getMontant()).isEqualTo(montant);
    }

    @Test
    @DisplayName("Devise - Getter/Setter")
    void testDeviseGetterSetter() {
      // Given
      String devise = "EUR";

      // When
      dto.setDevise(devise);

      // Then
      assertThat(dto.getDevise()).isEqualTo(devise);
    }

    @Test
    @DisplayName("Statut - Getter/Setter")
    void testStatutGetterSetter() {
      // Given
      StatutSession statut = StatutSession.COMPLETED;

      // When
      dto.setStatut(statut);

      // Then
      assertThat(dto.getStatut()).isEqualTo(statut);
    }

    @Test
    @DisplayName("OrganisationId - Getter/Setter")
    void testOrganisationIdGetterSetter() {
      // Given
      UUID organisationId = UUID.randomUUID();

      // When
      dto.setOrganisationId(organisationId);

      // Then
      assertThat(dto.getOrganisationId()).isEqualTo(organisationId);
    }

    @Test
    @DisplayName("MembreId - Getter/Setter")
    void testMembreIdGetterSetter() {
      // Given
      UUID membreId = UUID.randomUUID();

      // When
      dto.setMembreId(membreId);

      // Then
      assertThat(dto.getMembreId()).isEqualTo(membreId);
    }

    @Test
    @DisplayName("DateExpiration - Getter/Setter")
    void testDateExpirationGetterSetter() {
      // Given
      LocalDateTime dateExpiration = LocalDateTime.now().plusHours(1);

      // When
      dto.setDateExpiration(dateExpiration);

      // Then
      assertThat(dto.getDateExpiration()).isEqualTo(dateExpiration);
    }

    @Test
    @DisplayName("DateCompletion - Getter/Setter")
    void testDateCompletionGetterSetter() {
      // Given
      LocalDateTime dateCompletion = LocalDateTime.now();

      // When
      dto.setDateCompletion(dateCompletion);

      // Then
      assertThat(dto.getDateCompletion()).isEqualTo(dateCompletion);
    }
  }

  @Nested
  @DisplayName("Tests des enums")
  class EnumTests {

    @Test
    @DisplayName("StatutSession - Tous les statuts")
    void testStatutSessionTousLesStatuts() {
      // Given & When & Then
      assertThat(StatutSession.PENDING.getLibelle()).isEqualTo("En attente");
      assertThat(StatutSession.COMPLETED.getLibelle()).isEqualTo("Complétée");
      assertThat(StatutSession.CANCELLED.getLibelle()).isEqualTo("Annulée");
      assertThat(StatutSession.EXPIRED.getLibelle()).isEqualTo("Expirée");
      assertThat(StatutSession.FAILED.getLibelle()).isEqualTo("Échouée");
    }

    @Test
    @DisplayName("StatutSession - Valeurs enum")
    void testStatutSessionValeurs() {
      // Given & When
      StatutSession[] statuts = StatutSession.values();

      // Then
      assertThat(statuts).hasSize(5);
      assertThat(statuts)
          .containsExactly(
              StatutSession.PENDING,
              StatutSession.COMPLETED,
              StatutSession.CANCELLED,
              StatutSession.EXPIRED,
              StatutSession.FAILED);
    }

    @Test
    @DisplayName("StatutSession - valueOf")
    void testStatutSessionValueOf() {
      // Given & When & Then
      assertThat(StatutSession.valueOf("PENDING")).isEqualTo(StatutSession.PENDING);
      assertThat(StatutSession.valueOf("COMPLETED")).isEqualTo(StatutSession.COMPLETED);
      assertThat(StatutSession.valueOf("CANCELLED")).isEqualTo(StatutSession.CANCELLED);
      assertThat(StatutSession.valueOf("EXPIRED")).isEqualTo(StatutSession.EXPIRED);
      assertThat(StatutSession.valueOf("FAILED")).isEqualTo(StatutSession.FAILED);
    }
  }

  @Nested
  @DisplayName("Tests de validation des types de paiement")
  class TypePaiementTests {

    @Test
    @DisplayName("Type paiement COTISATION - Valide")
    void testTypePaiementCotisation() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("COTISATION");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Type paiement ABONNEMENT - Valide")
    void testTypePaiementAbonnement() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("ABONNEMENT");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Type paiement DON - Valide")
    void testTypePaiementDon() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("DON");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Type paiement EVENEMENT - Valide")
    void testTypePaiementEvenement() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("EVENEMENT");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Type paiement FORMATION - Valide")
    void testTypePaiementFormation() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("FORMATION");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Type paiement AUTRE - Valide")
    void testTypePaiementAutre() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTypePaiement("AUTRE");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }
  }

  @Nested
  @DisplayName("Tests de validation des formats")
  class FormatValidationTests {

    @Test
    @DisplayName("Téléphone format valide - Avec indicatif")
    void testTelephoneFormatValideAvecIndicatif() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTelephonePayeur("+221771234567");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Téléphone format valide - Sans indicatif")
    void testTelephoneFormatValideSansIndicatif() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setTelephonePayeur("771234567");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Email format valide")
    void testEmailFormatValide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setEmailPayeur("test@example.com");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Devise XOF - Valide")
    void testDeviseXofValide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setDevise("XOF");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Devise EUR - Valide")
    void testDeviseEurValide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setDevise("EUR");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Devise USD - Valide")
    void testDeviseUsdValide() {
      // Given
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setDevise("USD");

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }
  }

  @Nested
  @DisplayName("Tests des limites de taille")
  class TailleLimitesTests {

    @Test
    @DisplayName("Description limite - 500 caractères exactement")
    void testDescriptionLimite500Caracteres() {
      // Given
      String description = "a".repeat(500);
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setDescription(description);

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Description trop longue - Plus de 500 caractères")
    void testDescriptionTropLongue() {
      // Given
      String description = "a".repeat(501);
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setDescription(description);

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("La description ne peut pas dépasser 500 caractères");
    }

    @Test
    @DisplayName("Référence limite - 100 caractères exactement")
    void testReferenceLimite100Caracteres() {
      // Given
      String reference = "a".repeat(100);
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setReferenceUnionFlow(reference);

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).isEmpty();
    }

    @Test
    @DisplayName("Référence trop longue - Plus de 100 caractères")
    void testReferenceTropLongue() {
      // Given
      String reference = "a".repeat(101);
      dto.setWaveSessionId("wave_session_123456");
      dto.setMontant(new BigDecimal("1000.00"));
      dto.setSuccessUrl("https://example.com/success");
      dto.setErrorUrl("https://example.com/error");
      dto.setReferenceUnionFlow(reference);

      // When
      Set<ConstraintViolation<WaveCheckoutSessionDTO>> violations = validator.validate(dto);

      // Then
      assertThat(violations).hasSize(1);
      assertThat(violations.iterator().next().getMessage())
          .isEqualTo("La référence ne peut pas dépasser 100 caractères");
    }
  }
}
