package dev.lions.unionflow.server.service;

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.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.config.request.ParametresLcbFtRequest;
import dev.lions.unionflow.server.api.dto.config.response.ParametresLcbFtResponse;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.ParametresLcbFt;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.ParametresLcbFtRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.math.BigDecimal;
import java.util.Optional;
import java.util.UUID;

/**
 * Tests unitaires pour ParametresLcbFtService.
 *
 * <p>Couvre getParametres, getSeuilJustification et saveOrUpdateParametres
 * ainsi que les branches null/blank pour le code devise.
 *
 * @author UnionFlow Team
 */
@QuarkusTest
@DisplayName("ParametresLcbFtService")
class ParametresLcbFtServiceTest {

    @Inject
    ParametresLcbFtService parametresLcbFtService;

    @InjectMock
    ParametresLcbFtRepository parametresRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    private UUID organisationId;
    private UUID paramsId;
    private Organisation organisation;
    private ParametresLcbFt parametresLcbFt;

    @BeforeEach
    void setup() {
        organisationId = UUID.randomUUID();
        paramsId = UUID.randomUUID();

        organisation = new Organisation();
        organisation.setId(organisationId);
        organisation.setNom("Org Test LCB-FT");
        organisation.setTypeOrganisation("ASSOCIATION");
        organisation.setEmail("org@test.com");

        parametresLcbFt = new ParametresLcbFt();
        parametresLcbFt.setId(paramsId);
        parametresLcbFt.setOrganisation(organisation);
        parametresLcbFt.setCodeDevise("XOF");
        parametresLcbFt.setMontantSeuilJustification(new BigDecimal("500000"));
        parametresLcbFt.setMontantSeuilValidationManuelle(new BigDecimal("1000000"));
    }

    // =========================================================================
    // getParametres — happy path
    // =========================================================================

