package com.qdb.sdk.util;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;

/**
 * 签名工具类
 * <p>
 * 企得宝ERP API 签名算法（MD5）：
 * <ol>
 *   <li>将所有公共参数按照参数名的首字母先后顺序排列（字典序）</li>
 *   <li>把排序后的结果按照参数名+参数值的方式拼接</li>
 *   <li>拼装好的字符串首尾拼接 client_secret 进行 md5（小写32位）</li>
 * </ol>
 * </p>
 *
 * @author qdb-sdk
 * @version 1.0.0
 */
public class SignUtil {

    private SignUtil() {
    }

    /**
     * 生成 API 请求签名
     *
     * @param params       公共参数（不包含 sign 本身）
     * @param clientSecret 应用密钥
     * @return 签名字符串（32位小写MD5）
     */
    public static String generateSign(Map<String, String> params, String clientSecret) {
        // 使用TreeMap按key字典序排序
        TreeMap<String, String> sortedParams = new TreeMap<>(params);

        // 按照 key+value 方式拼接
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            sb.append(entry.getKey()).append(entry.getValue());
        }

        // 首尾拼接 client_secret
        String signStr = clientSecret + sb + clientSecret;

        // MD5 加密（小写32位）
        return md5(signStr);
    }

    /**
     * 生成回调通知签名验证字符串
     * <p>
     * 与请求签名规则相同：所有参数（除 sign）按字典序排列，
     * 按参数名+参数值拼接，两端加上 secret 后进行 MD5。
     * </p>
     *
     * @param params 回调通知参数（不包含 sign）
     * @param secret 企业密钥
     * @return 验证签名字符串
     */
    public static String generateCallbackSign(Map<String, String> params, String secret) {
        return generateSign(params, secret);
    }

    /**
     * MD5 加密
     *
     * @param str 待加密字符串
     * @return 32位小写 MD5 值
     */
    public static String md5(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(str.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 加密失败", e);
        }
    }
}
