package com.zsElectric.boot.common.util;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * HMAC-MD5参数签名工具类
 * 严格按照RFC 2104标准的7个步骤实现
 * @version 1.0
 */
public class HmacMD5Util {
    
    private static final int BLOCK_SIZE = 64; // 块大小为64字节
    private static final byte IPAD = 0x36;   // 内部填充常量
    private static final byte OPAD = 0x5C;   // 外部填充常量


    /**
     * 根据入参顺序生成签名: OperatorID+Data+TimeStamp+Seq
     * @param operatorId 操作员ID
     * @param data 数据内容
     * @param timeStamp 时间戳
     * @param seq 序列号
     * @param sigSecret 签名密钥
     * @return 签名结果(大写)
     * @throws NoSuchAlgorithmException
     */
    public static String genSign(
            String operatorId,
            String data,
            String timeStamp,
            String seq,
            String sigSecret) throws NoSuchAlgorithmException {

        String content = (operatorId + data + timeStamp + seq);
        return hmacMD5Hex(content, sigSecret);
    }


    /**
     * 出参
     * */
    public static String genSign(
            int Ret,
            String Msg,
            String Data,
            String sigSecret) throws NoSuchAlgorithmException {

        String content = (Ret + Msg + Data).toUpperCase();
        return hmacMD5Hex(content, sigSecret);
    }
    
    /**
     * HMAC-MD5签名生成
     * @param data 待签名的消息内容
     * @param key 签名密钥
     * @return 16字节的HMAC-MD5签名结果
     * @throws NoSuchAlgorithmException
     */
    public static byte[] hmacMD5(byte[] data, byte[] key) throws NoSuchAlgorithmException {
        // 步骤1：在签名密钥后面添加0创建长为64字节的字符串
        byte[] k = prepareKey(key);
        
        // 步骤2：将密钥与ipad(0x36)做异或运算
        byte[] iPadXor = xorWithPad(k, IPAD);
        
        // 步骤3：将消息内容附加到第二步的结果字符串的末尾
        byte[] firstInput = concatenate(iPadXor, data);
        
        // 步骤4：对第三步生成的数据流做MD5运算
        byte[] firstHash = md5(firstInput);
        
        // 步骤5：将第一步生成的字符串与opad(0x5c)做异或运算
        byte[] oPadXor = xorWithPad(k, OPAD);
        
        // 步骤6：将第四步的结果附加到第五步的结果字符串的末尾
        byte[] secondInput = concatenate(oPadXor, firstHash);
        
        // 步骤7：对第六步生成的数据流做MD5运算，输出最终结果
        return md5(secondInput);
    }
    
    /**
     * 步骤1：准备密钥 - 填充或哈希到64字节
     * @param key 原始密钥
     * @return 64字节的密钥
     * @throws NoSuchAlgorithmException
     */
    private static byte[] prepareKey(byte[] key) throws NoSuchAlgorithmException {
        byte[] result = new byte[BLOCK_SIZE];
        
        if (key.length > BLOCK_SIZE) {
            // 如果密钥长度超过64字节，先进行MD5哈希
            byte[] hashedKey = md5(key);
            System.arraycopy(hashedKey, 0, result, 0, hashedKey.length);
            // 剩余部分填充0
            for (int i = hashedKey.length; i < BLOCK_SIZE; i++) {
                result[i] = 0;
            }
        } else if (key.length < BLOCK_SIZE) {
            // 如果密钥长度不足64字节，后面补0
            System.arraycopy(key, 0, result, 0, key.length);
            for (int i = key.length; i < BLOCK_SIZE; i++) {
                result[i] = 0;
            }
        } else {
            // 密钥正好64字节
            result = key.clone();
        }
        
        return result;
    }
    
    /**
     * 步骤2/5：密钥与pad常量进行异或运算
     * @param key 64字节的密钥
     * @param pad 填充常量(IPAD或OPAD)
     * @return 异或结果
     */
    private static byte[] xorWithPad(byte[] key, byte pad) {
        byte[] result = new byte[BLOCK_SIZE];
        for (int i = 0; i < BLOCK_SIZE; i++) {
            result[i] = (byte) (key[i] ^ pad);
        }
        return result;
    }
    
    /**
     * 字节数组拼接
     * @param a 第一个字节数组
     * @param b 第二个字节数组
     * @return 拼接后的字节数组
     */
    private static byte[] concatenate(byte[] a, byte[] b) {
        byte[] result = new byte[a.length + b.length];
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }
    
