package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Paiement;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import org.junit.jupiter.api.BeforeEach;
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;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@DisplayName("PaiementRepository — tests unitaires")
class PaiementRepositoryTest {

    static class TestablePaiementRepository extends PaiementRepository {
        // instanciation sans CDI
    }

    private PaiementRepository repo;

    @BeforeEach
    void setUp() {
        repo = spy(new TestablePaiementRepository());
    }

    @Test
    @DisplayName("classExists : instanciation possible sans CDI")
    void classExists() {
        assertThat(new TestablePaiementRepository()).isNotNull();
    }

    // ─── findPaiementById ─────────────────────────────────────────────────────

    @Test
    @DisplayName("findPaiementById : retourne Optional.empty() si aucun paiement")
    @SuppressWarnings("unchecked")
    void findPaiementById_noResult_returnsEmpty() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.empty());
        doReturn(query).when(repo).find(anyString(), any(UUID.class));

        Optional<Paiement> result = repo.findPaiementById(UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findPaiementById : retourne le paiement si trouvé")
    @SuppressWarnings("unchecked")
    void findPaiementById_withResult_returnsPaiement() {
        Paiement paiement = new Paiement();
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.of(paiement));
        doReturn(query).when(repo).find(anyString(), any(UUID.class));

        Optional<Paiement> result = repo.findPaiementById(UUID.randomUUID());

        assertThat(result).isPresent().contains(paiement);
    }

    // ─── findByNumeroReference ────────────────────────────────────────────────

    @Test
    @DisplayName("findByNumeroReference : retourne Optional.empty() si référence inconnue")
    @SuppressWarnings("unchecked")
    void findByNumeroReference_noResult_returnsEmpty() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.empty());
        doReturn(query).when(repo).find(anyString(), anyString());

        Optional<Paiement> result = repo.findByNumeroReference("PAY-UNKNOWN-999");

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByNumeroReference : retourne le paiement correspondant")
    @SuppressWarnings("unchecked")
    void findByNumeroReference_withResult_returnsPaiement() {
        Paiement paiement = new Paiement();
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.of(paiement));
        doReturn(query).when(repo).find(anyString(), anyString());

        Optional<Paiement> result = repo.findByNumeroReference("PAY-2026-001");

        assertThat(result).isPresent().contains(paiement);
    }

    // ─── findByMembreId ───────────────────────────────────────────────────────

    @Test
    @DisplayName("findByMembreId : retourne liste vide si aucun paiement")
    @SuppressWarnings("unchecked")
    void findByMembreId_noResult_returnsEmpty() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of());
        doReturn(query).when(repo).find(anyString(), any(io.quarkus.panache.common.Sort.class), any(UUID.class));

        List<Paiement> result = repo.findByMembreId(UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByMembreId : retourne les paiements du membre")
    @SuppressWarnings("unchecked")
    void findByMembreId_withResult_returnsList() {
        Paiement p1 = new Paiement();
        Paiement p2 = new Paiement();
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of(p1, p2));
        doReturn(query).when(repo).find(anyString(), any(io.quarkus.panache.common.Sort.class), any(UUID.class));

        List<Paiement> result = repo.findByMembreId(UUID.randomUUID());

        assertThat(result).hasSize(2);
    }

    // ─── findByStatut ─────────────────────────────────────────────────────────

    @Test
    @DisplayName("findByStatut : retourne les paiements du statut donné")
    @SuppressWarnings("unchecked")
    void findByStatut_returnsList() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of());
        doReturn(query).when(repo).find(anyString(), any(io.quarkus.panache.common.Sort.class), anyString());

        List<Paiement> result = repo.findByStatut("VALIDE");

        assertThat(result).isNotNull();
    }

    // ─── findByMethode ────────────────────────────────────────────────────────

    @Test
    @DisplayName("findByMethode : retourne les paiements de la méthode donnée")
    @SuppressWarnings("unchecked")
    void findByMethode_returnsList() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of());
        doReturn(query).when(repo).find(anyString(), any(io.quarkus.panache.common.Sort.class), anyString());

        List<Paiement> result = repo.findByMethode("WAVE");

        assertThat(result).isNotNull();
    }

    // ─── findValidesParPeriode ────────────────────────────────────────────────

    @Test
    @DisplayName("findValidesParPeriode : retourne la liste des paiements validés dans la période")
    @SuppressWarnings("unchecked")
    void findValidesParPeriode_returnsList() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of());
        doReturn(query).when(repo).find(
                anyString(),
                any(io.quarkus.panache.common.Sort.class),
                any(LocalDateTime.class),
                any(LocalDateTime.class));

        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        List<Paiement> result = repo.findValidesParPeriode(debut, fin);

        assertThat(result).isNotNull();
    }

    // ─── calculerMontantTotalValides ──────────────────────────────────────────

    @Test
    @DisplayName("calculerMontantTotalValides : retourne ZERO si aucun paiement validé")
    @SuppressWarnings("unchecked")
    void calculerMontantTotalValides_noPaiements_returnsZero() {
        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of());
        doReturn(query).when(repo).find(
                anyString(),
                any(io.quarkus.panache.common.Sort.class),
                any(LocalDateTime.class),
                any(LocalDateTime.class));

        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        BigDecimal total = repo.calculerMontantTotalValides(debut, fin);

        assertThat(total).isEqualByComparingTo(BigDecimal.ZERO);
    }

    @Test
    @DisplayName("calculerMontantTotalValides : somme les montants des paiements validés")
    @SuppressWarnings("unchecked")
    void calculerMontantTotalValides_withPaiements_returnsSum() {
        Paiement p1 = new Paiement();
        p1.setMontant(new BigDecimal("10000"));
        Paiement p2 = new Paiement();
        p2.setMontant(new BigDecimal("5000"));

        PanacheQuery<Paiement> query = mock(PanacheQuery.class);
        when(query.list()).thenReturn(List.of(p1, p2));
        doReturn(query).when(repo).find(
                anyString(),
                any(io.quarkus.panache.common.Sort.class),
                any(LocalDateTime.class),
                any(LocalDateTime.class));

        LocalDateTime debut = LocalDateTime.now().minusDays(30);
        LocalDateTime fin = LocalDateTime.now();
        BigDecimal total = repo.calculerMontantTotalValides(debut, fin);

        assertThat(total).isEqualByComparingTo("15000");
    }
}
