package dev.lions.unionflow.server.payment.orchestration;

import dev.lions.unionflow.server.api.payment.CheckoutRequest;
import dev.lions.unionflow.server.api.payment.CheckoutSession;
import dev.lions.unionflow.server.api.payment.PaymentException;
import dev.lions.unionflow.server.api.payment.PaymentProvider;
import dev.lions.unionflow.server.service.PaiementService;
import org.junit.jupiter.api.BeforeEach;
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.math.BigDecimal;
import java.time.Instant;
import java.util.Map;

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

@ExtendWith(MockitoExtension.class)
class PaymentOrchestratorTest {

    @InjectMocks
    PaymentOrchestrator orchestrator;

    @Mock
    PaymentProviderRegistry registry;

    @Mock
    PaiementService paiementService;

    private CheckoutRequest sampleRequest;

    @BeforeEach
    void setUp() throws Exception {
        Field defaultProviderField = PaymentOrchestrator.class.getDeclaredField("defaultProvider");
        defaultProviderField.setAccessible(true);
        defaultProviderField.set(orchestrator, "WAVE");

        Field pispiField = PaymentOrchestrator.class.getDeclaredField("pispiPriority");
        pispiField.setAccessible(true);
        pispiField.set(orchestrator, false);

        sampleRequest = new CheckoutRequest(
                BigDecimal.valueOf(5000), "XOF",
                "+2210100000000", "test@test.com",
                "REF-TEST-001", "http://success", "http://cancel", Map.of());
    }

    @Test
    void initierPaiement_usesRequestedProvider() throws Exception {
        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        CheckoutSession expectedSession = new CheckoutSession("EXT-001", "https://wave.pay/001", Instant.now().plusSeconds(3600), Map.of());
        when(waveProvider.initiateCheckout(any())).thenReturn(expectedSession);

        when(registry.get("WAVE")).thenReturn(waveProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, "WAVE");

        assertThat(session.externalId()).isEqualTo("EXT-001");
    }

    @Test
    void initierPaiement_fallsBackToDefault_whenRequestedUnavailable() throws Exception {
        PaymentProvider unavailableProvider = mock(PaymentProvider.class);
        when(unavailableProvider.isAvailable()).thenReturn(false);

        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        CheckoutSession expectedSession = new CheckoutSession("WAVE-EXT-001", "https://wave.pay/001", Instant.now().plusSeconds(3600), Map.of());
        when(waveProvider.initiateCheckout(any())).thenReturn(expectedSession);

        when(registry.get("MOMO")).thenReturn(unavailableProvider);
        when(registry.get("WAVE")).thenReturn(waveProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, "MOMO");

        assertThat(session.externalId()).isEqualTo("WAVE-EXT-001");
    }

    @Test
    void initierPaiement_throwsWhenNoProviderAvailable() throws Exception {
        when(registry.get(any())).thenThrow(new UnsupportedOperationException("Provider non supporté"));

        assertThatThrownBy(() -> orchestrator.initierPaiement(sampleRequest, "UNKNOWN"))
                .isInstanceOf(PaymentException.class);
    }

    @Test
    void initierPaiement_withPispiPriority_triesPispiFirst() throws Exception {
        Field pispiField = PaymentOrchestrator.class.getDeclaredField("pispiPriority");
        pispiField.setAccessible(true);
        pispiField.set(orchestrator, true);

        PaymentProvider pispiProvider = mock(PaymentProvider.class);
        when(pispiProvider.isAvailable()).thenReturn(true);
        CheckoutSession expectedSession = new CheckoutSession("PISPI-EXT-001", "https://pispi.bceao/001", Instant.now().plusSeconds(3600), Map.of());
        when(pispiProvider.initiateCheckout(any())).thenReturn(expectedSession);

        when(registry.get("PISPI")).thenReturn(pispiProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, "WAVE");

        assertThat(session.externalId()).isEqualTo("PISPI-EXT-001");
    }

    @Test
    void initierPaiement_withPispiPriority_noRequestedProvider_triesPispiThenDefault() throws Exception {
        Field pispiField = PaymentOrchestrator.class.getDeclaredField("pispiPriority");
        pispiField.setAccessible(true);
        pispiField.set(orchestrator, true);

        PaymentProvider pispiProvider = mock(PaymentProvider.class);
        when(pispiProvider.isAvailable()).thenReturn(false); // PISPI unavailable

        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        CheckoutSession expectedSession = new CheckoutSession("WAVE-FALLBACK", "https://wave.pay/fallback", Instant.now().plusSeconds(3600), Map.of());
        when(waveProvider.initiateCheckout(any())).thenReturn(expectedSession);

        when(registry.get("PISPI")).thenReturn(pispiProvider);
        when(registry.get("WAVE")).thenReturn(waveProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, null);

        assertThat(session.externalId()).isEqualTo("WAVE-FALLBACK");
    }

    @Test
    void initierPaiement_noRequestedProvider_usesDefault() throws Exception {
        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        CheckoutSession expectedSession = new CheckoutSession("WAVE-DEFAULT", "https://wave.pay/default", Instant.now().plusSeconds(3600), Map.of());
        when(waveProvider.initiateCheckout(any())).thenReturn(expectedSession);

        when(registry.get("WAVE")).thenReturn(waveProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, null);

        assertThat(session.externalId()).isEqualTo("WAVE-DEFAULT");
    }

    @Test
    void initierPaiement_whenProviderThrowsPaymentException_fallsBackToDefault() throws Exception {
        PaymentProvider requestedProvider = mock(PaymentProvider.class);
        when(requestedProvider.isAvailable()).thenReturn(true);
        when(requestedProvider.initiateCheckout(any())).thenThrow(new PaymentException("MOMO", "Erreur MOMO", 503));

        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        CheckoutSession fallbackSession = new CheckoutSession("WAVE-AFTER-FAIL", "https://wave.pay/after-fail", Instant.now().plusSeconds(3600), Map.of());
        when(waveProvider.initiateCheckout(any())).thenReturn(fallbackSession);

        when(registry.get("MOMO")).thenReturn(requestedProvider);
        when(registry.get("WAVE")).thenReturn(waveProvider);

        CheckoutSession session = orchestrator.initierPaiement(sampleRequest, "MOMO");
        assertThat(session.externalId()).isEqualTo("WAVE-AFTER-FAIL");
    }

    @Test
    void initierPaiement_allProvidersThrow_throwsLastException() throws Exception {
        PaymentProvider requestedProvider = mock(PaymentProvider.class);
        when(requestedProvider.isAvailable()).thenReturn(true);
        when(requestedProvider.initiateCheckout(any())).thenThrow(new PaymentException("MOMO", "Erreur MOMO", 503));

        PaymentProvider waveProvider = mock(PaymentProvider.class);
        when(waveProvider.isAvailable()).thenReturn(true);
        when(waveProvider.initiateCheckout(any())).thenThrow(new PaymentException("WAVE", "Erreur WAVE", 503));

        when(registry.get("MOMO")).thenReturn(requestedProvider);
        when(registry.get("WAVE")).thenReturn(waveProvider);

        assertThatThrownBy(() -> orchestrator.initierPaiement(sampleRequest, "MOMO"))
                .isInstanceOf(PaymentException.class)
                .hasMessageContaining("WAVE");
    }
}