    /**
     * MD5哈希计算
     * @param input 输入数据
     * @return MD5哈希结果(16字节)
     * @throws NoSuchAlgorithmException
     */
    private static byte[] md5(byte[] input) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        return md.digest(input);
    }
    
    /**
     * 字节数组转换为十六进制字符串
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString().toUpperCase();
    }
    
    /**
     * 生成HMAC-MD5签名(十六进制字符串形式)
     * @param data 消息内容
     * @param key 密钥
     * @return 32位十六进制签名字符串
     * @throws NoSuchAlgorithmException
     */
    public static String hmacMD5Hex(String data, String key) throws NoSuchAlgorithmException {
        byte[] signature = hmacMD5(data.getBytes(StandardCharsets.UTF_8), 
                                 key.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(signature);
    }
    
    /**
     * 生成HMAC-MD5签名(十六进制字符串形式)
     * @param data 消息内容字节数组
     * @param key 密钥字节数组
     * @return 32位十六进制签名字符串
     * @throws NoSuchAlgorithmException
     */
    public static String hmacMD5Hex(byte[] data, byte[] key) throws NoSuchAlgorithmException {
        byte[] signature = hmacMD5(data, key);
        return bytesToHex(signature);
    }
    
    /**
     * 验证HMAC-MD5签名
     * @param data 原始消息内容
     * @param key 密钥
     * @param signature 待验证的签名(十六进制字符串)
     * @return 验证结果
     * @throws NoSuchAlgorithmException
     */
    public static boolean verify(String data, String key, String signature) throws NoSuchAlgorithmException {
        String calculatedSignature = hmacMD5Hex(data, key);
        return calculatedSignature.equalsIgnoreCase(signature);
    }
    
    /**
     * 测试方法
     */
    public static void main(String[] args) {
        try {
            // 测试数据
            String data = "KYWxoKWK3w8a8867aXCha+tgVE2cbZ4eR1Dc1YExri06DfZWBpUMAzlhY7rWR5SeU+xCVOauk4F7MxCJLN+5aJCBENCOAZtUksMM7VgsOz0=";
            String key = "U9xFXjjdYAycq30C";
            
            System.out.println("=== HMAC-MD5签名测试 ===");
            System.out.println("原始数据: " + data);
            System.out.println("密钥: " + key);
            
            // 生成签名
            long startTime = System.nanoTime();
            String signature = hmacMD5Hex(data, key);
            long signTime = System.nanoTime() - startTime;
            
            System.out.println("HMAC-MD5签名: " + signature);
            System.out.println("签名长度: " + signature.length() + "字符(32字节)");
            System.out.println("签名耗时: " + signTime + "纳秒");
            
            // 验证签名
            startTime = System.nanoTime();
            boolean isValid = verify(data, key, signature);
            long verifyTime = System.nanoTime() - startTime;
            
            System.out.println("签名验证: " + (isValid ? "成功" : "失败"));
            System.out.println("验证耗时: " + verifyTime + "纳秒");
            
            // 测试不同长度密钥
            System.out.println("\n=== 不同长度密钥测试 ===");
            testWithDifferentKeyLengths();
            
            // 测试签名一致性
            System.out.println("\n=== 签名一致性测试 ===");
            testSignatureConsistency();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 测试不同长度密钥的签名
     */
    private static void testWithDifferentKeyLengths() throws NoSuchAlgorithmException {
        String data = "测试数据";
        
        // 短密钥(小于64字节)
        String shortKey = "short";
        String signature1 = hmacMD5Hex(data, shortKey);
        System.out.println("短密钥签名: " + signature1);
        
        // 长密钥(等于64字节)
        String longKey = "thisIsAExactly64BytesLongKeyUsedForHMACMD5SignatureTest123";
        String signature2 = hmacMD5Hex(data, longKey);
        System.out.println("64字节密钥签名: " + signature2);
        
        // 超长密钥(大于64字节)
        String veryLongKey = "thisIsAVeryLongKeyThatExceedsThe64BytesBlockSizeUsedForHMACMD5SignatureTest123456789";
        String signature3 = hmacMD5Hex(data, veryLongKey);
        System.out.println("超长密钥签名: " + signature3);
    }
    
    /**
     * 测试签名一致性(相同输入应产生相同输出)
     */
    private static void testSignatureConsistency() throws NoSuchAlgorithmException {
        String data = "一致性测试数据";
        String key = "testKey";
        
        // 多次签名应该结果一致
        String signature1 = hmacMD5Hex(data, key);
        String signature2 = hmacMD5Hex(data, key);
        String signature3 = hmacMD5Hex(data, key);
        
        System.out.println("第一次签名: " + signature1);
        System.out.println("第二次签名: " + signature2);
        System.out.println("第三次签名: " + signature3);
        
        boolean consistent = signature1.equals(signature2) && signature2.equals(signature3);
        System.out.println("签名一致性: " + (consistent ? "通过" : "失败"));
        
        // 测试数据微小变化会导致签名完全不同
        String similarData = "一致性测试数据 "; // 多一个空格
        String differentSignature = hmacMD5Hex(similarData, key);
        System.out.println("微小变化后签名: " + differentSignature);
        System.out.println("敏感性测试: " + (!signature1.equals(differentSignature) ? "通过" : "失败"));
    }
}