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

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

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

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

  private TestableBaseDTO baseDto;

  @BeforeEach
  void setUp() {
    baseDto = new TestableBaseDTO();
  }

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

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

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

      assertThat(newDto.getId()).isNotNull();
      assertThat(newDto.getDateCreation()).isNotNull();
      assertThat(newDto.isActif()).isTrue();
      assertThat(newDto.getVersion()).isEqualTo(0L);
      assertThat(newDto.getDateModification()).isNull();
      assertThat(newDto.getCreePar()).isNull();
      assertThat(newDto.getModifiePar()).isNull();
    }
  }

  @Nested
  @DisplayName("Tests Getters/Setters")
  class GettersSettersTests {

    @Test
    @DisplayName("Test tous les getters/setters")
    void testGettersSetters() {
      UUID id = UUID.randomUUID();
      LocalDateTime dateCreation = LocalDateTime.now().minusDays(1);
      LocalDateTime dateModification = LocalDateTime.now();
      String creePar = "user1";
      String modifiePar = "user2";
      Long version = 5L;

      baseDto.setId(id);
      baseDto.setDateCreation(dateCreation);
      baseDto.setDateModification(dateModification);
      baseDto.setCreePar(creePar);
      baseDto.setModifiePar(modifiePar);
      baseDto.setVersion(version);
      baseDto.setActif(false);

      assertThat(baseDto.getId()).isEqualTo(id);
      assertThat(baseDto.getDateCreation()).isEqualTo(dateCreation);
      assertThat(baseDto.getDateModification()).isEqualTo(dateModification);
      assertThat(baseDto.getCreePar()).isEqualTo(creePar);
      assertThat(baseDto.getModifiePar()).isEqualTo(modifiePar);
      assertThat(baseDto.getVersion()).isEqualTo(version);
      assertThat(baseDto.isActif()).isFalse();
    }
  }

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

    @Test
    @DisplayName("Test marquerCommeModifie")
    void testMarquerCommeModifie() {
      String utilisateur = "testUser";
      LocalDateTime avant = LocalDateTime.now().minusSeconds(1);

      baseDto.marquerCommeModifie(utilisateur);

      assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
      assertThat(baseDto.getDateModification()).isAfter(avant);
      assertThat(baseDto.getVersion()).isEqualTo(1L);
    }

    @Test
    @DisplayName("Test marquerCommeModifie - Incrémente version")
    void testMarquerCommeModifieIncrementeVersion() {
      baseDto.setVersion(3L);

      baseDto.marquerCommeModifie("user");

      assertThat(baseDto.getVersion()).isEqualTo(4L);
    }

    @Test
    @DisplayName("Test desactiver")
    void testDesactiver() {
      baseDto.setActif(true);

      baseDto.desactiver("user");

      assertThat(baseDto.isActif()).isFalse();
    }

    @Test
    @DisplayName("Test reactiver")
    void testReactiver() {
      baseDto.setActif(false);

      baseDto.reactiver("user");

      assertThat(baseDto.isActif()).isTrue();
    }

    @Test
    @DisplayName("Test isNouveau")
    void testIsNouveau() {
      // Nouveau DTO (ID null)
      baseDto.setId(null);
      assertThat(baseDto.isNouveau()).isTrue();

      // DTO existant (ID non null)
      baseDto.setId(UUID.randomUUID());
      assertThat(baseDto.isNouveau()).isFalse();
    }

    @Test
    @DisplayName("Test isActif")
    void testIsActif() {
      // Test actif
      baseDto.setActif(true);
      assertThat(baseDto.isActif()).isTrue();

      // Test inactif
      baseDto.setActif(false);
      assertThat(baseDto.isActif()).isFalse();

      // Test avec null
      baseDto.setActif(null);
      assertThat(baseDto.isActif()).isFalse();

      // Test avec Boolean.TRUE explicite
      baseDto.setActif(Boolean.TRUE);
      assertThat(baseDto.isActif()).isTrue();

      // Test avec Boolean.FALSE explicite
      baseDto.setActif(Boolean.FALSE);
      assertThat(baseDto.isActif()).isFalse();
    }

    @Test
    @DisplayName("Test marquerCommeNouveau")
    void testMarquerCommeNouveau() {
      String utilisateur = "testUser";
      LocalDateTime avant = LocalDateTime.now().minusSeconds(1);

      baseDto.marquerCommeNouveau(utilisateur);

      assertThat(baseDto.getCreePar()).isEqualTo(utilisateur);
      assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
      assertThat(baseDto.getDateCreation()).isAfter(avant);
      assertThat(baseDto.getDateModification()).isAfter(avant);
      assertThat(baseDto.getVersion()).isEqualTo(0L);
      assertThat(baseDto.isActif()).isTrue();
    }

    @Test
    @DisplayName("Test marquerCommeModifie avec version null")
    void testMarquerCommeModifieAvecVersionNull() {
      // Given
      baseDto.setVersion(null);
      String utilisateur = "testUser";
      LocalDateTime avant = LocalDateTime.now().minusSeconds(1);

      // When
      baseDto.marquerCommeModifie(utilisateur);

      // Then
      assertThat(baseDto.getModifiePar()).isEqualTo(utilisateur);
      assertThat(baseDto.getDateModification()).isAfter(avant);
      assertThat(baseDto.getVersion()).isNull(); // Version reste null car elle était null
    }

    @Test
    @DisplayName("Test accès et modification du champ actif")
    void testAccesChampActif() {
      // Given & When - Vérifier l'initialisation par défaut
      assertThat(baseDto.getActif()).isTrue();
      assertThat(baseDto.isActif()).isTrue(); // Test de la méthode isActif() pour Boolean

      // Test modification du champ actif
      baseDto.setActif(false);
      assertThat(baseDto.getActif()).isFalse();
      assertThat(baseDto.isActif()).isFalse();

      baseDto.setActif(null);
      assertThat(baseDto.getActif()).isNull();
      assertThat(baseDto.isActif()).isFalse(); // isActif() retourne false pour null avec Lombok

      baseDto.setActif(true);
      assertThat(baseDto.getActif()).isTrue();
      assertThat(baseDto.isActif()).isTrue();
    }
  }

  @Nested
  @DisplayName("Tests equals et hashCode")
  class EqualsHashCodeTests {

    @Test
    @DisplayName("Test equals - Même ID")
    void testEqualsMemeId() {
      UUID id = UUID.randomUUID();
      baseDto.setId(id);

      TestableBaseDTO autre = new TestableBaseDTO();
      autre.setId(id);

      assertThat(baseDto).isEqualTo(autre);
      assertThat(baseDto.hashCode()).isEqualTo(autre.hashCode());
    }

    @Test
    @DisplayName("Test equals - IDs différents")
    void testEqualsIdsDifferents() {
      baseDto.setId(UUID.randomUUID());

      TestableBaseDTO autre = new TestableBaseDTO();
      autre.setId(UUID.randomUUID());

      assertThat(baseDto).isNotEqualTo(autre);
    }

    @Test
    @DisplayName("Test equals - ID null")
    void testEqualsIdNull() {
      baseDto.setId(null);

      TestableBaseDTO autre = new TestableBaseDTO();
      autre.setId(null);

      assertThat(baseDto).isNotEqualTo(autre);
    }

    @Test
    @DisplayName("Test equals - Objet null")
    void testEqualsObjetNull() {
      assertThat(baseDto).isNotEqualTo(null);
    }

    @Test
    @DisplayName("Test equals - Classe différente")
    void testEqualsClasseDifferente() {
      assertThat(baseDto).isNotEqualTo("string");
    }

    @Test
    @DisplayName("Test equals - Même objet")
    void testEqualsMemeObjet() {
      assertThat(baseDto).isEqualTo(baseDto);
    }

    @Test
    @DisplayName("Test hashCode - ID null")
    void testHashCodeIdNull() {
      baseDto.setId(null);
      assertThat(baseDto.hashCode()).isEqualTo(0);
    }

    @Test
    @DisplayName("Test hashCode - ID non null")
    void testHashCodeIdNonNull() {
      UUID id = UUID.randomUUID();
      baseDto.setId(id);
      assertThat(baseDto.hashCode()).isEqualTo(id.hashCode());
    }
  }

  @Nested
  @DisplayName("Tests toString")
  class ToStringTests {

    @Test
    @DisplayName("Test toString")
    void testToString() {
      UUID id = UUID.randomUUID();
      baseDto.setId(id);
      baseDto.setVersion(2L);
      baseDto.setActif(true);

      String result = baseDto.toString();
      assertThat(result).contains("TestableBaseDTO");
      assertThat(result).contains("id=" + id.toString());
      assertThat(result).contains("version=2");
      assertThat(result).contains("actif=true");
    }
  }

  /** Classe de test concrète pour tester BaseDTO. */
  private static class TestableBaseDTO extends BaseDTO {
    private static final long serialVersionUID = 1L;

    @Override
    public String toString() {
      return "TestableBaseDTO{" + super.toString() + "}";
    }
  }
}
