package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Membre;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.TestTransaction;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

@QuarkusTest
class MembreRepositoryTest {

    @Inject
    MembreRepository membreRepository;

    private Membre newMembre(String emailSuffix) {
        Membre m = new Membre();
        m.setNumeroMembre("MEM-" + UUID.randomUUID().toString().substring(0, 8));
        m.setPrenom("Prénom");
        m.setNom("Test");
        m.setEmail("membre-" + emailSuffix + "@test.com");
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setActif(true);
        return m;
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById retrouve le membre")
    void persist_thenFindById_findsMembre() {
        Membre m = newMembre(UUID.randomUUID().toString());
        membreRepository.persist(m);
        assertThat(m.getId()).isNotNull();
        Membre found = membreRepository.findById(m.getId());
        assertThat(found).isNotNull();
        assertThat(found.getEmail()).isEqualTo(m.getEmail());
    }

    @Test
    @TestTransaction
    @DisplayName("findByEmail retrouve le membre après persist")
    void findByEmail_findsMembre() {
        String email = "find-email-" + UUID.randomUUID() + "@test.com";
        Membre m = newMembre("x");
        m.setEmail(email);
        membreRepository.persist(m);
        Optional<Membre> found = membreRepository.findByEmail(email);
        assertThat(found).isPresent();
        assertThat(found.get().getEmail()).isEqualTo(email);
    }

    @Test
    @TestTransaction
    @DisplayName("findByEmail retourne empty pour email inexistant")
    void findByEmail_inexistant_returnsEmpty() {
        Optional<Membre> found = membreRepository.findByEmail("inexistant-" + UUID.randomUUID() + "@test.com");
        assertThat(found).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNumeroMembre retrouve le membre après persist")
    void findByNumeroMembre_findsMembre() {
        String numero = "NUM-" + UUID.randomUUID().toString().substring(0, 8);
        Membre m = newMembre(UUID.randomUUID().toString());
        m.setNumeroMembre(numero);
        membreRepository.persist(m);
        Optional<Membre> found = membreRepository.findByNumeroMembre(numero);
        assertThat(found).isPresent();
        assertThat(found.get().getNumeroMembre()).isEqualTo(numero);
    }

    @Test
    @TestTransaction
    @DisplayName("findAllActifs retourne une liste")
    void findAllActifs_returnsList() {
        List<Membre> list = membreRepository.findAllActifs();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countActifs retourne un nombre >= 0")
    void countActifs_returnsNonNegative() {
        assertThat(membreRepository.countActifs()).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenom retourne une liste")
    void findByNomOrPrenom_returnsList() {
        List<Membre> list = membreRepository.findByNomOrPrenom("%");
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByKeycloakUserId avec null retourne empty")
    void findByKeycloakUserId_null_returnsEmpty() {
        Optional<Membre> found = membreRepository.findByKeycloakUserId(null);
        assertThat(found).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByKeycloakUserId avec UUID inexistant retourne empty")
    void findByKeycloakUserId_inexistant_returnsEmpty() {
        Optional<Membre> found = membreRepository.findByKeycloakUserId(UUID.randomUUID().toString());
        assertThat(found).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee retourne une liste")
    void rechercheAvancee_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.rechercheAvancee(null, true, null, null, page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec recherche non null couvre la branche recherche != null")
    void rechercheAvancee_withRecherche_returnsList() {
        Membre m = newMembre(UUID.randomUUID().toString());
        membreRepository.persist(m);
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.rechercheAvancee("Prénom", null, null, null, page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec sort non null couvre la branche sort != null")
    void rechercheAvancee_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.rechercheAvancee(null, null, null, null, page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee sans actif couvre la branche actif == null")
    void rechercheAvancee_nullActif_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.rechercheAvancee(null, null, null, null, page, null);
        assertThat(list).isNotNull();
    }

    // ── Branch coverage manquantes ─────────────────────────────────────────

    /**
     * L236 + L251 branch manquante : recherche != null mais isEmpty() → false
     * → `if (recherche != null && !recherche.isEmpty())` → false (recherche est "")
     */
    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec recherche vide (empty string) couvre la branche isEmpty")
    void rechercheAvancee_emptyRecherche_treatedAsNoFilter() {
        Page page = new Page(0, 10);
        // recherche = "" → !recherche.isEmpty() est false → branche false du && → pas filtré
        List<Membre> list = membreRepository.rechercheAvancee("", null, null, null, page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("listAll et count cohérents")
    void listAll_count_consistent() {
        List<Membre> all = membreRepository.listAll();
        long count = membreRepository.count();
        assertThat((long) all.size()).isEqualTo(count);
    }

    // ── Branches manquantes ────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findAllActifs(Page, Sort): sort non-null → ORDER BY (branche sort != null)")
    void findAllActifs_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findAllActifs(page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findAllActifs(Page, Sort): sort null → pas de ORDER BY (branche sort null)")
    void findAllActifs_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findAllActifs(page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenom(Page,Sort): sort non-null → ORDER BY (branche sort != null)")
    void findByNomOrPrenom_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Descending);
        List<Membre> list = membreRepository.findByNomOrPrenom("Test", page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenom(Page,Sort): sort null → pas de ORDER BY")
    void findByNomOrPrenom_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByNomOrPrenom("Test", page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findDistinctByOrganisationIdIn: null → retourne liste vide")
    void findDistinctByOrganisationIdIn_null_returnsEmpty() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(null, page, null);
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findDistinctByOrganisationIdIn: vide → retourne liste vide")
    void findDistinctByOrganisationIdIn_empty_returnsEmpty() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(java.util.Set.of(), page, null);
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findDistinctByOrganisationIdIn: ids valides + sort non-null → exécute la requête")
    void findDistinctByOrganisationIdIn_withIds_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        // ids non existants → liste vide, mais code exécuté
        List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(
                java.util.Set.of(UUID.randomUUID()), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findDistinctByOrganisationIdIn: ids valides + sort null → exécute la requête")
    void findDistinctByOrganisationIdIn_withIds_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(
                java.util.Set.of(UUID.randomUUID()), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countDistinctByOrganisationIdIn: null → retourne 0L")
    void countDistinctByOrganisationIdIn_null_returnsZero() {
        long count = membreRepository.countDistinctByOrganisationIdIn(null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countDistinctByOrganisationIdIn: vide → retourne 0L")
    void countDistinctByOrganisationIdIn_empty_returnsZero() {
        long count = membreRepository.countDistinctByOrganisationIdIn(java.util.Set.of());
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countDistinctByOrganisationIdIn: ids valides → exécute la requête")
    void countDistinctByOrganisationIdIn_withIds_executesQuery() {
        long count = membreRepository.countDistinctByOrganisationIdIn(java.util.Set.of(UUID.randomUUID()));
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countActifsDistinctByOrganisationIdIn: null → retourne 0L")
    void countActifsDistinctByOrganisationIdIn_null_returnsZero() {
        long count = membreRepository.countActifsDistinctByOrganisationIdIn(null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countActifsDistinctByOrganisationIdIn: vide → retourne 0L")
    void countActifsDistinctByOrganisationIdIn_empty_returnsZero() {
        long count = membreRepository.countActifsDistinctByOrganisationIdIn(java.util.Set.of());
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countActifsDistinctByOrganisationIdIn: ids valides → exécute la requête")
    void countActifsDistinctByOrganisationIdIn_withIds_executesQuery() {
        long count = membreRepository.countActifsDistinctByOrganisationIdIn(java.util.Set.of(UUID.randomUUID()));
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenomAndOrganisationIdIn: null → retourne liste vide")
    void findByNomOrPrenomAndOrganisationIdIn_null_returnsEmpty() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn("Test", null, page, null);
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenomAndOrganisationIdIn: vide → retourne liste vide")
    void findByNomOrPrenomAndOrganisationIdIn_empty_returnsEmpty() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn("Test", java.util.Set.of(), page, null);
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenomAndOrganisationIdIn: ids valides + sort non-null → exécute la requête")
    void findByNomOrPrenomAndOrganisationIdIn_withIds_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn(
                "Test", java.util.Set.of(UUID.randomUUID()), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByNomOrPrenomAndOrganisationIdIn: ids valides + sort null → exécute la requête")
    void findByNomOrPrenomAndOrganisationIdIn_withIds_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn(
                "Test", java.util.Set.of(UUID.randomUUID()), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countNouveauxMembresByOrganisationId: null → retourne 0L")
    void countNouveauxMembresByOrganisationId_null_returnsZero() {
        long count = membreRepository.countNouveauxMembresByOrganisationId(LocalDate.now().minusMonths(1), null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNouveauxMembresByOrganisationId: org valide → exécute la requête")
    void countNouveauxMembresByOrganisationId_valid_executesQuery() {
        long count = membreRepository.countNouveauxMembresByOrganisationId(LocalDate.now().minusMonths(1), UUID.randomUUID());
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNouveauxMembresByOrganisationIdInPeriod: null → retourne 0L")
    void countNouveauxMembresByOrganisationIdInPeriod_null_returnsZero() {
        long count = membreRepository.countNouveauxMembresByOrganisationIdInPeriod(
                LocalDate.now().minusMonths(1), LocalDate.now(), null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNouveauxMembresByOrganisationIdInPeriod: org valide → exécute la requête")
    void countNouveauxMembresByOrganisationIdInPeriod_valid_executesQuery() {
        long count = membreRepository.countNouveauxMembresByOrganisationIdInPeriod(
                LocalDate.now().minusMonths(1), LocalDate.now(), UUID.randomUUID());
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut: sort non-null → ORDER BY (branche sort != null)")
    void findByStatut_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findByStatut(true, page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut: sort null → pas de ORDER BY")
    void findByStatut_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByStatut(false, page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByTrancheAge: sort non-null → ORDER BY (branche sort != null)")
    void findByTrancheAge_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateNaissance", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findByTrancheAge(20, 40, page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByTrancheAge: sort null → pas de ORDER BY")
    void findByTrancheAge_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findByTrancheAge(20, 40, page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: colonnes multiples couvre la branche i>0 (via findAllActifs)")
    void buildOrderBy_multipleColumns_coversIPlusBranch() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Descending).and("prenom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findAllActifs(page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: direction ASC couvre la branche Ascending (via findAllActifs)")
    void buildOrderBy_ascendingDirection_coversAscBranch() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending);
        List<Membre> list = membreRepository.findAllActifs(page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByKeycloakUserId: string non-UUID → retourne empty (catch IllegalArgumentException)")
    void findByKeycloakUserId_nonUuid_returnsEmpty() {
        Optional<Membre> found = membreRepository.findByKeycloakUserId("not-a-uuid");
        assertThat(found).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: sort non-null colonnes vides → fallback m.id (branche isEmpty L265)")
    void buildOrderBy_sortNonNullEmptyColumns_fallback() throws Exception {
        // Sort non-null avec liste colonnes vide → L265: isEmpty() = true → fallback "m.id"
        java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
        ctor.setAccessible(true);
        Sort emptySort = ctor.newInstance();

        Page page = new Page(0, 10);
        List<Membre> list = membreRepository.findAllActifs(page, emptySort);
        assertThat(list).isNotNull();
    }
}
