package com.ruoyi.app.omgpay;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;

import org.springframework.stereotype.Component;

@Component
public class OmgCheckMacSigner {

    public String sign(Map<String, String> fields, String hashKey, String hashIv) {
        requireSecrets(hashKey, hashIv);
        TreeMap<String, String> sorted = validatedCopy(fields);
        String query = sorted.entrySet().stream()
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));
        String raw = "HashKey=" + hashKey + "&" + query + "&HashIV=" + hashIv;
        String encoded = dotNetUrlEncode(raw).toLowerCase(Locale.ROOT);
        return HexFormat.of().withUpperCase().formatHex(sha256(encoded));
    }

    private static void requireSecrets(String hashKey, String hashIv) {
        requirePresent("hashKey", hashKey);
        requirePresent("hashIv", hashIv);
    }

    private static TreeMap<String, String> validatedCopy(Map<String, String> fields) {
        Objects.requireNonNull(fields, "fields must not be null");
        TreeMap<String, String> sorted = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        for (Map.Entry<String, String> entry : fields.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            requireNonNull("field key", key);
            requireNonNull("field value", value);
            if ("CheckMacValue".equals(key)) {
                throw new IllegalArgumentException("CheckMacValue must not be supplied");
            }
            if (sorted.put(key, value) != null) {
                throw new IllegalArgumentException("duplicate field key ignoring case");
            }
        }
        return sorted;
    }

    private static void requirePresent(String name, String value) {
        if (value == null) {
            throw new IllegalArgumentException(name + " must not be null");
        }
        if (value.isEmpty()) {
            throw new IllegalArgumentException(name + " must not be empty");
        }
    }

    private static void requireNonNull(String name, String value) {
        if (value == null) {
            throw new IllegalArgumentException(name + " must not be null");
        }
    }

    private static String dotNetUrlEncode(String raw) {
        String encoded = URLEncoder.encode(raw, StandardCharsets.UTF_8);
        // OMG's official appendix uses .NET URL conversion before lowercasing and hashing.
        return encoded.replace("%2D", "-")
                .replace("%5F", "_")
                .replace("%2E", ".")
                .replace("%21", "!")
                .replace("%2A", "*")
                .replace("%28", "(")
                .replace("%29", ")");
    }

    private static byte[] sha256(String value) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("SHA-256 not available", exception);
        }
    }
}
