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

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

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 BaseDTO.
 * Couvre constructeur, marquerCommeNouveau, marquerCommeModifie (version null et non null),
 * desactiver, reactiver, isNouveau, isActif, equals, hashCode, toString.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests BaseDTO")
class BaseDTOTest {

  /** Sous-classe concrète pour tester BaseDTO (classe abstraite). */
  private static final class ConcreteBaseDTO extends BaseDTO {
    // aucune propriété additionnelle
  }

  @Nested
  @DisplayName("Constructeur et état initial")
  class Constructeur {

    @Test
    @DisplayName("constructeur par défaut initialise dateCreation, actif et version")
    void testConstructeurParDefaut() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.getDateCreation()).isNotNull();
      assertThat(dto.getActif()).isTrue();
      assertThat(dto.getVersion()).isEqualTo(0L);
      assertThat(dto.getId()).isNull();
    }
  }

  @Nested
  @DisplayName("marquerCommeNouveau")
  class MarquerCommeNouveau {

    @Test
    @DisplayName("marquerCommeNouveau remplit dates, creePar, modifiePar, version et actif")
    void testMarquerCommeNouveau() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setVersion(5L);
      dto.setActif(false);
      dto.marquerCommeNouveau("user@test.com");
      assertThat(dto.getDateCreation()).isNotNull();
      assertThat(dto.getDateModification()).isNotNull();
      assertThat(dto.getDateCreation()).isEqualTo(dto.getDateModification());
      assertThat(dto.getCreePar()).isEqualTo("user@test.com");
      assertThat(dto.getModifiePar()).isEqualTo("user@test.com");
      assertThat(dto.getVersion()).isEqualTo(0L);
      assertThat(dto.getActif()).isTrue();
    }
  }

  @Nested
  @DisplayName("marquerCommeModifie")
  class MarquerCommeModifie {

    @Test
    @DisplayName("marquerCommeModifie quand version non null incrémente version")
    void testMarquerCommeModifieVersionNonNull() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setVersion(3L);
      dto.marquerCommeModifie("other@test.com");
      assertThat(dto.getModifiePar()).isEqualTo("other@test.com");
      assertThat(dto.getDateModification()).isNotNull();
      assertThat(dto.getVersion()).isEqualTo(4L);
    }

    @Test
    @DisplayName("marquerCommeModifie quand version null ne lance pas NPE")
    void testMarquerCommeModifieVersionNull() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setVersion(null);
      dto.marquerCommeModifie("other@test.com");
      assertThat(dto.getModifiePar()).isEqualTo("other@test.com");
      assertThat(dto.getVersion()).isNull();
    }
  }

  @Nested
  @DisplayName("desactiver et reactiver")
  class DesactiverReactiver {

    @Test
    @DisplayName("desactiver met actif à false et appelle marquerCommeModifie")
    void testDesactiver() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.desactiver("admin@test.com");
      assertThat(dto.getActif()).isFalse();
      assertThat(dto.getModifiePar()).isEqualTo("admin@test.com");
    }

    @Test
    @DisplayName("reactiver met actif à true et appelle marquerCommeModifie")
    void testReactiver() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setActif(false);
      dto.reactiver("admin@test.com");
      assertThat(dto.getActif()).isTrue();
      assertThat(dto.getModifiePar()).isEqualTo("admin@test.com");
    }
  }

  @Nested
  @DisplayName("isNouveau")
  class IsNouveau {

    @Test
    @DisplayName("isNouveau retourne true quand id est null")
    void testIsNouveauTrue() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.isNouveau()).isTrue();
    }

    @Test
    @DisplayName("isNouveau retourne false quand id est non null")
    void testIsNouveauFalse() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setId(UUID.randomUUID());
      assertThat(dto.isNouveau()).isFalse();
    }
  }

  @Nested
  @DisplayName("isActif")
  class IsActif {

    @Test
    @DisplayName("isActif retourne true quand actif est Boolean.TRUE")
    void testIsActifTrue() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setActif(true);
      assertThat(dto.isActif()).isTrue();
    }

    @Test
    @DisplayName("isActif retourne false quand actif est false ou null")
    void testIsActifFalse() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setActif(false);
      assertThat(dto.isActif()).isFalse();
      dto.setActif(null);
      assertThat(dto.isActif()).isFalse();
    }
  }

  @Nested
  @DisplayName("equals")
  class Equals {

    @Test
    @DisplayName("equals retourne true pour même instance")
    void testEqualsMemeInstance() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.equals(dto)).isTrue();
    }

    @Test
    @DisplayName("equals retourne false pour null")
    void testEqualsNull() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.equals(null)).isFalse();
    }

    @Test
    @DisplayName("equals retourne false pour classe différente")
    void testEqualsClasseDifferent() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.equals("not a DTO")).isFalse();
    }

    @Test
    @DisplayName("equals retourne true quand ids égaux (non null)")
    void testEqualsIdsEgaux() {
      UUID id = UUID.randomUUID();
      ConcreteBaseDTO a = new ConcreteBaseDTO();
      a.setId(id);
      ConcreteBaseDTO b = new ConcreteBaseDTO();
      b.setId(id);
      assertThat(a).isEqualTo(b);
    }

    @Test
    @DisplayName("equals retourne false quand id de this est null")
    void testEqualsIdThisNull() {
      ConcreteBaseDTO a = new ConcreteBaseDTO();
      a.setId(null);
      ConcreteBaseDTO b = new ConcreteBaseDTO();
      b.setId(UUID.randomUUID());
      assertThat(a.equals(b)).isFalse();
    }

    @Test
    @DisplayName("equals retourne false quand ids différents")
    void testEqualsIdsDifferents() {
      ConcreteBaseDTO a = new ConcreteBaseDTO();
      a.setId(UUID.randomUUID());
      ConcreteBaseDTO b = new ConcreteBaseDTO();
      b.setId(UUID.randomUUID());
      assertThat(a).isNotEqualTo(b);
    }
  }

  @Nested
  @DisplayName("hashCode")
  class HashCode {

    @Test
    @DisplayName("hashCode retourne 0 quand id est null")
    void testHashCodeIdNull() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      assertThat(dto.hashCode()).isEqualTo(0);
    }

    @Test
    @DisplayName("hashCode retourne id.hashCode() quand id non null")
    void testHashCodeIdNonNull() {
      UUID id = UUID.randomUUID();
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      dto.setId(id);
      assertThat(dto.hashCode()).isEqualTo(id.hashCode());
    }
  }

  @Nested
  @DisplayName("toString")
  class ToString {

    @Test
    @DisplayName("toString contient le nom de la classe et les champs")
    void testToString() {
      ConcreteBaseDTO dto = new ConcreteBaseDTO();
      UUID id = UUID.randomUUID();
      dto.setId(id);
      dto.setCreePar("u1");
      dto.setModifiePar("u2");
      dto.setVersion(1L);
      String s = dto.toString();
      assertThat(s).contains("ConcreteBaseDTO");
      assertThat(s).contains("id=" + id);
      assertThat(s).contains("creePar='u1'");
      assertThat(s).contains("modifiePar='u2'");
      assertThat(s).contains("version=1");
      assertThat(s).contains("actif=true");
    }
  }
}
