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

import dev.lions.unionflow.server.api.dto.paiement.request.CreatePaiementRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest;
import dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementSummaryResponse;
import dev.lions.unionflow.server.service.PaiementService;
import jakarta.ws.rs.core.Response;
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.math.BigDecimal;
import java.util.List;
import java.util.UUID;

/**
 * Tests unitaires Mockito pour {@link PaiementResource}.
 *
 * <p>Couvre tous les endpoints :
 * <ul>
 *   <li>trouverParId — 200 OK</li>
 *   <li>trouverParNumeroReference — 200 OK</li>
 *   <li>listerParMembre — 200 OK (liste vide et non vide)</li>
 *   <li>getMonHistoriquePaiements — 200 OK avec limite personnalisée</li>
 *   <li>creerPaiement — 201 Created</li>
 *   <li>validerPaiement — 200 OK</li>
 *   <li>annulerPaiement — 200 OK</li>
 *   <li>initierPaiementEnLigne — 201 Created</li>
 *   <li>getStatutIntention — 200 OK</li>
 *   <li>declarerPaiementManuel — 201 Created</li>
 * </ul>
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Tests PaiementResource")
class PaiementResourceTest {

    @Mock
    PaiementService paiementService;

    @InjectMocks
    PaiementResource resource;

    private static final UUID PAIEMENT_ID   = UUID.fromString("00000000-0000-0000-0000-000000000001");
    private static final UUID MEMBRE_ID     = UUID.fromString("00000000-0000-0000-0000-000000000002");
    private static final UUID INTENTION_ID  = UUID.fromString("00000000-0000-0000-0000-000000000003");
    private static final UUID COTISATION_ID = UUID.fromString("00000000-0000-0000-0000-000000000004");
    private static final String REFERENCE   = "PAY-2026-001";

    // ── GET /api/paiements/{id} ───────────────────────────────────────────────

    @Test
    @DisplayName("trouverParId — paiement existant → 200 OK avec entité")
    void trouverParId_found_returns200() {
        PaiementResponse paiement = buildPaiementResponse(PAIEMENT_ID, "VALIDE");
        when(paiementService.trouverParId(PAIEMENT_ID)).thenReturn(paiement);

        Response result = resource.trouverParId(PAIEMENT_ID);

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

    @Test
    @DisplayName("trouverParId — différents UUIDs → 200 OK avec le bon paiement")
    void trouverParId_differentUuid_returnsCorrectPaiement() {
        UUID otherId = UUID.randomUUID();
        PaiementResponse paiement = buildPaiementResponse(otherId, "EN_ATTENTE");
        when(paiementService.trouverParId(otherId)).thenReturn(paiement);

        Response result = resource.trouverParId(otherId);

        assertThat(result.getStatus()).isEqualTo(200);
        PaiementResponse entity = (PaiementResponse) result.getEntity();
        assertThat(entity.getId()).isEqualTo(otherId);
    }

    // ── GET /api/paiements/reference/{numeroReference} ───────────────────────

    @Test
    @DisplayName("trouverParNumeroReference — référence connue → 200 OK")
    void trouverParNumeroReference_found_returns200() {
        PaiementResponse paiement = buildPaiementResponse(PAIEMENT_ID, "VALIDE");
        paiement.setNumeroReference(REFERENCE);
        when(paiementService.trouverParNumeroReference(REFERENCE)).thenReturn(paiement);

        Response result = resource.trouverParNumeroReference(REFERENCE);

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(((PaiementResponse) result.getEntity()).getNumeroReference()).isEqualTo(REFERENCE);
    }

    @Test
    @DisplayName("trouverParNumeroReference — autre référence → 200 OK")
    void trouverParNumeroReference_otherRef_returns200() {
        String ref = "PAY-WAVE-ABCD1234";
        PaiementResponse paiement = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE");
        when(paiementService.trouverParNumeroReference(ref)).thenReturn(paiement);

        Response result = resource.trouverParNumeroReference(ref);

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

    // ── GET /api/paiements/membre/{membreId} ─────────────────────────────────

    @Test
    @DisplayName("listerParMembre — liste non vide → 200 OK avec tous les éléments")
    void listerParMembre_withResults_returns200() {
        List<PaiementSummaryResponse> summaries = List.of(
                buildSummaryResponse(UUID.randomUUID(), "VALIDE"),
                buildSummaryResponse(UUID.randomUUID(), "EN_ATTENTE")
        );
        when(paiementService.listerParMembre(MEMBRE_ID)).thenReturn(summaries);

        Response result = resource.listerParMembre(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        @SuppressWarnings("unchecked")
        List<PaiementSummaryResponse> entity = (List<PaiementSummaryResponse>) result.getEntity();
        assertThat(entity).hasSize(2);
    }

    @Test
    @DisplayName("listerParMembre — liste vide → 200 OK avec liste vide")
    void listerParMembre_emptyList_returns200() {
        when(paiementService.listerParMembre(MEMBRE_ID)).thenReturn(List.of());

        Response result = resource.listerParMembre(MEMBRE_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        @SuppressWarnings("unchecked")
        List<PaiementSummaryResponse> entity = (List<PaiementSummaryResponse>) result.getEntity();
        assertThat(entity).isEmpty();
    }

    // ── GET /api/paiements/mon-historique ────────────────────────────────────

    @Test
    @DisplayName("getMonHistoriquePaiements — limit par défaut (20) → 200 OK")
    void getMonHistoriquePaiements_defaultLimit_returns200() {
        List<PaiementSummaryResponse> historique = List.of(
                buildSummaryResponse(UUID.randomUUID(), "VALIDE")
        );
        when(paiementService.getMonHistoriquePaiements(20)).thenReturn(historique);

        Response result = resource.getMonHistoriquePaiements(20);

        assertThat(result.getStatus()).isEqualTo(200);
        @SuppressWarnings("unchecked")
        List<PaiementSummaryResponse> entity = (List<PaiementSummaryResponse>) result.getEntity();
        assertThat(entity).hasSize(1);
    }

    @Test
    @DisplayName("getMonHistoriquePaiements — limit=5 → 200 OK avec au plus 5 éléments")
    void getMonHistoriquePaiements_customLimit_returns200() {
        List<PaiementSummaryResponse> historique = List.of(
                buildSummaryResponse(UUID.randomUUID(), "VALIDE"),
                buildSummaryResponse(UUID.randomUUID(), "VALIDE")
        );
        when(paiementService.getMonHistoriquePaiements(5)).thenReturn(historique);

        Response result = resource.getMonHistoriquePaiements(5);

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

    @Test
    @DisplayName("getMonHistoriquePaiements — aucun paiement → 200 OK avec liste vide")
    void getMonHistoriquePaiements_noPaiements_returns200Empty() {
        when(paiementService.getMonHistoriquePaiements(anyInt())).thenReturn(List.of());

        Response result = resource.getMonHistoriquePaiements(20);

        assertThat(result.getStatus()).isEqualTo(200);
        @SuppressWarnings("unchecked")
        List<PaiementSummaryResponse> entity = (List<PaiementSummaryResponse>) result.getEntity();
        assertThat(entity).isEmpty();
    }

    // ── POST /api/paiements (creerPaiement) ──────────────────────────────────

    @Test
    @DisplayName("creerPaiement — requête valide → 201 Created avec paiement")
    void creerPaiement_validRequest_returns201() {
        CreatePaiementRequest request = CreatePaiementRequest.builder()
                .numeroReference(REFERENCE)
                .montant(BigDecimal.valueOf(10000))
                .codeDevise("XOF")
                .methodePaiement("ESPECES")
                .membreId(MEMBRE_ID)
                .build();

        PaiementResponse created = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE");
        when(paiementService.creerPaiement(request)).thenReturn(created);

        Response result = resource.creerPaiement(request);

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

    @Test
    @DisplayName("creerPaiement — sans membreId → 201 Created")
    void creerPaiement_withoutMembreId_returns201() {
        CreatePaiementRequest request = CreatePaiementRequest.builder()
                .numeroReference("PAY-EXTERNE-001")
                .montant(BigDecimal.valueOf(5000))
                .codeDevise("XOF")
                .methodePaiement("VIREMENT")
                .build();

        PaiementResponse created = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE");
        when(paiementService.creerPaiement(any(CreatePaiementRequest.class))).thenReturn(created);

        Response result = resource.creerPaiement(request);

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

    // ── POST /api/paiements/{id}/valider ─────────────────────────────────────

    @Test
    @DisplayName("validerPaiement — paiement existant → 200 OK avec statut VALIDE")
    void validerPaiement_success_returns200() {
        PaiementResponse validated = buildPaiementResponse(PAIEMENT_ID, "VALIDE");
        when(paiementService.validerPaiement(PAIEMENT_ID)).thenReturn(validated);

        Response result = resource.validerPaiement(PAIEMENT_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        PaiementResponse entity = (PaiementResponse) result.getEntity();
        assertThat(entity.getStatutPaiement()).isEqualTo("VALIDE");
    }

    @Test
    @DisplayName("validerPaiement — déjà validé → 200 OK (idempotent)")
    void validerPaiement_alreadyValidated_returns200() {
        PaiementResponse alreadyValidated = buildPaiementResponse(PAIEMENT_ID, "VALIDE");
        when(paiementService.validerPaiement(PAIEMENT_ID)).thenReturn(alreadyValidated);

        Response result = resource.validerPaiement(PAIEMENT_ID);

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

    // ── POST /api/paiements/{id}/annuler ─────────────────────────────────────

    @Test
    @DisplayName("annulerPaiement — paiement annulable → 200 OK avec statut ANNULE")
    void annulerPaiement_success_returns200() {
        PaiementResponse cancelled = buildPaiementResponse(PAIEMENT_ID, "ANNULE");
        when(paiementService.annulerPaiement(PAIEMENT_ID)).thenReturn(cancelled);

        Response result = resource.annulerPaiement(PAIEMENT_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        PaiementResponse entity = (PaiementResponse) result.getEntity();
        assertThat(entity.getStatutPaiement()).isEqualTo("ANNULE");
    }

    @Test
    @DisplayName("annulerPaiement — UUID différent → 200 OK")
    void annulerPaiement_differentUuid_returns200() {
        UUID otherId = UUID.randomUUID();
        PaiementResponse cancelled = buildPaiementResponse(otherId, "ANNULE");
        when(paiementService.annulerPaiement(otherId)).thenReturn(cancelled);

        Response result = resource.annulerPaiement(otherId);

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

    // ── POST /api/paiements/initier-paiement-en-ligne ────────────────────────

    @Test
    @DisplayName("initierPaiementEnLigne — Wave → 201 Created avec waveLaunchUrl")
    void initierPaiementEnLigne_wave_returns201() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                COTISATION_ID, "WAVE", "771234567");

        PaiementGatewayResponse gatewayResponse = PaiementGatewayResponse.builder()
                .transactionId(PAIEMENT_ID)
                .redirectUrl("https://app.wave.com/launch/...")
                .waveLaunchUrl("https://app.wave.com/launch/...")
                .waveCheckoutSessionId("cos-abc123")
                .montant(BigDecimal.valueOf(10000))
                .statut("EN_ATTENTE")
                .methodePaiement("WAVE")
                .build();

        when(paiementService.initierPaiementEnLigne(request)).thenReturn(gatewayResponse);

        Response result = resource.initierPaiementEnLigne(request);

        assertThat(result.getStatus()).isEqualTo(201);
        PaiementGatewayResponse entity = (PaiementGatewayResponse) result.getEntity();
        assertThat(entity.getMethodePaiement()).isEqualTo("WAVE");
        assertThat(entity.getWaveLaunchUrl()).isNotBlank();
    }

    @Test
    @DisplayName("initierPaiementEnLigne — Orange Money → 201 Created")
    void initierPaiementEnLigne_orangeMoney_returns201() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                COTISATION_ID, "ORANGE_MONEY", "771234567");

        PaiementGatewayResponse gatewayResponse = PaiementGatewayResponse.builder()
                .transactionId(PAIEMENT_ID)
                .redirectUrl("https://orange-money.com/pay/" + PAIEMENT_ID)
                .montant(BigDecimal.valueOf(5000))
                .statut("EN_ATTENTE")
                .methodePaiement("ORANGE_MONEY")
                .build();

        when(paiementService.initierPaiementEnLigne(request)).thenReturn(gatewayResponse);

        Response result = resource.initierPaiementEnLigne(request);

        assertThat(result.getStatus()).isEqualTo(201);
        assertThat(((PaiementGatewayResponse) result.getEntity()).getMethodePaiement())
                .isEqualTo("ORANGE_MONEY");
    }

    @Test
    @DisplayName("initierPaiementEnLigne — sans numéro de téléphone → 201 Created")
    void initierPaiementEnLigne_noPhone_returns201() {
        InitierPaiementEnLigneRequest request = new InitierPaiementEnLigneRequest(
                COTISATION_ID, "CARTE_BANCAIRE", null);

        PaiementGatewayResponse gatewayResponse = PaiementGatewayResponse.builder()
                .transactionId(PAIEMENT_ID)
                .redirectUrl("https://payment-gateway.com/pay/" + PAIEMENT_ID)
                .statut("EN_ATTENTE")
                .methodePaiement("CARTE_BANCAIRE")
                .build();

        when(paiementService.initierPaiementEnLigne(request)).thenReturn(gatewayResponse);

        Response result = resource.initierPaiementEnLigne(request);

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

    // ── GET /api/paiements/statut-intention/{intentionId} ────────────────────

    @Test
    @DisplayName("getStatutIntention — intention en cours → 200 OK avec confirme=false")
    void getStatutIntention_enCours_returns200NotConfirmed() {
        IntentionStatutResponse statut = IntentionStatutResponse.builder()
                .intentionId(INTENTION_ID)
                .statut("EN_COURS")
                .confirme(false)
                .message("En attente de confirmation Wave...")
                .build();
        when(paiementService.getStatutIntention(INTENTION_ID)).thenReturn(statut);

        Response result = resource.getStatutIntention(INTENTION_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        IntentionStatutResponse entity = (IntentionStatutResponse) result.getEntity();
        assertThat(entity.getStatut()).isEqualTo("EN_COURS");
        assertThat(entity.isConfirme()).isFalse();
    }

    @Test
    @DisplayName("getStatutIntention — intention complétée → 200 OK avec confirme=true")
    void getStatutIntention_completee_returns200Confirmed() {
        IntentionStatutResponse statut = IntentionStatutResponse.builder()
                .intentionId(INTENTION_ID)
                .statut("COMPLETEE")
                .confirme(true)
                .waveTransactionId("TCN-XYZ-123")
                .message("Paiement confirmé !")
                .build();
        when(paiementService.getStatutIntention(INTENTION_ID)).thenReturn(statut);

        Response result = resource.getStatutIntention(INTENTION_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        IntentionStatutResponse entity = (IntentionStatutResponse) result.getEntity();
        assertThat(entity.isConfirme()).isTrue();
        assertThat(entity.getWaveTransactionId()).isEqualTo("TCN-XYZ-123");
    }

    @Test
    @DisplayName("getStatutIntention — intention expirée → 200 OK avec statut EXPIREE")
    void getStatutIntention_expiree_returns200Expired() {
        IntentionStatutResponse statut = IntentionStatutResponse.builder()
                .intentionId(INTENTION_ID)
                .statut("EXPIREE")
                .confirme(false)
                .message("Session expirée, veuillez recommencer")
                .build();
        when(paiementService.getStatutIntention(INTENTION_ID)).thenReturn(statut);

        Response result = resource.getStatutIntention(INTENTION_ID);

        assertThat(result.getStatus()).isEqualTo(200);
        assertThat(((IntentionStatutResponse) result.getEntity()).getStatut()).isEqualTo("EXPIREE");
    }

    @Test
    @DisplayName("getStatutIntention — intention échouée → 200 OK avec statut ECHOUEE")
    void getStatutIntention_echouee_returns200Failed() {
        IntentionStatutResponse statut = IntentionStatutResponse.builder()
                .intentionId(INTENTION_ID)
                .statut("ECHOUEE")
                .confirme(false)
                .message("Paiement échoué")
                .build();
        when(paiementService.getStatutIntention(INTENTION_ID)).thenReturn(statut);

        Response result = resource.getStatutIntention(INTENTION_ID);

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

    // ── POST /api/paiements/declarer-manuel ──────────────────────────────────

    @Test
    @DisplayName("declarerPaiementManuel — espèces → 201 Created avec statut EN_ATTENTE_VALIDATION")
    void declarerPaiementManuel_cash_returns201() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                COTISATION_ID, "ESPECES", null, "Remis en main propre");

        PaiementResponse created = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE_VALIDATION");
        when(paiementService.declarerPaiementManuel(request)).thenReturn(created);

        Response result = resource.declarerPaiementManuel(request);

        assertThat(result.getStatus()).isEqualTo(201);
        assertThat(((PaiementResponse) result.getEntity()).getStatutPaiement())
                .isEqualTo("EN_ATTENTE_VALIDATION");
    }

    @Test
    @DisplayName("declarerPaiementManuel — virement avec référence → 201 Created")
    void declarerPaiementManuel_virement_returns201() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                COTISATION_ID, "VIREMENT", "VIR-2026-987654", "Virement mensuel");

        PaiementResponse created = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE_VALIDATION");
        when(paiementService.declarerPaiementManuel(any(DeclarerPaiementManuelRequest.class)))
                .thenReturn(created);

        Response result = resource.declarerPaiementManuel(request);

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

    @Test
    @DisplayName("declarerPaiementManuel — chèque → 201 Created")
    void declarerPaiementManuel_cheque_returns201() {
        DeclarerPaiementManuelRequest request = new DeclarerPaiementManuelRequest(
                COTISATION_ID, "CHEQUE", "CHQ-00123456", null);

        PaiementResponse created = buildPaiementResponse(PAIEMENT_ID, "EN_ATTENTE_VALIDATION");
        when(paiementService.declarerPaiementManuel(any())).thenReturn(created);

        Response result = resource.declarerPaiementManuel(request);

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

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

    private PaiementResponse buildPaiementResponse(UUID id, String statut) {
        PaiementResponse response = new PaiementResponse();
        response.setId(id);
        response.setNumeroReference(REFERENCE);
        response.setMontant(BigDecimal.valueOf(10000));
        response.setCodeDevise("XOF");
        response.setMethodePaiement("ESPECES");
        response.setStatutPaiement(statut);
        return response;
    }

    private PaiementSummaryResponse buildSummaryResponse(UUID id, String statut) {
        return new PaiementSummaryResponse(
                id,
                REFERENCE,
                BigDecimal.valueOf(10000),
                "XOF",
                "Espèces",
                statut,
                statut,
                "success",
                null
        );
    }
}