    @Test
    @DisplayName("getParametres - retourne le DTO quand les parametres existent")
    void getParametres_retourneDto_quandParametresExistent() {
        when(parametresRepository.findByOrganisationAndDevise(organisationId, "XOF"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.getParametres(organisationId, "XOF");

        assertThat(response).isNotNull();
        assertThat(response.getCodeDevise()).isEqualTo("XOF");
        assertThat(response.getMontantSeuilJustification()).isEqualByComparingTo(new BigDecimal("500000"));
        assertThat(response.getMontantSeuilValidationManuelle()).isEqualByComparingTo(new BigDecimal("1000000"));
        assertThat(response.getOrganisationId()).isEqualTo(organisationId.toString());
        assertThat(response.getOrganisationNom()).isEqualTo("Org Test LCB-FT");
        assertThat(response.getEstParametrePlateforme()).isFalse();
    }

    @Test
    @DisplayName("getParametres - parametres plateforme (organisation null) → estParametrePlateforme=true")
    void getParametres_parametresPlateforme_estParametrePlateformeTrue() {
        parametresLcbFt.setOrganisation(null);
        when(parametresRepository.findByOrganisationAndDevise(null, "XOF"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.getParametres(null, "XOF");

        assertThat(response.getEstParametrePlateforme()).isTrue();
        assertThat(response.getOrganisationId()).isNull();
        assertThat(response.getOrganisationNom()).isNull();
    }

    @Test
    @DisplayName("getParametres - codeDevise null → utilise XOF par defaut")
    void getParametres_codeDeviseNull_utiliseXof() {
        when(parametresRepository.findByOrganisationAndDevise(organisationId, "XOF"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.getParametres(organisationId, null);

        assertThat(response).isNotNull();
        verify(parametresRepository).findByOrganisationAndDevise(organisationId, "XOF");
    }

    @Test
    @DisplayName("getParametres - codeDevise blank → utilise XOF par defaut")
    void getParametres_codeDeviseBlank_utiliseXof() {
        when(parametresRepository.findByOrganisationAndDevise(organisationId, "XOF"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.getParametres(organisationId, "   ");

        assertThat(response).isNotNull();
        verify(parametresRepository).findByOrganisationAndDevise(organisationId, "XOF");
    }

    @Test
    @DisplayName("getParametres - parametres non trouves → NotFoundException")
    void getParametres_parametresNonTrouves_throwsNotFoundException() {
        when(parametresRepository.findByOrganisationAndDevise(any(), any()))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> parametresLcbFtService.getParametres(organisationId, "EUR"))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Paramètres LCB-FT non configurés");
    }

    // =========================================================================
    // getSeuilJustification
    // =========================================================================

    @Test
    @DisplayName("getSeuilJustification - retourne le seuil quand il existe")
    void getSeuilJustification_retourneSeuil_quandExiste() {
        when(parametresRepository.getSeuilJustification(organisationId, "XOF"))
                .thenReturn(Optional.of(new BigDecimal("500000")));

        BigDecimal seuil = parametresLcbFtService.getSeuilJustification(organisationId, "XOF");

        assertThat(seuil).isEqualByComparingTo(new BigDecimal("500000"));
    }

    @Test
    @DisplayName("getSeuilJustification - pas de parametres → fallback 500000")
    void getSeuilJustification_pasDeParametres_fallback500000() {
        when(parametresRepository.getSeuilJustification(organisationId, "XOF"))
                .thenReturn(Optional.empty());

        BigDecimal seuil = parametresLcbFtService.getSeuilJustification(organisationId, "XOF");

        assertThat(seuil).isEqualByComparingTo(new BigDecimal("500000"));
    }

    @Test
    @DisplayName("getSeuilJustification - codeDevise null → utilise XOF par defaut")
    void getSeuilJustification_codeDeviseNull_utiliseXof() {
        when(parametresRepository.getSeuilJustification(organisationId, "XOF"))
                .thenReturn(Optional.of(new BigDecimal("500000")));

        BigDecimal seuil = parametresLcbFtService.getSeuilJustification(organisationId, null);

        assertThat(seuil).isNotNull();
        verify(parametresRepository).getSeuilJustification(organisationId, "XOF");
    }

    @Test
    @DisplayName("getSeuilJustification - codeDevise blank → utilise XOF par defaut")
    void getSeuilJustification_codeDeviseBlank_utiliseXof() {
        when(parametresRepository.getSeuilJustification(organisationId, "XOF"))
                .thenReturn(Optional.of(new BigDecimal("750000")));

        BigDecimal seuil = parametresLcbFtService.getSeuilJustification(organisationId, "");

        assertThat(seuil).isEqualByComparingTo(new BigDecimal("750000"));
    }

    // =========================================================================
    // saveOrUpdateParametres — creation
    // =========================================================================

    @Test
    @DisplayName("saveOrUpdateParametres - creation quand aucun parametre n'existe")
    void saveOrUpdateParametres_creation_quandAucunParametreExiste() {
        ParametresLcbFtRequest request = ParametresLcbFtRequest.builder()
                .organisationId(organisationId.toString())
                .codeDevise("XOF")
                .montantSeuilJustification(new BigDecimal("500000"))
                .montantSeuilValidationManuelle(new BigDecimal("1000000"))
                .build();

        when(organisationRepository.findByIdOptional(organisationId))
                .thenReturn(Optional.of(organisation));
        when(parametresRepository.findByOrganisationAndDevise(eq(organisationId), eq("XOF")))
                .thenReturn(Optional.empty());

        ParametresLcbFtResponse response = parametresLcbFtService.saveOrUpdateParametres(request);

        assertThat(response).isNotNull();
        assertThat(response.getMontantSeuilJustification()).isEqualByComparingTo(new BigDecimal("500000"));
        verify(parametresRepository).persist(any(ParametresLcbFt.class));
    }

    @Test
    @DisplayName("saveOrUpdateParametres - mise a jour quand les parametres existent deja")
    void saveOrUpdateParametres_miseAJour_quandParametresExistent() {
        ParametresLcbFtRequest request = ParametresLcbFtRequest.builder()
                .organisationId(organisationId.toString())
                .codeDevise("XOF")
                .montantSeuilJustification(new BigDecimal("600000"))
                .montantSeuilValidationManuelle(new BigDecimal("1200000"))
                .build();

        when(organisationRepository.findByIdOptional(organisationId))
                .thenReturn(Optional.of(organisation));
        when(parametresRepository.findByOrganisationAndDevise(eq(organisationId), eq("XOF")))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.saveOrUpdateParametres(request);

        assertThat(response).isNotNull();
        assertThat(response.getMontantSeuilJustification()).isEqualByComparingTo(new BigDecimal("600000"));
        assertThat(response.getMontantSeuilValidationManuelle()).isEqualByComparingTo(new BigDecimal("1200000"));
    }

    @Test
    @DisplayName("saveOrUpdateParametres - organisationId null → parametres plateforme")
    void saveOrUpdateParametres_organisationIdNull_parametresPlateforme() {
        ParametresLcbFtRequest request = ParametresLcbFtRequest.builder()
                .organisationId(null)
                .codeDevise("EUR")
                .montantSeuilJustification(new BigDecimal("1000"))
                .montantSeuilValidationManuelle(new BigDecimal("5000"))
                .build();

        parametresLcbFt.setOrganisation(null);
        when(parametresRepository.findByOrganisationAndDevise(null, "EUR"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.saveOrUpdateParametres(request);

        assertThat(response).isNotNull();
        assertThat(response.getEstParametrePlateforme()).isTrue();
    }

    @Test
    @DisplayName("saveOrUpdateParametres - codeDevise null dans request → utilise XOF")
    void saveOrUpdateParametres_codeDeviseNullDansRequest_utiliseXof() {
        ParametresLcbFtRequest request = ParametresLcbFtRequest.builder()
                .organisationId(organisationId.toString())
                .codeDevise(null)
                .montantSeuilJustification(new BigDecimal("500000"))
                .montantSeuilValidationManuelle(new BigDecimal("1000000"))
                .build();

        when(organisationRepository.findByIdOptional(organisationId))
                .thenReturn(Optional.of(organisation));
        when(parametresRepository.findByOrganisationAndDevise(organisationId, "XOF"))
                .thenReturn(Optional.of(parametresLcbFt));

        ParametresLcbFtResponse response = parametresLcbFtService.saveOrUpdateParametres(request);

        assertThat(response).isNotNull();
        verify(parametresRepository).findByOrganisationAndDevise(organisationId, "XOF");
    }

    @Test
    @DisplayName("saveOrUpdateParametres - organisation non trouvee → NotFoundException")
    void saveOrUpdateParametres_organisationNonTrouvee_throwsNotFoundException() {
        ParametresLcbFtRequest request = ParametresLcbFtRequest.builder()
                .organisationId(organisationId.toString())
                .codeDevise("XOF")
                .montantSeuilJustification(new BigDecimal("500000"))
                .montantSeuilValidationManuelle(new BigDecimal("1000000"))
                .build();

        when(organisationRepository.findByIdOptional(organisationId))
                .thenReturn(Optional.empty());

        assertThatThrownBy(() -> parametresLcbFtService.saveOrUpdateParametres(request))
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Organisation non trouvée");
    }
}
