package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Cotisation;
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.LocalDate;
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 CotisationRepositoryTest {

    @Inject
    CotisationRepository cotisationRepository;
    @Inject
    OrganisationRepository organisationRepository;
    @Inject
    MembreRepository membreRepository;

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

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

    @Test
    @TestTransaction
    @DisplayName("findByNumeroReference retourne empty pour référence inexistante")
    void findByNumeroReference_inexistant_returnsEmpty() {
        Optional<Cotisation> opt = cotisationRepository.findByNumeroReference("REF-" + UUID.randomUUID());
        assertThat(opt).isEmpty();
    }

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

    @Test
    @TestTransaction
    @DisplayName("findByStatut retourne une liste")
    void findByStatut_returnsList() {
        Page page = new Page(0, 10);
        List<Cotisation> list = cotisationRepository.findByStatut("EN_ATTENTE", page);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findCotisationsEnRetard retourne une liste")
    void findCotisationsEnRetard_returnsList() {
        Page page = new Page(0, 10);
        List<Cotisation> list = cotisationRepository.findCotisationsEnRetard(LocalDate.now(), page);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByPeriode avec année retourne une liste")
    void findByPeriode_returnsList() {
        Page page = new Page(0, 10);
        List<Cotisation> list = cotisationRepository.findByPeriode(LocalDate.now().getYear(), null, page);
        assertThat(list).isNotNull();
    }

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

    @Test
    @TestTransaction
    @DisplayName("calculerTotalMontantDu retourne zéro pour membre sans cotisation")
    void calculerTotalMontantDu_returnsZero() {
        BigDecimal total = cotisationRepository.calculerTotalMontantDu(UUID.randomUUID());
        assertThat(total).isEqualByComparingTo(BigDecimal.ZERO);
    }

    @Test
    @TestTransaction
    @DisplayName("compterParStatut retourne un nombre >= 0")
    void compterParStatut_returnsNonNegative() {
        long n = cotisationRepository.compterParStatut("PAYEE");
        assertThat(n).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("sommeMontantDu retourne un BigDecimal")
    void sommeMontantDu_returnsBigDecimal() {
        BigDecimal sum = cotisationRepository.sommeMontantDu();
        assertThat(sum).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("getStatistiquesPeriode retourne une map avec les clés attendues")
    void getStatistiquesPeriode_returnsMap() {
        int annee = LocalDate.now().getYear();
        Map<String, Object> stats = cotisationRepository.getStatistiquesPeriode(annee, null);
        assertThat(stats).containsKeys("totalCotisations", "montantTotal", "montantPaye", "cotisationsPayees", "tauxPaiement");
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy avec plusieurs colonnes couvre la branche i>0 et direction ASC")
    void buildOrderBy_multipleColumns_ascAndDesc() {
        Page page = new Page(0, 10);
        // Sort with two columns: first DESC, second ASC — exercises i>0 and Ascending branch
        Sort sort = Sort.by("annee", Sort.Direction.Descending)
                        .and("mois", Sort.Direction.Ascending);
        List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("buildOrderBy avec une colonne ASC couvre la branche direction Ascending")
    void buildOrderBy_singleColumnAscending() {
        Page page = new Page(0, 10);
        Sort sort = Sort.by("annee", Sort.Direction.Ascending);
        List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, sort);
        assertThat(list).isNotNull();
    }

    // ── Branch coverage manquante : buildOrderBy ───────────────────────────

    /**
     * L586 branch manquante : sort != null mais sort.getColumns().isEmpty() → true
     * → `if (sort == null || sort.getColumns().isEmpty())` → true via deuxième terme
     * On crée un Sort non-null avec colonnes vides via réflexion.
     */
    @Test
    @TestTransaction
    @DisplayName("buildOrderBy: sort non-null avec colonnes vides → fallback dateEcheance DESC (branche isEmpty)")
    void buildOrderBy_sortNonNull_emptyColumns_fallback() throws Exception {
        // Créer un Sort non-null avec liste de colonnes vide via le constructeur privé
        java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
        ctor.setAccessible(true);
        Sort emptySort = ctor.newInstance();
        // emptySort.getColumns() retourne une ArrayList vide → isEmpty() = true

        Page page = new Page(0, 10);
        // Ne doit pas lancer d'exception et retourner une liste (avec ORDER BY c.dateEcheance DESC)
        List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, emptySort);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countPayeesByMembreId avec membreId null retourne 0 (branche null true)")
    void countPayeesByMembreId_nullMembreId_returnsZero() {
        long result = cotisationRepository.countPayeesByMembreId(null);
        assertThat(result).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByPeriode avec mois non-null couvre la branche mois != null")
    void findByPeriode_avecMois_returnsList() {
        Page page = new Page(0, 10);
        List<Cotisation> list = cotisationRepository.findByPeriode(LocalDate.now().getYear(), LocalDate.now().getMonthValue(), page);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findByNumeroReference retrouve la cotisation")
    void persist_thenFindByNumeroReference_findsCotisation() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        String ref = "COT-TEST-" + UUID.randomUUID().toString().substring(0, 8);
        Cotisation c = Cotisation.builder()
                .numeroReference(ref)
                .membre(membre)
                .organisation(org)
                .typeCotisation("ANNUELLE")
                .libelle("Cotisation test")
                .montantDu(BigDecimal.valueOf(5000))
                .codeDevise("XOF")
                .statut("EN_ATTENTE")
                .annee(LocalDate.now().getYear())
                .mois(LocalDate.now().getMonthValue())
                .dateEcheance(LocalDate.now().plusMonths(1))
                .build();
        cotisationRepository.persist(c);
        Optional<Cotisation> found = cotisationRepository.findByNumeroReference(ref);
        assertThat(found).isPresent();
        assertThat(found.get().getNumeroReference()).isEqualTo(ref);
    }

    @Test
    @TestTransaction
    @DisplayName("findByType(String, Page) retourne une liste non-null")
    void findByType_avecPage_returnsList() {
        Page page = new Page(0, 10);
        List<Cotisation> list = cotisationRepository.findByType("ANNUELLE", page);
        assertThat(list).isNotNull();
    }
}
