package com.ruoyi.app.omgpay;

import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class OmgCheckMacSignerTest {

    private static final String HASH_KEY = "5294y06JbISpM5x9";
    private static final String HASH_IV = "v77hoKGq4kWxNNIS";
    private static final String EXPECTED_SIGNATURE =
            "AA5842FDA7E55ACEB7118D6353E9822CA6D6FF09A0D1FC129A879DD5CAF93266";

    private final OmgCheckMacSigner signer = new OmgCheckMacSigner();

    @Test
    void signsOfficialAppendixVector() {
        assertEquals(EXPECTED_SIGNATURE, signer.sign(officialFields(), HASH_KEY, HASH_IV));
    }

    @Test
    void includesAdditionalAndEmptyFieldsInSignature() {
        Map<String, String> fields = officialFields();

        assertNotEquals(signer.sign(fields, HASH_KEY, HASH_IV),
                signer.sign(withExtraField(fields, "NeedExtraPaidInfo", "Y"), HASH_KEY, HASH_IV));
        assertNotEquals(signer.sign(fields, HASH_KEY, HASH_IV),
                signer.sign(withExtraField(fields, "EmptyExtra", ""), HASH_KEY, HASH_IV));
    }

    @Test
    void queryResponseFieldsAreSortedCaseInsensitively() {
        Map<String, String> fields = new LinkedHashMap<>();
        fields.put("MerchantID", "1000031");
        fields.put("amount", "");
        fields.put("CustomField1", "");
        fields.put("card4no", "4242");
        assertEquals(independentCaseInsensitiveSignature(fields),
                signer.sign(fields, HASH_KEY, HASH_IV));
    }

    @Test
    void rejectsCallerSuppliedCheckMacValue() {
        assertThrows(IllegalArgumentException.class,
                () -> signer.sign(Map.of("CheckMacValue", "caller-value"), HASH_KEY, HASH_IV));
    }

    @Test
    void rejectsNullHashKey() {
        assertThrows(IllegalArgumentException.class, () -> signer.sign(officialFields(), null, HASH_IV));
    }

    @Test
    void rejectsNullHashIv() {
        assertThrows(IllegalArgumentException.class, () -> signer.sign(officialFields(), HASH_KEY, null));
    }

    @Test
    void rejectsEmptyHashKey() {
        assertThrows(IllegalArgumentException.class, () -> signer.sign(officialFields(), "", HASH_IV));
    }

    @Test
    void rejectsEmptyHashIv() {
        assertThrows(IllegalArgumentException.class, () -> signer.sign(officialFields(), HASH_KEY, ""));
    }

    @Test
    void leavesInputUnchangedAndReturnsUppercaseSha256Hex() {
        Map<String, String> fields = new LinkedHashMap<>(officialFields());
        Map<String, String> snapshot = new LinkedHashMap<>(fields);

        String signature = signer.sign(fields, HASH_KEY, HASH_IV);

        assertEquals(snapshot, fields);
        assertTrue(signature.matches("[0-9A-F]{64}"));
    }

    private static Map<String, String> officialFields() {
        Map<String, String> fields = new LinkedHashMap<>();
        fields.put("TradeDesc", "\u4FC3\u92B7\u65B9\u6848");
        fields.put("PaymentType", "aio");
        fields.put("MerchantTradeDate", "2013/03/12 15:30:23");
        fields.put("MerchantTradeNo", "funpoint20130312153023");
        fields.put("MerchantID", "2000132");
        fields.put("ReturnURL", "https://www.funpoint.com.tw/receive.php");
        fields.put("ItemName", "Apple iphone 7 \u624B\u6A5F\u6BBC");
        fields.put("TotalAmount", "1000");
        fields.put("ChoosePayment", "ALL");
        fields.put("EncryptType", "1");
        return fields;
    }

    private static Map<String, String> withExtraField(Map<String, String> fields, String key, String value) {
        Map<String, String> updated = new LinkedHashMap<>(fields);
        updated.put(key, value);
        return updated;
    }

    private static String independentCaseInsensitiveSignature(Map<String, String> fields) {
        java.util.TreeMap<String, String> sorted = new java.util.TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        sorted.putAll(fields);
        String query = sorted.entrySet().stream()
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(java.util.stream.Collectors.joining("&"));
        String raw = "HashKey=" + HASH_KEY + "&" + query + "&HashIV=" + HASH_IV;
        String encoded = java.net.URLEncoder.encode(raw, java.nio.charset.StandardCharsets.UTF_8)
                .replace("%2D", "-").replace("%5F", "_").replace("%2E", ".")
                .replace("%21", "!").replace("%2A", "*")
                .replace("%28", "(").replace("%29", ")")
                .toLowerCase(java.util.Locale.ROOT);
        try {
            byte[] digest = java.security.MessageDigest.getInstance("SHA-256")
                    .digest(encoded.getBytes(java.nio.charset.StandardCharsets.UTF_8));
            return java.util.HexFormat.of().withUpperCase().formatHex(digest);
        } catch (java.security.NoSuchAlgorithmException exception) {
            throw new AssertionError(exception);
        }
    }
}
