package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Organisation;
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.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

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

@QuarkusTest
class EvenementRepositoryTest {

    @Inject
    EvenementRepository evenementRepository;
    @Inject
    OrganisationRepository organisationRepository;

    private Organisation newOrganisation() {
        Organisation o = new Organisation();
        o.setNom("Org Evenement");
        o.setTypeOrganisation("ASSOCIATION");
        o.setStatut("ACTIVE");
        o.setEmail("evt-org-" + UUID.randomUUID() + "@test.com");
        o.setActif(true);
        organisationRepository.persist(o);
        return o;
    }

    private Evenement newEvenement(Organisation org) {
        Evenement e = new Evenement();
        e.setTitre("Événement test");
        e.setDescription("Description");
        e.setDateDebut(LocalDateTime.now().plusDays(1));
        e.setStatut("PLANIFIE");
        e.setTypeEvenement("REUNION");
        e.setOrganisation(org);
        e.setActif(true);
        e.setVisiblePublic(true);
        return e;
    }

    @Test
    @TestTransaction
    @DisplayName("findByTitre retourne empty pour titre inexistant")
    void findByTitre_inexistant_returnsEmpty() {
        Optional<Evenement> opt = evenementRepository.findByTitre("Titre inexistant " + UUID.randomUUID());
        assertThat(opt).isEmpty();
    }

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

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

