package com.ruoyi.app.omgpay;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class OmgStoreCredentialVerifierTest {
    private static final String HASH_KEY = "5294y06JbISpM5x9";
    private static final String HASH_IV = "v77hoKGq4kWxNNIS";

    private OmgPaymentQueryGateway gateway;
    private OmgCheckMacSigner signer;
    private OmgStoreCredentialVerifier verifier;

    @BeforeEach
    void setUp() {
        gateway = mock(OmgPaymentQueryGateway.class);
        signer = new OmgCheckMacSigner();
        verifier = new OmgStoreCredentialVerifier(gateway, new OmgQueryResponseParser(), signer,
                Clock.fixed(Instant.ofEpochSecond(1_786_598_400L), ZoneOffset.UTC));
    }

    @Test
    void signsAStageProbeAndVerifiesEveryReturnedField() {
        when(gateway.query(anyMap())).thenAnswer(invocation -> signedResponse(invocation.getArgument(0)));

        Map<String, String> response = verifier.verify("1000031", HASH_KEY, HASH_IV);

        assertEquals("0", response.get("TradeStatus"));
        @SuppressWarnings("unchecked")
        var request = org.mockito.ArgumentCaptor.forClass(Map.class);
        verify(gateway).query(request.capture());
        assertEquals(Set.of("MerchantID", "MerchantTradeNo", "TimeStamp", "CheckMacValue"),
                request.getValue().keySet());
        assertEquals("1000031", request.getValue().get("MerchantID"));
        assertEquals("OMGVERIFY1786598400", request.getValue().get("MerchantTradeNo"));
        assertEquals("1786598400", request.getValue().get("TimeStamp"));
        LinkedHashMap<String, String> signingFields = new LinkedHashMap<>(request.getValue());
        String actual = signingFields.remove("CheckMacValue");
        assertEquals(signer.sign(signingFields, HASH_KEY, HASH_IV), actual);
    }

    @Test
    void rejectsAnyTamperedExtraResponseField() {
        when(gateway.query(anyMap())).thenAnswer(invocation ->
                signedResponse(invocation.getArgument(0)).replace("FutureEmptyField=", "FutureEmptyField=tampered"));

        assertThrows(IllegalArgumentException.class,
                () -> verifier.verify("1000031", HASH_KEY, HASH_IV));
    }

    @Test
    void springContextCreatesCredentialVerifierWithDependencies() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.registerBean(OmgPaymentQueryGateway.class, () -> gateway);
            context.registerBean(OmgQueryResponseParser.class);
            context.registerBean(OmgCheckMacSigner.class);
            context.register(OmgStoreCredentialVerifier.class);

            context.refresh();

            assertNotNull(context.getBean(OmgStoreCredentialVerifier.class));
        }
    }

    private String signedResponse(Map<String, String> request) {
        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put("MerchantID", request.get("MerchantID"));
        fields.put("MerchantTradeNo", request.get("MerchantTradeNo"));
        fields.put("TradeStatus", "0");
        fields.put("RtnMsg", "Not found");
        fields.put("FutureEmptyField", "");
        fields.put("CheckMacValue", signer.sign(fields, HASH_KEY, HASH_IV));
        return fields.entrySet().stream()
                .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue()))
                .collect(java.util.stream.Collectors.joining("&"));
    }

    private static String encode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }
}
