package quant.rich.emoney.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.util.encoders.Hex;

import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.RequestBody;

@Slf4j
public class EncryptUtils {

    private static final byte[] IV = "aesiv_emapp@2018".getBytes(StandardCharsets.UTF_8);
    private static final String PASSWORD_KEY_STRING_BASE64 = "5pxlF/NrBtxlBve0jQwGEw==";
    private static final String PASSWORD_CIPHER_INSTANCE = "AES/CBC/PKCS5Padding";
    private static final String AES_DECODE_KEY_STRING_BASE64 = "39KLC1etSfUXuiGxRm5ilDDtqapKXm0COuHxUrWSWlc=";
    private static final String EM_SIGN_MESS_1 = "23082404151001";
    private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01";
    private static final String SLASH = "/";
    private static final String COLON = ":";
    private static final String EMPTY_SHA3_224 = sha3("", 224);
    
    /**
     * 判断密码是否空字符串，或经过 SHA_224 加密过的空字符串
     * @param password
     * @return
     */
    public static boolean passwordIsNotEmpty(String password) {
        return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_SHA3_224);
    }
    
    /**
     * 加密用于 Emoney 登录的密码
     * @param pwd 明文密码
     * @return 加密过的密码（Base64 字符串）
     */
    public static String encryptAesForEmoneyPassword(String pwd) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64);

            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(IV);

            Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // 执行加密
            byte[] encryptedBytes = cipher.doFinal(pwd.getBytes(StandardCharsets.UTF_8));

            // 将加密结果转换为Base64字符串
            String encryptedString = Base64.getEncoder().encodeToString(encryptedBytes);
            return encryptedString;

        } catch (Exception e) {
            log.error("Encrypt aes for emoney-password failed", e);
            return null;
        }
    }
    
    /**
     * 解密由 Emoney 进行加密过的密码
     * @param pwd 加密过的密码（Base64 字符串）
     * @return 明文密码
     */
    public static String decryptAesForEmoneyPassword(String pwd) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(PASSWORD_KEY_STRING_BASE64);
            byte[] passBytes = Base64.getDecoder().decode(pwd);
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(IV);

            Cipher cipher = Cipher.getInstance(PASSWORD_CIPHER_INSTANCE);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // 执行解密
            byte[] decryptedBytes = cipher.doFinal(passBytes);

            return new String(decryptedBytes, "UTF-8");

        } catch (Exception e) {
            //log.error("Decrypt aes for emoney-password failed", e);
            return null;
        }
    }
    
    /**
     * 使用益盟自带 AES key 对指定字节数组进行解密，一般而言该字节数组通过解码 Base64 字符串得到
     * @param bArr
     * @return
     */
    private static String decryptAesForEmSignMess(byte[] bArr) {
        
        try {
            byte[] keyBytes = Base64.getDecoder().decode(AES_DECODE_KEY_STRING_BASE64);
            EmoneyBytesResortUtils.d(keyBytes);
            
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            
            return new String(cipher.doFinal(bArr), "UTF-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
                | BadPaddingException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 字符串转 32 位小写 md5 字符串
     * 
     * @param str
     * @return
     */
    public static String toMD5String(String str) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        try {
            return toMD5String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e10) {
            e10.printStackTrace();
            return null;
        }
    }

    /**
     * 字节数组转 32 位小写 md5 字符串
     * @param bytes
     * @return
     */
    public static String toMD5String(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        try {
            MessageDigest instance = MessageDigest.getInstance("MD5");
            instance.update(bytes);
            return bytesToHex(instance.digest());
        } catch (NoSuchAlgorithmException e10) {
            e10.printStackTrace();
            return null;
        }
    }

    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            // 将字节转换为无符号整数表示的16进制，并且保证每个字节输出两个字符
            sb.append(String.format("%02x", b & 0xFF));
        }
        return sb.toString();
    }
    
    /**
     * 根据 content、method 和 xProtocolId 生成 EM-Sign，随机字符串和时间戳自动生成
     * @param content 请求的字节数组
     * @param method 请求方法
     * @param xProtocolId 请求 X-Protocol-Id
     * @return
     * @throws IOException
     */
    public static String getEMSign(
            byte[] content,
            String method,
            String xProtocolId) throws IOException {
        String randomString = TextUtils.randomString(10);
        long timestampFixed = Instant.now().toEpochMilli();
        return getEMSign(content, method, xProtocolId, randomString, timestampFixed);
    }
    
    /**
     * 根据 content、method、xProtocolId、指定的随机 10 位字符串、指定的时间戳生成 EM-Sign
     * @param content 请求的字节数组
     * @param method 请求方法
     * @param xProtocolId 请求 X-Protocol-Id
     * @return
     * @throws IOException
     */
    public static String getEMSign(
            byte[] content,
            String method,
            String xProtocolId,
            String randomString, long timestampFixed) throws IOException {
        
        // TODO: 空校验、时间戳校验、随机字符串长度校验等防呆操作
        
        StringBuffer plainSignSb = new StringBuffer();
        plainSignSb.append(EM_SIGN_MESS_1);
        plainSignSb.append(method.toUpperCase());
        plainSignSb.append(EM_SIGN_MESS_2);
        plainSignSb.append(SLASH);
        plainSignSb.append(URLDecoder.decode(xProtocolId, "UTF-8"));
        plainSignSb.append(timestampFixed);
        plainSignSb.append(randomString);
        
        if (content != null && content.length != 0) {
            plainSignSb.append(toMD5String(content));
        }
        
        String md5Sign = EncryptUtils.toMD5String(plainSignSb.toString());
        
        StringBuilder emSignSb = new StringBuilder();
        emSignSb.append(EM_SIGN_MESS_1);
        emSignSb.append(COLON);
        emSignSb.append(md5Sign);
        emSignSb.append(COLON);
        emSignSb.append(randomString);
        emSignSb.append(COLON);
        emSignSb.append(timestampFixed);
        
        String emSign = emSignSb.toString();
        return emSign;
    }

    public static byte[] sha3(byte[] bytes, int digit) {
        SHA3Digest digest = new SHA3Digest(digit);
        digest.update(bytes, 0, bytes.length);
        byte[] rsData = new byte[digest.getDigestSize()];
        digest.doFinal(rsData, 0);
        return rsData;
    }

    public static String sha256(String input) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception e) {
            log.error("SHA-256 Hash failed", e);
            return null;
        }
    }

    public static String sha3(String data, int digit) {
        return Hex.toHexString(sha3(data.getBytes(), digit));
    }

    public static String toHexString(byte[] data) {
        return Hex.toHexString(data);
    }

    public static void main(String[] args) throws IOException {
        // EM-Sign:
        // 23082404151001:356b7041c37848b4f73a8ad5c9416f48:nBZTuInqo1:1721439297485
        long timestampFixed = 1721439297485L;
        String randomString = "nBZTuInqo1";
        byte[] mess = Base64.getDecoder().decode("0JryhAshy7JXgP+5sLmWwaurGM3YHWaFbGaKH/mi7oKN+5mlTnEp5LTeFJVtJTTR");
        String f13443a = EncryptUtils.decryptAesForEmSignMess(mess);

        String path = "C:\\Users\\Administrator\\Desktop\\Emoney\\login.request.body";

        FileInputStream in = new FileInputStream(new File(path));
        byte[] requestBody = new byte[in.available()];
        in.read(requestBody);
        in.close();

        String requestBodyStr = "{\"date\":0}";// new String(requestBody, "UTF-8") + '\0';
        requestBody = requestBodyStr.getBytes("UTF-8");

        RequestBody body = RequestBody.create(requestBodyStr, MediaType.parse("application/json"));
        okio.Buffer buffer = new okio.Buffer();
        body.writeTo(buffer);
        requestBody = buffer.readByteArray();

        String requestBodyMd5;
        if (requestBody == null || requestBody.length == 0) {
            requestBodyMd5 = "";
        } else {
            requestBodyMd5 = toMD5String(requestBody);
        }

        String str2 = "23082404151001" + "POST" + f13443a + "/"
                + URLDecoder.decode("SmartInvestment%2FIndex%2FTianYan", "UTF-8") + timestampFixed + randomString
                + requestBodyMd5;

        String md5 = EncryptUtils.toMD5String(str2);
        log.info("Handmade emsign md5: {}", md5);
        String emSign = getEMSign(requestBody, "POST", "SmartInvestment%2FIndex%2FTianYan", randomString, timestampFixed);
        log.info("Method emsign: {}", emSign);
        
        // 测试密码加解密
        String encryptPass = encryptAesForEmoneyPassword("123456");
        log.info("Encrypt pass for 123456: {}", encryptPass);
        String decryptPass = decryptAesForEmoneyPassword(encryptPass);
        log.info("Decrypt pass for {}: {}", encryptPass, decryptPass);
        
    }

}
