package dev.lions.unionflow.server.resource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.kyc.KycDossierRequest;
import dev.lions.unionflow.server.api.dto.kyc.KycDossierResponse;
import dev.lions.unionflow.server.api.enums.membre.StatutKyc;
import dev.lions.unionflow.server.api.enums.membre.TypePieceIdentite;
import dev.lions.unionflow.server.service.KycAmlService;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.lang.reflect.Field;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * Tests unitaires Mockito pour {@link KycResource}.
 *
 * <p>Couvre tous les chemins d'exécution :
 * <ul>
 *   <li>soumettre — succès → 201 Created</li>
 *   <li>getDossierActif — trouvé → 200 OK, non trouvé → 404</li>
 *   <li>evaluerRisque — succès → 200 OK</li>
 *   <li>valider — succès → 200 OK</li>
 *   <li>refuser — succès → 200 OK</li>
 *   <li>getDossiersEnAttente — liste → 200</li>
 *   <li>getPep — liste → 200</li>
 *   <li>getPiecesExpirant — liste → 200</li>
 * </ul>
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Tests KycResource")
class KycResourceTest {

    @Mock
    KycAmlService kycAmlService;

    @Mock
    SecurityIdentity identity;

    @Mock
    Principal principal;

    @InjectMocks
    KycResource resource;

    private static final UUID MEMBRE_ID    = UUID.fromString("00000000-0000-0000-0000-000000000001");
    private static final UUID DOSSIER_ID   = UUID.fromString("00000000-0000-0000-0000-000000000002");
    private static final UUID VALIDATEUR_ID = UUID.fromString("00000000-0000-0000-0000-000000000003");
    private static final String OPERATEUR  = "admin@unionflow.com";

    @BeforeEach
    void configurePrincipal() {
        // Stub du principal utilisé par soumettre(), valider(), refuser().
        // @InjectMocks injecte le mock SecurityIdentity par type dans KycResource.
        // Si l'injection automatique échoue (ex : proxy CDI), on la force via réflexion.
        try {
            Field identityField = KycResource.class.getDeclaredField("identity");
            identityField.setAccessible(true);
            if (identityField.get(resource) == null) {
                identityField.set(resource, identity);
            }
        } catch (Exception ignored) {
            // @InjectMocks a déjà injecté le mock — rien à faire
        }
        when(identity.getPrincipal()).thenReturn(principal);
        when(principal.getName()).thenReturn(OPERATEUR);
    }

    // ── POST /api/kyc/dossiers — soumettre ────────────────────────────────────

    @Test
    @DisplayName("soumettre — dossier valide → 201 Created avec KycDossierResponse")
    void soumettre_validRequest_returns201() {
        KycDossierRequest request = buildRequest();
        KycDossierResponse response = buildResponse(DOSSIER_ID, StatutKyc.EN_COURS);
        when(kycAmlService.soumettreOuMettreAJour(eq(request), eq(OPERATEUR))).thenReturn(response);

        Response result = resource.soumettre(request);

        assertThat(result.getStatus()).isEqualTo(201);
        assertThat(result.getEntity()).isSameAs(response);
    }

    @Test
    @DisplayName("soumettre — second dossier pour même membre → 201 Created (idempotent)")
    void soumettre_secondDossier_returns201() {
        KycDossierRequest request = buildRequest();
        KycDossierResponse updatedResponse = buildResponse(DOSSIER_ID, StatutKyc.EN_COURS);
        when(kycAmlService.soumettreOuMettreAJour(any(), any())).thenReturn(updatedResponse);

        Response result = resource.soumettre(request);

        assertThat(result.getStatus()).isEqualTo(201);
        assertThat(result.getEntity()).isNotNull();
    }

    // ── GET /api/kyc/membres/{membreId} — getDossierActif ────────────────────

