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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
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.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.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
 * où {@code compte.getOrganisation() == null} (orgId null passé au repository LCB-FT).
 */
@QuarkusTest
@DisplayName("TransactionEpargneService.executerTransaction — compte sans organisation")
class TransactionEpargneServiceOrganisationNullTest {

    @Inject
    TransactionEpargneService service;

    @InjectMock
    TransactionEpargneRepository repository;

    @InjectMock
    CompteEpargneRepository compteEpargneRepository;

    @InjectMock
    TransactionEpargneMapper mapper;

    @InjectMock
    ParametresLcbFtRepository parametresLcbFtRepository;

    @InjectMock
    AuditService auditService;

    @InjectMock
    AlerteLcbFtService alerteLcbFtService;

    @BeforeEach
    void setupMocks() {
        // Seuil par défaut via null orgId → branche compte.getOrganisation() == null
        when(parametresLcbFtRepository.getSeuilJustification(isNull(), eq("XOF")))
                .thenReturn(Optional.of(new BigDecimal("500000")));
    }

    private CompteEpargne makeCompteActifSansOrg(UUID compteId, BigDecimal solde) {
        CompteEpargne compte = new CompteEpargne();
        compte.setId(compteId);
        compte.setStatut(StatutCompteEpargne.ACTIF);
        compte.setSoldeActuel(solde);
        compte.setSoldeBloque(BigDecimal.ZERO);
        compte.setOrganisation(null); // CLEF : organisation null → branche L71 false → orgId=null
        return compte;
    }

    // =========================================================================
    // Branche L71 : compte.getOrganisation() == null → orgId = null
    // =========================================================================

    @Test
    @DisplayName("executerTransaction compte sans organisation → getSeuilJustification appelé avec null orgId (branche L71)")
    void executerTransaction_compteSansOrganisation_seuilAvecNullOrgId() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActifSansOrg(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("100")) // Sous le seuil → pas de bloc audit
                .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);

        // Vérifier que getSeuilJustification a bien été appelé avec null (orgId null car pas d'organisation)
        verify(parametresLcbFtRepository).getSeuilJustification(isNull(), eq("XOF"));
    }

    @Test
    @DisplayName("executerTransaction compte sans organisation, montant >= seuil → audit avec orgId null")
    void executerTransaction_compteSansOrganisation_montantSeuil_auditAvecOrgIdNull() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActifSansOrg(compteId, new BigDecimal("2000000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.DEPOT)
                .montant(new BigDecimal("500000")) // Exactement au seuil → bloc audit
                .origineFonds("Epargne personnelle")
                .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);

        // Audit appelé avec orgId=null (car organisation null)
        verify(auditService).logLcbFtSeuilAtteint(
                isNull(), any(), any(), any(), any(), any());
        // AlerteLcbFt appelé avec orgId=null et membreId=null (car membre aussi null)
        verify(alerteLcbFtService).genererAlerteSeuilDepasse(
                isNull(), isNull(), any(), any(), any(), any(), any());
    }

    @Test
    @DisplayName("executerTransaction debit compte sans organisation → solde mis à jour correctement")
    void executerTransaction_debitComptesSansOrganisation_soldeMisAJour() {
        UUID compteId = UUID.randomUUID();
        CompteEpargne compte = makeCompteActifSansOrg(compteId, new BigDecimal("1000"));

        TransactionEpargneRequest request = TransactionEpargneRequest.builder()
                .compteId(compteId.toString())
                .typeTransaction(TypeTransactionEpargne.RETRAIT)
                .montant(new BigDecimal("300"))
                .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);

        // Solde = 1000 - 300 = 700
        assertThat(compte.getSoldeActuel()).isEqualByComparingTo(new BigDecimal("700"));
        verify(parametresLcbFtRepository).getSeuilJustification(isNull(), eq("XOF"));
    }
}
