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

import dev.lions.unionflow.server.api.payment.PaymentEvent;
import dev.lions.unionflow.server.api.payment.PaymentException;
import dev.lions.unionflow.server.api.payment.PaymentStatus;
import dev.lions.unionflow.server.payment.orchestration.PaymentOrchestrator;
import dev.lions.unionflow.server.payment.pispi.dto.Pacs002Response;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
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.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.Instant;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class PispiWebhookResourceTest {

    @InjectMocks
    PispiWebhookResource resource;

    @Mock
    PispiSignatureVerifier verifier;

    @Mock
    PispiIso20022Mapper mapper;

    @Mock
    PaymentOrchestrator orchestrator;

    @Mock
    HttpHeaders headers;

    private static final String VALID_XML =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "<FIToFIPmtStsRpt>" +
        "  <OrgnlEndToEndId>REF-001</OrgnlEndToEndId>" +
        "  <TxSts>ACSC</TxSts>" +
        "  <ClrSysRef>PISPI-TXN-001</ClrSysRef>" +
        "</FIToFIPmtStsRpt>";

    @BeforeEach
    void setUp() {
        // Default: IP is allowed
        when(verifier.isIpAllowed(anyString())).thenReturn(true);
    }

    @Test
    void recevoir_whenIpNotAllowed_returns403() {
        when(verifier.isIpAllowed("192.168.1.100")).thenReturn(false);

        Response response = resource.recevoir(VALID_XML, headers, "192.168.1.100");

        assertThat(response.getStatus()).isEqualTo(403);
    }

    @Test
    void recevoir_whenIpAllowedAndSignatureInvalid_returns401() throws Exception {
        MultivaluedMap<String, String> headersMap = new MultivaluedHashMap<>();
        headersMap.put("X-PISPI-Signature", List.of("invalidsig"));
        when(headers.getRequestHeaders()).thenReturn(headersMap);
        doThrow(new PaymentException("PISPI", "Signature invalide", 401))
                .when(verifier).verifySignature(anyString(), any());

        Response response = resource.recevoir(VALID_XML, headers, "");

        assertThat(response.getStatus()).isEqualTo(401);
    }

    @Test
    void recevoir_whenValid_returns200AndCallsOrchestrator() throws Exception {
        MultivaluedMap<String, String> headersMap = new MultivaluedHashMap<>();
        when(headers.getRequestHeaders()).thenReturn(headersMap);
        when(verifier.verifySignature(anyString(), any())).thenReturn(true);

        Pacs002Response pacs002 = new Pacs002Response();
        pacs002.setOriginalEndToEndId("REF-001");
        pacs002.setTransactionStatus("ACSC");
        pacs002.setClearingSystemReference("PISPI-TXN-001");

        PaymentEvent event = new PaymentEvent("PISPI-TXN-001", "REF-001", PaymentStatus.SUCCESS,
                null, "PISPI-TXN-001", Instant.now());

        try (MockedStatic<Pacs002Response> mockedStatic = mockStatic(Pacs002Response.class)) {
            mockedStatic.when(() -> Pacs002Response.fromXml(VALID_XML)).thenReturn(pacs002);
            when(mapper.fromPacs002(pacs002)).thenReturn(event);
            doNothing().when(orchestrator).handleEvent(event);

            Response response = resource.recevoir(VALID_XML, headers, "");

            assertThat(response.getStatus()).isEqualTo(200);
            verify(orchestrator).handleEvent(event);
        }
    }

    @Test
    void recevoir_whenXmlInvalid_returns500() throws Exception {
        MultivaluedMap<String, String> headersMap = new MultivaluedHashMap<>();
        when(headers.getRequestHeaders()).thenReturn(headersMap);
        when(verifier.verifySignature(anyString(), any())).thenReturn(true);

        String invalidXml = "NOT VALID XML <<<";

        Response response = resource.recevoir(invalidXml, headers, "");

        assertThat(response.getStatus()).isEqualTo(500);
    }

    @Test
    void recevoir_whenIpFromForwardedFor_extractsFirstIp() {
        // Two IPs in X-Forwarded-For: first should be checked
        when(verifier.isIpAllowed("10.0.0.1")).thenReturn(false);

        Response response = resource.recevoir(VALID_XML, headers, "10.0.0.1, 172.16.0.1");

        assertThat(response.getStatus()).isEqualTo(403);
    }

    @Test
    void recevoir_whenForwardedForBlank_usesUnknown() {
        // "unknown" IP should be allowed (default behavior with empty allowedIps)
        when(verifier.isIpAllowed("unknown")).thenReturn(true);

        MultivaluedMap<String, String> headersMap = new MultivaluedHashMap<>();
        when(headers.getRequestHeaders()).thenReturn(headersMap);
        when(verifier.verifySignature(anyString(), any())).thenReturn(true);

        try (MockedStatic<Pacs002Response> mockedStatic = mockStatic(Pacs002Response.class)) {
            Pacs002Response pacs002 = new Pacs002Response();
            pacs002.setTransactionStatus("ACSC");
            mockedStatic.when(() -> Pacs002Response.fromXml(anyString())).thenReturn(pacs002);

            PaymentEvent event = new PaymentEvent("EXT", "REF", PaymentStatus.SUCCESS, null, "TXN", Instant.now());
            when(mapper.fromPacs002(pacs002)).thenReturn(event);

            // forwardedFor = "" (blank)
            Response response = resource.recevoir(VALID_XML, headers, "");

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

    @Test
    void recevoir_whenOrchestratorThrows_returns500() throws Exception {
        MultivaluedMap<String, String> headersMap = new MultivaluedHashMap<>();
        when(headers.getRequestHeaders()).thenReturn(headersMap);
        when(verifier.verifySignature(anyString(), any())).thenReturn(true);

        try (MockedStatic<Pacs002Response> mockedStatic = mockStatic(Pacs002Response.class)) {
            Pacs002Response pacs002 = new Pacs002Response();
            pacs002.setTransactionStatus("ACSC");
            mockedStatic.when(() -> Pacs002Response.fromXml(VALID_XML)).thenReturn(pacs002);

            PaymentEvent event = new PaymentEvent("EXT", "REF", PaymentStatus.SUCCESS, null, "TXN", Instant.now());
            when(mapper.fromPacs002(pacs002)).thenReturn(event);
            doThrow(new RuntimeException("Orchestrator error")).when(orchestrator).handleEvent(event);

            Response response = resource.recevoir(VALID_XML, headers, "");

            assertThat(response.getStatus()).isEqualTo(500);
        }
    }
}
