package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.entity.Membre;
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.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

@QuarkusTest
class DemandeAideRepositoryTest {

    @Inject
    DemandeAideRepository demandeAideRepository;
    @Inject
    OrganisationRepository organisationRepository;
    @Inject
    MembreRepository membreRepository;

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

    private Membre newMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("M-" + UUID.randomUUID().toString().substring(0, 8));
        m.setPrenom("Test");
        m.setNom("User");
        m.setEmail("da-membre-" + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(java.time.LocalDate.of(1990, 1, 1));
        m.setActif(true);
        membreRepository.persist(m);
        return m;
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId retourne une liste")
    void findByOrganisationId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID());
        assertThat(list).isNotNull();
        assertThat(list).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId avec pagination retourne une liste")
    void findByOrganisationId_paged_returnsList() {
        Page page = new Page(0, 10);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut avec enum retourne une liste")
    void findByStatut_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByStatut(StatutAide.EN_ATTENTE);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByTypeAide retourne une liste")
    void findByTypeAide_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findUrgentes retourne une liste")
    void findUrgentes_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findUrgentes();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findRecentes retourne une liste")
    void findRecentes_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findRecentes();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("count retourne un nombre >= 0")
    void count_returnsNonNegative() {
        long n = demandeAideRepository.count();
        assertThat(n).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByPeriode retourne une liste")
    void findByPeriode_returnsList() {
        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        List<DemandeAide> list = demandeAideRepository.findByPeriode(debut, fin);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("sumMontantDemandeByOrganisationId retourne empty quand aucun montant")
    void sumMontantDemandeByOrganisationId_empty_returnsEmpty() {
        Optional<BigDecimal> sum = demandeAideRepository.sumMontantDemandeByOrganisationId(UUID.randomUUID());
        assertThat(sum).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById retrouve la demande")
    void persist_thenFindById_findsDemande() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        DemandeAide d = DemandeAide.builder()
                .titre("Test aide")
                .description("Description")
                .typeAide(TypeAide.AIDE_COTISATION)
                .statut(StatutAide.EN_ATTENTE)
                .montantDemande(BigDecimal.valueOf(1000))
                .dateDemande(LocalDateTime.now())
                .demandeur(membre)
                .organisation(org)
                .urgence(false)
                .build();
        demandeAideRepository.persist(d);
        assertThat(d.getId()).isNotNull();
        DemandeAide found = demandeAideRepository.findById(d.getId());
        assertThat(found).isNotNull();
        assertThat(found.getTitre()).isEqualTo("Test aide");
    }

    @Test
    @TestTransaction
    @DisplayName("sumMontantDemandeByOrganisationId retourne present quand montant > 0")
    void sumMontantDemandeByOrganisationId_withData_returnsPresent() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        DemandeAide d = DemandeAide.builder()
                .titre("Aide montant test")
                .description("Test")
                .typeAide(TypeAide.AIDE_COTISATION)
                .statut(StatutAide.EN_ATTENTE)
                .montantDemande(BigDecimal.valueOf(500))
                .dateDemande(LocalDateTime.now())
                .demandeur(membre)
                .organisation(org)
                .urgence(false)
                .build();
        demandeAideRepository.persist(d);
        Optional<BigDecimal> sum = demandeAideRepository.sumMontantDemandeByOrganisationId(org.getId());
        assertThat(sum).isPresent();
        assertThat(sum.get()).isGreaterThan(BigDecimal.ZERO);
    }

    @Test
    @TestTransaction
    @DisplayName("sumMontantApprouveByOrganisationId retourne empty quand aucun montant approuvé")
    void sumMontantApprouveByOrganisationId_empty_returnsEmpty() {
        Optional<BigDecimal> sum = demandeAideRepository.sumMontantApprouveByOrganisationId(UUID.randomUUID());
        assertThat(sum).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("sumMontantApprouveByOrganisationId retourne present quand montant approuvé > 0")
    void sumMontantApprouveByOrganisationId_withData_returnsPresent() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        DemandeAide d = DemandeAide.builder()
                .titre("Aide approuvée test")
                .description("Test")
                .typeAide(TypeAide.AIDE_COTISATION)
                .statut(StatutAide.APPROUVEE)
                .montantDemande(BigDecimal.valueOf(800))
                .montantApprouve(BigDecimal.valueOf(700))
                .dateDemande(LocalDateTime.now())
                .demandeur(membre)
                .organisation(org)
                .urgence(false)
                .build();
        demandeAideRepository.persist(d);
        Optional<BigDecimal> sum = demandeAideRepository.sumMontantApprouveByOrganisationId(org.getId());
        assertThat(sum).isPresent();
        assertThat(sum.get()).isGreaterThan(BigDecimal.ZERO);
    }

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

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId(Page, Sort): sort null → ORDER BY dateDemande DESC (défaut)")
    void findByOrganisationId_withPage_nullSort_returnsList() {
        Page page = new Page(0, 10);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, null);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId(Page, Sort): sort non-null → buildOrderBy utilisé")
    void findByOrganisationId_withPage_withSort_returnsList() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDemande", Sort.Direction.Descending);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: colonnes multiples → branche i>0 (via findByOrganisationId)")
    void buildOrderBy_multipleColumns_coversIPlusBranch() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("dateDemande", Sort.Direction.Descending).and("titre", Sort.Direction.Ascending);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: direction ASC → branche Ascending (via findByOrganisationId)")
    void buildOrderBy_ascendingDirection_coversAscBranch() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("titre", Sort.Direction.Ascending);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

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

        Page page = new Page(0, 10);
        List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, emptySort);
        assertThat(list).isNotNull();
    }

    // ── Méthodes non couvertes ────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByDemandeurId retourne une liste")
    void findByDemandeurId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByDemandeurId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatutAndOrganisationId retourne une liste")
    void findByStatutAndOrganisationId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByStatutAndOrganisationId(
                StatutAide.EN_ATTENTE, UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findUrgentesByOrganisationId retourne une liste")
    void findUrgentesByOrganisationId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findUrgentesByOrganisationId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByPeriodeAndOrganisationId retourne une liste")
    void findByPeriodeAndOrganisationId_returnsList() {
        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        List<DemandeAide> list = demandeAideRepository.findByPeriodeAndOrganisationId(
                debut, fin, UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countByStatut retourne un nombre >= 0")
    void countByStatut_returnsNonNegative() {
        long count = demandeAideRepository.countByStatut("EN_ATTENTE");
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countByStatutAndOrganisationId retourne un nombre >= 0")
    void countByStatutAndOrganisationId_returnsNonNegative() {
        long count = demandeAideRepository.countByStatutAndOrganisationId("EN_ATTENTE", UUID.randomUUID());
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findRecentesByOrganisationId retourne une liste")
    void findRecentesByOrganisationId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findRecentesByOrganisationId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEnAttenteDepuis retourne une liste")
    void findEnAttenteDepuis_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findEnAttenteDepuis(30);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByEvaluateurId retourne une liste")
    void findByEvaluateurId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findByEvaluateurId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEnCoursEvaluationByEvaluateurId retourne une liste")
    void findEnCoursEvaluationByEvaluateurId_returnsList() {
        List<DemandeAide> list = demandeAideRepository.findEnCoursEvaluationByEvaluateurId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countDemandesApprouvees retourne un nombre >= 0")
    void countDemandesApprouvees_returnsNonNegative() {
        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        long count = demandeAideRepository.countDemandesApprouvees(UUID.randomUUID(), debut, fin);
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countDemandes retourne un nombre >= 0")
    void countDemandes_returnsNonNegative() {
        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        long count = demandeAideRepository.countDemandes(UUID.randomUUID(), debut, fin);
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("sumMontantsAccordes retourne un montant >= 0")
    void sumMontantsAccordes_returnsNonNegative() {
        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        java.math.BigDecimal total = demandeAideRepository.sumMontantsAccordes(UUID.randomUUID(), debut, fin);
        assertThat(total).isNotNull();
        assertThat(total).isGreaterThanOrEqualTo(java.math.BigDecimal.ZERO);
    }
}
