package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.AlerteLcbFt;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
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.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

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

@QuarkusTest
class AlerteLcbFtRepositoryTest {

    @Inject
    AlerteLcbFtRepository alerteLcbFtRepository;
    @Inject
    OrganisationRepository organisationRepository;
    @Inject
    MembreRepository membreRepository;

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

    private Membre newMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("AL-" + UUID.randomUUID().toString().substring(0, 8));
        m.setPrenom("Test");
        m.setNom("User");
        m.setEmail("alerte-mb-" + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1985, 6, 15));
        m.setActif(true);
        membreRepository.persist(m);
        return m;
    }

    private AlerteLcbFt newAlerte(Organisation org, Membre membre, String typeAlerte, boolean traitee) {
        AlerteLcbFt a = AlerteLcbFt.builder()
                .organisation(org)
                .membre(membre)
                .typeAlerte(typeAlerte)
                .dateAlerte(LocalDateTime.now())
                .description("Alerte de test " + UUID.randomUUID().toString().substring(0, 8))
                .montant(BigDecimal.valueOf(1500000))
                .seuil(BigDecimal.valueOf(1000000))
                .severite("WARNING")
                .traitee(traitee)
                .typeOperation("DEPOT")
                .transactionRef(UUID.randomUUID().toString())
                .build();
        a.setActif(true);
        alerteLcbFtRepository.persist(a);
        return a;
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById retrouve l'alerte")
    void persist_thenFindById_findsAlerte() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        AlerteLcbFt a = newAlerte(org, membre, "SEUIL_DEPASSE", false);
        assertThat(a.getId()).isNotNull();
        AlerteLcbFt found = alerteLcbFtRepository.findById(a.getId());
        assertThat(found).isNotNull();
        assertThat(found.getTypeAlerte()).isEqualTo("SEUIL_DEPASSE");
    }

    @Test
    @TestTransaction
    @DisplayName("search sans filtres retourne une liste")
    void search_noFilters_returnsList() {
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(null, null, null, null, null, 0, 10);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec filtre organisationId retourne les alertes de l'organisation")
    void search_withOrganisationId_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(org.getId(), null, null, null, null, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
        assertThat(list).allMatch(a -> a.getOrganisation().getId().equals(org.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec filtre typeAlerte retourne les alertes du bon type")
    void search_withTypeAlerte_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "JUSTIFICATION_MANQUANTE", false);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                null, "JUSTIFICATION_MANQUANTE", null, null, null, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
        assertThat(list).allMatch(a -> "JUSTIFICATION_MANQUANTE".equals(a.getTypeAlerte()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec filtre traitee=false retourne les alertes non traitées")
    void search_withTraiteeFalse_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                org.getId(), null, false, null, null, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
        assertThat(list).allMatch(a -> Boolean.FALSE.equals(a.getTraitee()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec filtre traitee=true retourne les alertes traitées")
    void search_withTraiteeTrue_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", true);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                org.getId(), null, true, null, null, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).allMatch(a -> Boolean.TRUE.equals(a.getTraitee()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec dateDebut retourne les alertes depuis la date")
    void search_withDateDebut_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        LocalDateTime dateDebut = LocalDateTime.now().minusDays(1);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                org.getId(), null, null, dateDebut, null, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
    }

    @Test
    @TestTransaction
    @DisplayName("search avec dateFin retourne les alertes jusqu'à la date")
    void search_withDateFin_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        LocalDateTime dateFin = LocalDateTime.now().plusDays(1);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                org.getId(), null, null, null, dateFin, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
    }

    @Test
    @TestTransaction
    @DisplayName("search avec tous les filtres retourne liste filtrée")
    void search_withAllFilters_returnsList() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        LocalDateTime debut = LocalDateTime.now().minusHours(1);
        LocalDateTime fin = LocalDateTime.now().plusHours(1);
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(
                org.getId(), "SEUIL_DEPASSE", false, debut, fin, 0, 10);
        assertThat(list).isNotNull();
        assertThat(list).hasSizeGreaterThanOrEqualTo(1);
    }

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

    /**
     * L39 + L63 branch manquante (search) : typeAlerte != null mais isBlank() → false
     * → `if (typeAlerte != null && !typeAlerte.isBlank())` → false (typeAlerte est "  ")
     */
    @Test
    @TestTransaction
    @DisplayName("search avec typeAlerte blank couvre la branche isBlank (ignoré comme filtre)")
    void search_withBlankTypeAlerte_treatedAsNoFilter() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);

        // typeAlerte = "  " → !typeAlerte.isBlank() est false → branche false → ignoré
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(org.getId(), "  ", null, null, null, 0, 10);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec pagination page 1 retourne liste")
    void search_secondPage_returnsList() {
        List<AlerteLcbFt> list = alerteLcbFtRepository.search(null, null, null, null, null, 1, 5);
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("count sans filtres retourne un nombre >= 0")
    void count_noFilters_returnsNonNegative() {
        long count = alerteLcbFtRepository.count(null, null, null, null, null);
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("count avec filtre organisationId retourne le bon compte")
    void count_withOrganisationId_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        long count = alerteLcbFtRepository.count(org.getId(), null, null, null, null);
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("count avec filtre typeAlerte retourne le bon compte")
    void count_withTypeAlerte_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        long count = alerteLcbFtRepository.count(null, "SEUIL_DEPASSE", null, null, null);
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("count avec filtre traitee retourne le bon compte")
    void count_withTraitee_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        long count = alerteLcbFtRepository.count(org.getId(), null, false, null, null);
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("count avec filtre dateDebut et dateFin retourne le bon compte")
    void count_withDateRange_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        LocalDateTime debut = LocalDateTime.now().minusDays(1);
        LocalDateTime fin = LocalDateTime.now().plusDays(1);
        long count = alerteLcbFtRepository.count(org.getId(), null, null, debut, fin);
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("count avec tous les filtres retourne le bon compte")
    void count_withAllFilters_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        LocalDateTime debut = LocalDateTime.now().minusHours(1);
        LocalDateTime fin = LocalDateTime.now().plusHours(1);
        long count = alerteLcbFtRepository.count(org.getId(), "SEUIL_DEPASSE", false, debut, fin);
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNonTraitees avec null retourne le total des alertes non traitées")
    void countNonTraitees_null_returnsTotalNonTraitees() {
        long count = alerteLcbFtRepository.countNonTraitees(null);
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNonTraitees pour une organisation retourne le bon compte")
    void countNonTraitees_withOrganisationId_returnsCount() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);
        long count = alerteLcbFtRepository.countNonTraitees(org.getId());
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNonTraitees pour organisation sans alertes retourne 0")
    void countNonTraitees_noAlerts_returnsZero() {
        Organisation org = newOrganisation();
        long count = alerteLcbFtRepository.countNonTraitees(org.getId());
        assertThat(count).isEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countNonTraitees après traitement de toutes les alertes retourne 0")
    void countNonTraitees_allTreated_returnsZero() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", true);
        long count = alerteLcbFtRepository.countNonTraitees(org.getId());
        assertThat(count).isEqualTo(0L);
    }

    /**
     * L101 + L123 branch manquante (count) : typeAlerte != null mais isBlank() → false
     * → `if (typeAlerte != null && !typeAlerte.isBlank())` → false (typeAlerte est "  ")
     */
    @Test
    @TestTransaction
    @DisplayName("count avec typeAlerte blank couvre la branche isBlank (ignoré comme filtre)")
    void count_withBlankTypeAlerte_treatedAsNoFilter() {
        Organisation org = newOrganisation();
        Membre membre = newMembre();
        newAlerte(org, membre, "SEUIL_DEPASSE", false);

        // typeAlerte = "  " → !typeAlerte.isBlank() est false → branche false → ignoré
        long count = alerteLcbFtRepository.count(org.getId(), "  ", null, null, null);
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("count global retourne un nombre >= 0")
    void countAll_returnsNonNegative() {
        long count = alerteLcbFtRepository.count();
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("listAll retourne une liste")
    void listAll_returnsList() {
        List<AlerteLcbFt> list = alerteLcbFtRepository.listAll();
        assertThat(list).isNotNull();
    }
}