    @Test
    @TestTransaction
    @DisplayName("findByStatut retourne une liste")
    void findByStatut_returnsList() {
        List<Evenement> list = evenementRepository.findByStatut("PLANIFIE");
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisation retourne une liste")
    void findByOrganisation_returnsList() {
        List<Evenement> list = evenementRepository.findByOrganisation(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsAVenir retourne une liste")
    void findEvenementsAVenir_returnsList() {
        List<Evenement> list = evenementRepository.findEvenementsAVenir();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsPublics retourne une liste")
    void findEvenementsPublics_returnsList() {
        List<Evenement> list = evenementRepository.findEvenementsPublics();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("getStatistiques retourne une map avec des clés attendues")
    void getStatistiques_returnsMap() {
        Map<String, Long> stats = evenementRepository.getStatistiques();
        assertThat(stats).containsKeys("total", "actifs", "inactifs", "aVenir", "enCours", "passes", "publics", "avecInscription");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec tous critères null retourne une liste")
    void rechercheAvancee_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.rechercheAvancee(
                null, null, null, null, null,
                null, null, 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("dateDebut", Sort.Direction.Descending);
        List<Evenement> list = evenementRepository.rechercheAvancee(
                null, null, null, null, null,
                null, null, null, null, null,
                page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec tous les critères fournis couvre toutes les branches de filtre")
    void rechercheAvancee_allFilters_returnsList() {
        Organisation org = newOrganisation();
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.rechercheAvancee(
                "test",              // recherche non null
                "PLANIFIE",          // statut non null
                "REUNION",           // type non null
                org.getId(),         // organisationId non null
                UUID.randomUUID(),   // organisateurId non null
                LocalDateTime.now().minusDays(1),  // dateDebutMin non null
                LocalDateTime.now().plusDays(30),  // dateDebutMax non null
                Boolean.TRUE,        // visiblePublic non null
                Boolean.FALSE,       // inscriptionRequise non null
                Boolean.TRUE,        // actif non null
                page, null);
        assertThat(list).isNotNull();
    }

    // ── Branch coverage manquante ──────────────────────────────────────────

    /**
     * L306 branch manquante : recherche != null mais recherche.trim().isEmpty() → false
     * → `if (recherche != null && !recherche.trim().isEmpty())` → false (recherche = "  ")
     */
    @Test
    @TestTransaction
    @DisplayName("rechercheAvancee avec recherche blank (whitespace) couvre la branche trim().isEmpty()")
    void rechercheAvancee_blankRecherche_treatedAsNoFilter() {
        Page page = new Page(0, 10);
        // recherche = "  " → trim().isEmpty() est true → !trim().isEmpty() est false → ignoré
        List<Evenement> list = evenementRepository.rechercheAvancee(
                "   ", null, null, null, null,
                null, null, null, null, null,
                page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById et findByTitre retrouvent l'événement")
    void persist_thenFind_findsEvenement() {
        Organisation org = newOrganisation();
        Evenement e = newEvenement(org);
        evenementRepository.persist(e);
        assertThat(e.getId()).isNotNull();
        Evenement found = evenementRepository.findById(e.getId());
        assertThat(found).isNotNull();
        assertThat(found.getTitre()).isEqualTo("Événement test");
        Optional<Evenement> byTitre = evenementRepository.findByTitre("Événement test");
        assertThat(byTitre).isPresent();
    }

    // ── 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("dateDebut", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findAllActifs(page, sort);
        assertThat(list).isNotNull();
    }

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

    @Test
    @TestTransaction
    @DisplayName("findByStatut(Page, Sort): sort non-null → ORDER BY")
    void findByStatut_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDebut", Sort.Direction.Descending);
        List<Evenement> list = evenementRepository.findByStatut("PLANIFIE", page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut(Page, Sort): sort null → pas de ORDER BY")
    void findByStatut_withPage_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findByStatut("PLANIFIE", page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByType(Page, Sort): sort non-null → ORDER BY")
    void findByType_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("titre", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findByType("REUNION", page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByType(Page, Sort): sort null → pas de ORDER BY")
    void findByType_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findByType("REUNION", page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisation(Page, Sort): sort non-null → ORDER BY")
    void findByOrganisation_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDebut", Sort.Direction.Descending);
        List<Evenement> list = evenementRepository.findByOrganisation(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisation(Page, Sort): sort null → pas de ORDER BY")
    void findByOrganisation_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findByOrganisation(UUID.randomUUID(), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsAVenir(Page, Sort): sort non-null → ORDER BY")
    void findEvenementsAVenir_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findEvenementsAVenir(page, sort);
        assertThat(list).isNotNull();
    }

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

    @Test
    @TestTransaction
    @DisplayName("countByOrganisationId: null → retourne 0L")
    void countByOrganisationId_null_returnsZero() {
        long count = evenementRepository.countByOrganisationId(null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countByOrganisationId: org valide → exécute la requête")
    void countByOrganisationId_valid_executesQuery() {
        long count = evenementRepository.countByOrganisationId(UUID.randomUUID());
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countActifsByOrganisationId: null → retourne 0L")
    void countActifsByOrganisationId_null_returnsZero() {
        long count = evenementRepository.countActifsByOrganisationId(null);
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countActifsByOrganisationId: org valide → exécute la requête")
    void countActifsByOrganisationId_valid_executesQuery() {
        long count = evenementRepository.countActifsByOrganisationId(UUID.randomUUID());
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsAVenirByOrganisationId: null → retourne liste vide")
    void findEvenementsAVenirByOrganisationId_null_returnsEmpty() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(null, page, null);
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsAVenirByOrganisationId: org valide + sort non-null → ORDER BY")
    void findEvenementsAVenirByOrganisationId_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsAVenirByOrganisationId: org valide + sort null → ORDER BY dateDebut ASC (défaut)")
    void findEvenementsAVenirByOrganisationId_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(UUID.randomUUID(), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsPublics(Page, Sort): sort non-null → ORDER BY")
    void findEvenementsPublics_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findEvenementsPublics(page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEvenementsPublics(Page, Sort): sort null → pas de ORDER BY")
    void findEvenementsPublics_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<Evenement> list = evenementRepository.findEvenementsPublics(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("dateDebut", Sort.Direction.Descending).and("titre", Sort.Direction.Ascending);
        List<Evenement> list = evenementRepository.findAllActifs(page, sort);
        assertThat(list).isNotNull();
    }

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

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

    @Test
    @TestTransaction
    @DisplayName("findByType(String) retourne une liste non-null")
    void findByType_simpleString_returnsList() {
        List<Evenement> list = evenementRepository.findByType("CONFERENCE");
        assertThat(list).isNotNull();
    }
}
