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

import dev.lions.unionflow.server.api.payment.PaymentException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Field;
import java.util.HexFormat;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class PispiSignatureVerifierTest {

    private PispiSignatureVerifier verifier;

    @BeforeEach
    void setUp() {
        verifier = new PispiSignatureVerifier();
    }

    private void setField(String fieldName, String value) throws Exception {
        Field f = PispiSignatureVerifier.class.getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(verifier, value);
    }

    @Test
    @DisplayName("isIpAllowed — pas de config → true")
    void isIpAllowed_whenNoConfig_returnsTrue() throws Exception {
        setField("allowedIps", "");
        assertThat(verifier.isIpAllowed("1.2.3.4")).isTrue();
    }

    @Test
    @DisplayName("isIpAllowed — IP dans la liste → true")
    void isIpAllowed_whenIpInList_returnsTrue() throws Exception {
        setField("allowedIps", "10.0.0.1, 10.0.0.2, 192.168.1.1");
        assertThat(verifier.isIpAllowed("10.0.0.2")).isTrue();
    }

    @Test
    @DisplayName("isIpAllowed — IP absente de la liste → false")
    void isIpAllowed_whenIpNotInList_returnsFalse() throws Exception {
        setField("allowedIps", "10.0.0.1,10.0.0.2");
        assertThat(verifier.isIpAllowed("1.2.3.4")).isFalse();
    }

    @Test
    @DisplayName("verifySignature — pas de secret configuré → true")
    void verifySignature_whenNoSecret_returnsTrue() throws Exception {
        setField("webhookSecret", "");
        assertThat(verifier.verifySignature("body", Map.of())).isTrue();
    }

    @Test
    @DisplayName("verifySignature — header absent → PaymentException 401")
    void verifySignature_whenSignatureAbsent_throwsPaymentException() throws Exception {
        setField("webhookSecret", "secret123");
        assertThatThrownBy(() -> verifier.verifySignature("body", Map.of()))
                .isInstanceOf(PaymentException.class)
                .hasMessageContaining("absente")
                .extracting(e -> ((PaymentException) e).getHttpStatus())
                .isEqualTo(401);
    }

    @Test
    @DisplayName("verifySignature — signature correcte → true")
    void verifySignature_whenSignatureValid_returnsTrue() throws Exception {
        setField("webhookSecret", "secret123");

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec("secret123".getBytes(), "HmacSHA256"));
        String validSig = HexFormat.of().formatHex(mac.doFinal("body".getBytes()));

        assertThat(verifier.verifySignature("body", Map.of("X-PISPI-Signature", validSig))).isTrue();
    }

    @Test
    @DisplayName("verifySignature — signature incorrecte → PaymentException 401")
    void verifySignature_whenSignatureInvalid_throwsPaymentException() throws Exception {
        setField("webhookSecret", "secret123");
        assertThatThrownBy(() -> verifier.verifySignature("body", Map.of("X-PISPI-Signature", "deadbeef")))
                .isInstanceOf(PaymentException.class)
                .hasMessageContaining("invalide")
                .extracting(e -> ((PaymentException) e).getHttpStatus())
                .isEqualTo(401);
    }

    @Test
    @DisplayName("verifySignature — header insensible à la casse")
    void verifySignature_caseInsensitiveHeader() throws Exception {
        setField("webhookSecret", "secret123");

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec("secret123".getBytes(), "HmacSHA256"));
        String validSig = HexFormat.of().formatHex(mac.doFinal("body".getBytes()));

        // header en minuscules
        assertThat(verifier.verifySignature("body", Map.of("x-pispi-signature", validSig))).isTrue();
    }
}
