package com.malk.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 请求签名工具：HMAC-SHA256
 *
 * <p>用于 {@code add-request-auth-replay-guard} 专项（capability: request-auth）的签名计算与验证。
 * 协议见 {@code mjava-baseline.md §3.4} 附录（或该 change 的 design.md）：</p>
 *
 * <pre>
 * signContent = timestamp + "\n" + nonce + "\n" + method + "\n" + path + "\n" + bodyHash
 * signature   = HEX-LOWER( HMAC-SHA256(secret, signContent) )
 * bodyHash    = HEX-LOWER( SHA256(body bytes) )   // body 为空时对空字符串求 SHA256
 * </pre>
 */
public abstract class UtilSignature {

    private static final String HMAC_SHA256 = "HmacSHA256";
    private static final String SHA_256 = "SHA-256";
    private static final char[] HEX = "0123456789abcdef".toCharArray();

    /**
     * 对请求体计算 SHA256 hex（小写）
     *
     * @param body 请求体字节；null 按空字符串处理
     */
    public static String sha256Hex(byte[] body) {
        try {
            MessageDigest md = MessageDigest.getInstance(SHA_256);
            byte[] digest = md.digest(body == null ? new byte[0] : body);
            return toHex(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("SHA-256 algorithm not available", e);
        }
    }

    /**
     * 计算 HMAC-SHA256 签名 hex（小写）
     *
     * @param secret      共享密钥
     * @param timestamp   请求时间戳（毫秒字符串）
     * @param nonce       请求 Nonce
     * @param method      HTTP 方法（大写）
     * @param path        请求路径（不含 host / query）
     * @param bodyHash    请求体 SHA256 hex（{@link #sha256Hex(byte[])}）
     */
    public static String sign(String secret, String timestamp, String nonce,
                              String method, String path, String bodyHash) {
        String content = timestamp + "\n" + nonce + "\n" + method + "\n" + path + "\n" + bodyHash;
        try {
            Mac mac = Mac.getInstance(HMAC_SHA256);
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256));
            byte[] digest = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return toHex(digest);
        } catch (Exception e) {
            throw new IllegalStateException("HMAC-SHA256 sign failed", e);
        }
    }

    /**
     * 常量时间比较两个签名，防止时序攻击
     */
    public static boolean safeEquals(String a, String b) {
        if (a == null || b == null) return false;
        if (a.length() != b.length()) return false;
        int result = 0;
        for (int i = 0; i < a.length(); i++) {
            result |= a.charAt(i) ^ b.charAt(i);
        }
        return result == 0;
    }

    private static String toHex(byte[] bytes) {
        char[] out = new char[bytes.length * 2];
        for (int i = 0; i < bytes.length; i++) {
            int v = bytes[i] & 0xFF;
            out[i * 2] = HEX[v >>> 4];
            out[i * 2 + 1] = HEX[v & 0x0F];
        }
        return new String(out);
    }
}