    @Test
    @DisplayName("getDossierActif — dossier trouvé → 200 OK avec dossier")
    void getDossierActif_found_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.EN_COURS);
        when(kycAmlService.getDossierActif(MEMBRE_ID)).thenReturn(Optional.of(dossier));

        Response result = resource.getDossierActif(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(result.getEntity()).isSameAs(dossier);
    }

    @Test
    @DisplayName("getDossierActif — aucun dossier actif → 404 avec message d'erreur")
    void getDossierActif_notFound_returns404() {
        when(kycAmlService.getDossierActif(MEMBRE_ID)).thenReturn(Optional.empty());

        Response result = resource.getDossierActif(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(404);
        assertThat(result.getEntity()).isNotNull();
        // Le body est une Map{"error": "..."}
        @SuppressWarnings("unchecked")
        java.util.Map<String, Object> body = (java.util.Map<String, Object>) result.getEntity();
        assertThat(body).containsKey("error");
    }

    @Test
    @DisplayName("getDossierActif — dossier vérifié → 200 OK")
    void getDossierActif_verified_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.VERIFIE);
        when(kycAmlService.getDossierActif(MEMBRE_ID)).thenReturn(Optional.of(dossier));

        Response result = resource.getDossierActif(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(((KycDossierResponse) result.getEntity()).getStatut()).isEqualTo(StatutKyc.VERIFIE);
    }

    // ── POST /api/kyc/membres/{membreId}/evaluer-risque ───────────────────────

    @Test
    @DisplayName("evaluerRisque — membre existant → 200 OK avec score calculé")
    void evaluerRisque_success_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.EN_COURS);
        dossier.setScoreRisque(25);
        when(kycAmlService.evaluerRisque(MEMBRE_ID)).thenReturn(dossier);

        Response result = resource.evaluerRisque(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(((KycDossierResponse) result.getEntity()).getScoreRisque()).isEqualTo(25);
    }

    @Test
    @DisplayName("evaluerRisque — PEP → 200 OK avec score élevé")
    void evaluerRisque_pepMember_returns200WithHighScore() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.EN_COURS);
        dossier.setScoreRisque(85);
        dossier.setEstPep(true);
        when(kycAmlService.evaluerRisque(MEMBRE_ID)).thenReturn(dossier);

        Response result = resource.evaluerRisque(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        KycDossierResponse entity = (KycDossierResponse) result.getEntity();
        assertThat(entity.getScoreRisque()).isEqualTo(85);
        assertThat(entity.isEstPep()).isTrue();
    }

    // ── POST /api/kyc/dossiers/{dossierId}/valider ───────────────────────────

    @Test
    @DisplayName("valider — dossier existant → 200 OK avec statut VERIFIE")
    void valider_success_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.VERIFIE);
        when(kycAmlService.valider(eq(DOSSIER_ID), eq(VALIDATEUR_ID), eq("RAS"), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.valider(DOSSIER_ID, VALIDATEUR_ID, "RAS");

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(result.getEntity()).isSameAs(dossier);
    }

    @Test
    @DisplayName("valider — notes null → 200 OK (notes optionnelles)")
    void valider_nullNotes_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.VERIFIE);
        when(kycAmlService.valider(eq(DOSSIER_ID), eq(VALIDATEUR_ID), eq(null), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.valider(DOSSIER_ID, VALIDATEUR_ID, null);

        assertThat(result.getStatus()).isEqualTo(200);
    }

    @Test
    @DisplayName("valider — validateurId null → 200 OK")
    void valider_nullValidateurId_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.VERIFIE);
        when(kycAmlService.valider(eq(DOSSIER_ID), eq(null), any(), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.valider(DOSSIER_ID, null, "Validé");

        assertThat(result.getStatus()).isEqualTo(200);
    }

    // ── POST /api/kyc/dossiers/{dossierId}/refuser ───────────────────────────

    @Test
    @DisplayName("refuser — dossier existant → 200 OK avec statut REFUSE")
    void refuser_success_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.REFUSE);
        when(kycAmlService.refuser(eq(DOSSIER_ID), eq(VALIDATEUR_ID), eq("Documents illisibles"), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.refuser(DOSSIER_ID, VALIDATEUR_ID, "Documents illisibles");

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(result.getEntity()).isSameAs(dossier);
    }

    @Test
    @DisplayName("refuser — motif null → 200 OK")
    void refuser_nullMotif_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.REFUSE);
        when(kycAmlService.refuser(eq(DOSSIER_ID), any(), eq(null), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.refuser(DOSSIER_ID, VALIDATEUR_ID, null);

        assertThat(result.getStatus()).isEqualTo(200);
    }

    @Test
    @DisplayName("refuser — validateurId null → 200 OK")
    void refuser_nullValidateurId_returns200() {
        KycDossierResponse dossier = buildResponse(DOSSIER_ID, StatutKyc.REFUSE);
        when(kycAmlService.refuser(eq(DOSSIER_ID), eq(null), any(), eq(OPERATEUR)))
                .thenReturn(dossier);

        Response result = resource.refuser(DOSSIER_ID, null, "Fraude suspectée");

        assertThat(result.getStatus()).isEqualTo(200);
    }

    // ── GET /api/kyc/dossiers/en-attente ─────────────────────────────────────

    @Test
    @DisplayName("getDossiersEnAttente — liste non vide → retourne tous les dossiers")
    void getDossiersEnAttente_multipleResults_returnsAll() {
        List<KycDossierResponse> dossiers = List.of(
                buildResponse(UUID.randomUUID(), StatutKyc.EN_COURS),
                buildResponse(UUID.randomUUID(), StatutKyc.EN_COURS)
        );
        when(kycAmlService.getDossiersEnAttente()).thenReturn(dossiers);

        List<KycDossierResponse> result = resource.getDossiersEnAttente();

        assertThat(result).hasSize(2);
        assertThat(result).allMatch(d -> d.getStatut() == StatutKyc.EN_COURS);
    }

    @Test
    @DisplayName("getDossiersEnAttente — liste vide → retourne liste vide")
    void getDossiersEnAttente_emptyList_returnsEmpty() {
        when(kycAmlService.getDossiersEnAttente()).thenReturn(List.of());

        List<KycDossierResponse> result = resource.getDossiersEnAttente();

        assertThat(result).isEmpty();
    }

    // ── GET /api/kyc/pep ─────────────────────────────────────────────────────

    @Test
    @DisplayName("getPep — membres PEP présents → retourne liste")
    void getPep_withPepMembers_returnsList() {
        KycDossierResponse pep = buildResponse(DOSSIER_ID, StatutKyc.VERIFIE);
        pep.setEstPep(true);
        when(kycAmlService.getDossiersPep()).thenReturn(List.of(pep));

        List<KycDossierResponse> result = resource.getPep();

        assertThat(result).hasSize(1);
        assertThat(result.get(0).isEstPep()).isTrue();
    }

    @Test
    @DisplayName("getPep — aucun PEP → liste vide")
    void getPep_noPepMembers_returnsEmpty() {
        when(kycAmlService.getDossiersPep()).thenReturn(List.of());

        List<KycDossierResponse> result = resource.getPep();

        assertThat(result).isEmpty();
    }

    // ── GET /api/kyc/pieces-expirant-bientot ─────────────────────────────────

    @Test
    @DisplayName("getPiecesExpirant — pièces expirant → retourne liste")
    void getPiecesExpirant_expiringSoon_returnsList() {
        List<KycDossierResponse> expiring = List.of(
                buildResponse(UUID.randomUUID(), StatutKyc.VERIFIE),
                buildResponse(UUID.randomUUID(), StatutKyc.EN_COURS)
        );
        when(kycAmlService.getPiecesExpirantDansLes30Jours()).thenReturn(expiring);

        List<KycDossierResponse> result = resource.getPiecesExpirant();

        assertThat(result).hasSize(2);
    }

    @Test
    @DisplayName("getPiecesExpirant — aucune pièce expirant → liste vide")
    void getPiecesExpirant_none_returnsEmpty() {
        when(kycAmlService.getPiecesExpirantDansLes30Jours()).thenReturn(List.of());

        List<KycDossierResponse> result = resource.getPiecesExpirant();

        assertThat(result).isEmpty();
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    private KycDossierRequest buildRequest() {
        return KycDossierRequest.builder()
                .membreId(MEMBRE_ID.toString())
                .typePiece(TypePieceIdentite.CNI)
                .numeroPiece("CI-2025-123456")
                .nationalite("SN")
                .estPep(false)
                .build();
    }

    private KycDossierResponse buildResponse(UUID id, StatutKyc statut) {
        KycDossierResponse dto = new KycDossierResponse();
        dto.setId(id);
        dto.setMembreId(MEMBRE_ID);
        dto.setStatut(statut);
        dto.setScoreRisque(0);
        return dto;
    }
}
