package dev.lions.unionflow.server.service.mutuelle.epargne;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
import dev.lions.unionflow.server.mapper.mutuelle.epargne.TransactionEpargneMapper;
import dev.lions.unionflow.server.repository.ParametresLcbFtRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.TransactionEpargneRepository;
import dev.lions.unionflow.server.service.AuditService;
import dev.lions.unionflow.server.service.AlerteLcbFtService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests complémentaires pour {@link TransactionEpargneService#executerTransaction} —
 * cas limites de type transaction, pièce justificative et date de transaction.
 */
@QuarkusTest
class TransactionEpargneServiceCoverageTest {

    @Inject
    TransactionEpargneService service;

    @InjectMock
    TransactionEpargneRepository repository;

    @InjectMock
    CompteEpargneRepository compteEpargneRepository;

    @InjectMock
    TransactionEpargneMapper mapper;

    @InjectMock
    ParametresLcbFtRepository parametresLcbFtRepository;

    @InjectMock
    AuditService auditService;

    @InjectMock
    AlerteLcbFtService alerteLcbFtService;

    @BeforeEach
    void setupMocks() {
        when(parametresLcbFtRepository.getSeuilJustification(any(), any()))
                .thenReturn(Optional.of(new BigDecimal("500000")));
    }

    private CompteEpargne makeCompteActif(UUID compteId, BigDecimal solde) {
        CompteEpargne compte = new CompteEpargne();
        compte.setId(compteId);
        compte.setStatut(StatutCompteEpargne.ACTIF);
        compte.setSoldeActuel(solde);
        compte.setSoldeBloque(BigDecimal.ZERO);
        return compte;
    }

    @Test
    @DisplayName("executerTransaction typeTransaction=null → IllegalArgumentException")
    void executerTransaction_typeTransactionNull_throwsElseBlock() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(null)
                .montant(new BigDecimal("100"))
                .build();

        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));

        assertThatThrownBy(() -> service.executerTransaction(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("non pris en charge");
    }

    @Test
    @DisplayName("executerTransaction pieceJustificativeId blank → UUID non parsé")
    void executerTransaction_pieceJustificativeIdBlank_nonParsee() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("100"))
                .pieceJustificativeId("   ")
                .build();

        TransactionEpargne entity = new TransactionEpargne();
        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(null);

        service.executerTransaction(request);

        // pieceJustificativeId ne doit PAS être affecté (reste null)
        assertThat(entity.getPieceJustificativeId()).isNull();
        verify(repository).persist(entity);
    }

    @Test
    @DisplayName("executerTransaction dateTransaction null → setDateTransaction now")
    void executerTransaction_dateTransactionNull_setDateTransactionNow() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("100"))
                .build();

        TransactionEpargne entity = new TransactionEpargne();
        entity.setDateTransaction(null);

        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(null);

        LocalDateTime before = LocalDateTime.now().minusSeconds(1);
        service.executerTransaction(request);

        assertThat(entity.getDateTransaction()).isNotNull();
        assertThat(entity.getDateTransaction()).isAfterOrEqualTo(before);
    }

    @Test
    @DisplayName("executerTransaction montant < seuil → audit non appelé")
    void executerTransaction_montantInferieurSeuil_auditNonAppele() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("100"))
                .build();

        TransactionEpargne entity = new TransactionEpargne();
        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(null);

        service.executerTransaction(request);

        verify(auditService, never()).logLcbFtSeuilAtteint(any(), any(), any(), any(), any(), any());
        verify(alerteLcbFtService, never()).genererAlerteSeuilDepasse(
                any(), any(), any(), any(), any(), any(), any());
    }

    @Test
    @DisplayName("executerTransaction compte avec org et membre → audit appelé avec orgId et membreId non-null")
    void executerTransaction_avecOrgEtMembre_auditAvecOrgIdEtMembreId() {
        UUID compteId = UUID.randomUUID();
        UUID orgId = UUID.randomUUID();
        UUID membreId = UUID.randomUUID();

        Organisation org = new Organisation();
        org.setId(orgId);

        Membre membre = new Membre();
        membre.setId(membreId);

        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("2000000"));
        compte.setOrganisation(org);
        compte.setMembre(membre);

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("500000"))
                .origineFonds("Salaire")
                .build();

        TransactionEpargne entity = new TransactionEpargne();

        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(null);
        when(parametresLcbFtRepository.getSeuilJustification(orgId, "XOF"))
                .thenReturn(Optional.of(new BigDecimal("500000")));

        service.executerTransaction(request);

        verify(auditService).logLcbFtSeuilAtteint(
                eq(orgId), any(), any(), any(), any(), any());

        verify(alerteLcbFtService).genererAlerteSeuilDepasse(
                eq(orgId), eq(membreId), any(), any(), any(), any(), any());
    }

    @Test
    @DisplayName("executerTransaction — transaction.getId() non-null après persist → ID propagé à l'audit")
    void executerTransaction_transactionIdNonNullApresPersist_couvrL135L147() {
        UUID compteId = UUID.randomUUID();
        UUID transactionId = UUID.randomUUID();

        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("2000000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("500000"))
                .origineFonds("Héritage")
                .build();

        TransactionEpargne entity = new TransactionEpargne();

        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(null);

        doAnswer(invocation -> {
            TransactionEpargne tx = invocation.getArgument(0);
            tx.setId(transactionId);
            return null;
        }).when(repository).persist(any(TransactionEpargne.class));

        service.executerTransaction(request);

        verify(auditService).logLcbFtSeuilAtteint(
                any(), any(), any(), eq(transactionId.toString()), any(), any());

        verify(alerteLcbFtService).genererAlerteSeuilDepasse(
                any(), any(), any(), any(), any(), eq(transactionId.toString()), any());
    }

    @Test
    @DisplayName("executerTransaction typeTransaction=null → exception avant la vérification du seuil")
    void executerTransaction_typeTransactionNull_lancesAvantL144() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActif(compteId, new BigDecimal("2000000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(null)
                .montant(new BigDecimal("500000"))
                .origineFonds("Test")
                .build();

        when(compteEpargneRepository.findByIdOptional(compteId)).thenReturn(Optional.of(compte));

        assertThatThrownBy(() -> service.executerTransaction(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("non pris en charge");
    }
}
