package com.bsphp.car;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public final class BSPHPCrypto {
    private BSPHPCrypto() {}

    static String md5Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder(d.length * 2);
        for (byte b : d) sb.append(String.format("%02x", b & 0xff));
        return sb.toString();
    }

    static String aes128CbcEncryptBase64(String plaintext, String key16) throws Exception {
        byte[] key = key16.getBytes(StandardCharsets.UTF_8);
        if (key.length > 16) key = java.util.Arrays.copyOf(key, 16);
        if (key.length < 16) key = java.util.Arrays.copyOf(key, 16);
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(key));
        return Base64.getEncoder().encodeToString(c.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    }

    static String aes128CbcDecryptBase64ToString(String ciphertextB64, String key16) throws Exception {
        byte[] key = key16.getBytes(StandardCharsets.UTF_8);
        if (key.length > 16) key = java.util.Arrays.copyOf(key, 16);
        if (key.length < 16) key = java.util.Arrays.copyOf(key, 16);
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(key));
        byte[] out = c.doFinal(Base64.getDecoder().decode(ciphertextB64));
        return new String(out, StandardCharsets.UTF_8);
    }

    static String rsaEncryptPkcs1Base64(String message, String publicKeyBase64Der) throws Exception {
        PublicKey pub = KeyFactory.getInstance("RSA").generatePublic(
                new X509EncodedKeySpec(Base64.getDecoder().decode(stripPem(publicKeyBase64Der))));
        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        c.init(Cipher.ENCRYPT_MODE, pub);
        return Base64.getEncoder().encodeToString(c.doFinal(message.getBytes(StandardCharsets.UTF_8)));
    }

    static String rsaDecryptPkcs1Base64(String ciphertextB64, String privateKeyBase64Der) throws Exception {
        PrivateKey priv = KeyFactory.getInstance("RSA").generatePrivate(
                new PKCS8EncodedKeySpec(Base64.getDecoder().decode(stripPem(privateKeyBase64Der))));
        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        c.init(Cipher.DECRYPT_MODE, priv);
        return new String(c.doFinal(Base64.getDecoder().decode(ciphertextB64)), StandardCharsets.UTF_8);
    }

    static String percentEncodePayload(String payload) {
        StringBuilder sb = new StringBuilder(payload.length() * 2);
        for (int i = 0; i < payload.length(); i++) {
            int cp = payload.codePointAt(i);
            if (Character.charCount(cp) == 2) i++;
            boolean unreserved = (cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')
                    || (cp >= '0' && cp <= '9') || cp == '-' || cp == '.' || cp == '_' || cp == '~';
            if (unreserved) sb.appendCodePoint(cp);
            else for (byte b : new String(Character.toChars(cp)).getBytes(StandardCharsets.UTF_8))
                sb.append(String.format("%%%02X", b & 0xff));
        }
        return sb.toString();
    }

    private static String stripPem(String b64) {
        return b64.replace("-----BEGIN RSA PRIVATE KEY-----", "")
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END RSA PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replace("\r", "").replace("\n", "").trim();
    }
}
