package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
import dev.lions.unionflow.server.entity.CompteWave;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.TransactionWave;
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.Optional;
import java.util.UUID;

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

@QuarkusTest
class TransactionWaveRepositoryTest {

    @Inject
    TransactionWaveRepository transactionWaveRepository;

    @Inject
    CompteWaveRepository compteWaveRepository;

    @Inject
    MembreRepository membreRepository;

    @Inject
    OrganisationRepository organisationRepository;

    // ── helpers ──────────────────────────────────────────────────────────────

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

    private Membre persistMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("MEM-TW-" + UUID.randomUUID().toString().substring(0, 8));
        m.setPrenom("Prénom");
        m.setNom("TWTest");
        m.setEmail("membre-tw-" + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setActif(true);
        membreRepository.persist(m);
        return m;
    }

    private CompteWave persistCompteWave(Membre membre) {
        CompteWave cw = new CompteWave();
        // Use a unique 8-digit suffix to stay inside the +225XXXXXXXX pattern
        String suffix = String.format("%08d", (int) (Math.random() * 100_000_000));
        cw.setNumeroTelephone("+225" + suffix);
        cw.setMembre(membre);
        cw.setActif(true);
        compteWaveRepository.persist(cw);
        return cw;
    }

    private TransactionWave persistTransaction(CompteWave compteWave,
                                               StatutTransactionWave statut,
                                               TypeTransactionWave type) {
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId("WT-" + UUID.randomUUID());
        tw.setWaveRequestId("WR-" + UUID.randomUUID());
        tw.setTypeTransaction(type);
        tw.setStatutTransaction(statut);
        tw.setMontant(BigDecimal.valueOf(1000));
        tw.setCodeDevise("XOF");
        tw.setCompteWave(compteWave);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);
        return tw;
    }

    // ── tests hérités (inchangés) ─────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findById retourne null pour UUID inexistant")
    void findById_inexistant_returnsNull() {
        assertThat(transactionWaveRepository.findById(UUID.randomUUID())).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findTransactionWaveById retourne empty pour UUID inexistant")
    void findTransactionWaveById_inexistant_returnsEmpty() {
        Optional<TransactionWave> opt = transactionWaveRepository.findTransactionWaveById(UUID.randomUUID());
        assertThat(opt).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByWaveTransactionId retourne empty pour id inexistant")
    void findByWaveTransactionId_inexistant_returnsEmpty() {
        Optional<TransactionWave> opt = transactionWaveRepository.findByWaveTransactionId("wt-" + UUID.randomUUID());
        assertThat(opt).isEmpty();
    }

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

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

    @Test
    @TestTransaction
    @DisplayName("findByCompteWaveId retourne une liste")
    void findByCompteWaveId_returnsList() {
        List<TransactionWave> list = transactionWaveRepository.findByCompteWaveId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    // ── findTransactionWaveById ───────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findTransactionWaveById retrouve la transaction persistée")
    void findTransactionWaveById_found_returnsPresent() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        TransactionWave tw = persistTransaction(cw, StatutTransactionWave.INITIALISE, TypeTransactionWave.PAIEMENT);

        Optional<TransactionWave> result = transactionWaveRepository.findTransactionWaveById(tw.getId());

        assertThat(result).isPresent();
        assertThat(result.get().getId()).isEqualTo(tw.getId());
    }

    // ── findByWaveTransactionId ───────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByWaveTransactionId retrouve la transaction par son identifiant Wave")
    void findByWaveTransactionId_found_returnsPresent() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        String waveId = "WT-FIND-" + UUID.randomUUID();
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId(waveId);
        tw.setTypeTransaction(TypeTransactionWave.DEPOT);
        tw.setStatutTransaction(StatutTransactionWave.REUSSIE);
        tw.setMontant(BigDecimal.valueOf(500));
        tw.setCodeDevise("XOF");
        tw.setCompteWave(cw);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);

        Optional<TransactionWave> result = transactionWaveRepository.findByWaveTransactionId(waveId);

        assertThat(result).isPresent();
        assertThat(result.get().getWaveTransactionId()).isEqualTo(waveId);
    }

    // ── findByWaveRequestId ───────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByWaveRequestId retourne empty pour requestId inexistant")
    void findByWaveRequestId_inexistant_returnsEmpty() {
        Optional<TransactionWave> result = transactionWaveRepository.findByWaveRequestId("WR-INEXISTANT-" + UUID.randomUUID());
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByWaveRequestId retrouve la transaction par son requestId")
    void findByWaveRequestId_found_returnsPresent() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        String requestId = "WR-FIND-" + UUID.randomUUID();
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId("WT-REQ-" + UUID.randomUUID());
        tw.setWaveRequestId(requestId);
        tw.setTypeTransaction(TypeTransactionWave.RETRAIT);
        tw.setStatutTransaction(StatutTransactionWave.INITIALISE);
        tw.setMontant(BigDecimal.valueOf(2000));
        tw.setCodeDevise("XOF");
        tw.setCompteWave(cw);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);

        Optional<TransactionWave> result = transactionWaveRepository.findByWaveRequestId(requestId);

        assertThat(result).isPresent();
        assertThat(result.get().getWaveRequestId()).isEqualTo(requestId);
    }

    // ── findByCompteWaveId ────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByCompteWaveId retourne liste vide pour compte sans transactions")
    void findByCompteWaveId_noTransactions_returnsEmpty() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);

        List<TransactionWave> result = transactionWaveRepository.findByCompteWaveId(cw.getId());

        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByCompteWaveId retrouve les transactions du compte")
    void findByCompteWaveId_withTransactions_returnsList() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        persistTransaction(cw, StatutTransactionWave.REUSSIE, TypeTransactionWave.PAIEMENT);
        persistTransaction(cw, StatutTransactionWave.ECHOUE, TypeTransactionWave.TRANSFERT);

        List<TransactionWave> result = transactionWaveRepository.findByCompteWaveId(cw.getId());

        assertThat(result).hasSize(2);
    }

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

    @Test
    @TestTransaction
    @DisplayName("findByStatut retourne liste vide quand aucune transaction ne correspond")
    void findByStatut_noMatch_returnsEmpty() {
        // Use ANNULEE which is unlikely to exist in a clean test transaction
        List<TransactionWave> result = transactionWaveRepository.findByStatut(StatutTransactionWave.ANNULEE);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut retrouve les transactions du statut demandé")
    void findByStatut_withMatch_returnsList() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        persistTransaction(cw, StatutTransactionWave.EN_COURS, TypeTransactionWave.DEPOT);

        List<TransactionWave> result = transactionWaveRepository.findByStatut(StatutTransactionWave.EN_COURS);

        assertThat(result).isNotEmpty();
        assertThat(result).allMatch(tw -> tw.getStatutTransaction() == StatutTransactionWave.EN_COURS);
    }

    // ── findByType ────────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByType retourne liste vide quand aucune transaction ne correspond")
    void findByType_noMatch_returnsEmpty() {
        List<TransactionWave> result = transactionWaveRepository.findByType(TypeTransactionWave.REMBOURSEMENT);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByType retrouve les transactions du type demandé")
    void findByType_withMatch_returnsList() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        persistTransaction(cw, StatutTransactionWave.REUSSIE, TypeTransactionWave.REMBOURSEMENT);

        List<TransactionWave> result = transactionWaveRepository.findByType(TypeTransactionWave.REMBOURSEMENT);

        assertThat(result).isNotEmpty();
        assertThat(result).allMatch(tw -> tw.getTypeTransaction() == TypeTransactionWave.REMBOURSEMENT);
    }

    // ── findReussiesByCompteWave ──────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findReussiesByCompteWave retourne liste vide pour compte sans transactions réussies")
    void findReussiesByCompteWave_noReussies_returnsEmpty() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        persistTransaction(cw, StatutTransactionWave.ECHOUE, TypeTransactionWave.PAIEMENT);

        List<TransactionWave> result = transactionWaveRepository.findReussiesByCompteWave(cw.getId());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findReussiesByCompteWave retrouve uniquement les transactions réussies")
    void findReussiesByCompteWave_withReussies_returnsList() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        persistTransaction(cw, StatutTransactionWave.REUSSIE, TypeTransactionWave.PAIEMENT);
        persistTransaction(cw, StatutTransactionWave.ECHOUE, TypeTransactionWave.TRANSFERT);

        List<TransactionWave> result = transactionWaveRepository.findReussiesByCompteWave(cw.getId());

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getStatutTransaction()).isEqualTo(StatutTransactionWave.REUSSIE);
    }

    // ── findEchoueesRetentables ───────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables retourne liste (éventuellement vide)")
    void findEchoueesRetentables_returnsNonNullList() {
        List<TransactionWave> result = transactionWaveRepository.findEchoueesRetentables();
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables inclut les transactions ECHOUE avec moins de 5 tentatives")
    void findEchoueesRetentables_withEchoue_includesTransaction() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId("WT-ECHEC-" + UUID.randomUUID());
        tw.setTypeTransaction(TypeTransactionWave.PAIEMENT);
        tw.setStatutTransaction(StatutTransactionWave.ECHOUE);
        tw.setMontant(BigDecimal.valueOf(300));
        tw.setCodeDevise("XOF");
        tw.setNombreTentatives(2);
        tw.setCompteWave(cw);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);

        List<TransactionWave> result = transactionWaveRepository.findEchoueesRetentables();

        assertThat(result).isNotEmpty();
        assertThat(result).anyMatch(t -> t.getId().equals(tw.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables inclut les transactions EXPIRED avec moins de 5 tentatives")
    void findEchoueesRetentables_withExpired_includesTransaction() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId("WT-EXP-" + UUID.randomUUID());
        tw.setTypeTransaction(TypeTransactionWave.TRANSFERT);
        tw.setStatutTransaction(StatutTransactionWave.EXPIRED);
        tw.setMontant(BigDecimal.valueOf(100));
        tw.setCodeDevise("XOF");
        tw.setNombreTentatives(0);
        tw.setCompteWave(cw);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);

        List<TransactionWave> result = transactionWaveRepository.findEchoueesRetentables();

        assertThat(result).anyMatch(t -> t.getId().equals(tw.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables exclut les transactions ECHOUE avec 5 tentatives ou plus")
    void findEchoueesRetentables_maxTentativesReached_excluded() {
        Membre m = persistMembre();
        CompteWave cw = persistCompteWave(m);
        TransactionWave tw = new TransactionWave();
        tw.setWaveTransactionId("WT-MAX-" + UUID.randomUUID());
        tw.setTypeTransaction(TypeTransactionWave.DEPOT);
        tw.setStatutTransaction(StatutTransactionWave.ECHOUE);
        tw.setMontant(BigDecimal.valueOf(500));
        tw.setCodeDevise("XOF");
        tw.setNombreTentatives(5);
        tw.setCompteWave(cw);
        tw.setActif(true);
        transactionWaveRepository.persist(tw);

        List<TransactionWave> result = transactionWaveRepository.findEchoueesRetentables();

        assertThat(result).noneMatch(t -> t.getId().equals(tw.getId()));
    }
}
