package com.bsphp.login;

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;

/**
 * BSPHP 协议：MD5、AES-128-CBC（key=iv 取 UTF-8 前 16 字节）、RSA PKCS#1。
 */
public final class BSPHPCrypto {

    private BSPHPCrypto() {}

    public 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();
    }

    public static String aes128CbcEncryptBase64(String plaintext, String key16) throws Exception {
        byte[] keyBytes = key16.getBytes(StandardCharsets.UTF_8);
        if (keyBytes.length < 16) {
            byte[] padded = new byte[16];
            System.arraycopy(keyBytes, 0, padded, 0, keyBytes.length);
            keyBytes = padded;
        } else if (keyBytes.length > 16) {
            byte[] cut = new byte[16];
            System.arraycopy(keyBytes, 0, cut, 0, 16);
            keyBytes = cut;
        }
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec iv = new IvParameterSpec(keyBytes);
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        byte[] enc = c.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(enc);
    }

    public static String aes128CbcDecryptBase64ToString(String ciphertextB64, String key16) throws Exception {
        byte[] ct = Base64.getDecoder().decode(ciphertextB64);
        byte[] keyBytes = key16.getBytes(StandardCharsets.UTF_8);
        if (keyBytes.length < 16) {
            byte[] padded = new byte[16];
            System.arraycopy(keyBytes, 0, padded, 0, keyBytes.length);
            keyBytes = padded;
        } else if (keyBytes.length > 16) {
            byte[] cut = new byte[16];
            System.arraycopy(keyBytes, 0, cut, 0, 16);
            keyBytes = cut;
        }
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec iv = new IvParameterSpec(keyBytes);
        Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(Cipher.DECRYPT_MODE, keySpec, iv);
        byte[] out = c.doFinal(ct);
        return new String(out, StandardCharsets.UTF_8);
    }

    public static String rsaEncryptPkcs1Base64(String message, String publicKeyBase64Der) throws Exception {
        PublicKey pub = loadPublicKey(publicKeyBase64Der);
        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        c.init(Cipher.ENCRYPT_MODE, pub);
        byte[] enc = c.doFinal(message.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(enc);
    }

    public static String rsaDecryptPkcs1Base64(String ciphertextB64, String privateKeyBase64Der) throws Exception {
        PrivateKey priv = loadPrivateKey(privateKeyBase64Der);
        byte[] ct = Base64.getDecoder().decode(ciphertextB64);
        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        c.init(Cipher.DECRYPT_MODE, priv);
        byte[] dec = c.doFinal(ct);
        return new String(dec, StandardCharsets.UTF_8);
    }

    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();
    }

    public static PublicKey loadPublicKey(String publicKeyBase64Der) throws Exception {
        String b64 = stripPem(publicKeyBase64Der);
        byte[] der = Base64.getDecoder().decode(b64);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(der);
        return KeyFactory.getInstance("RSA").generatePublic(spec);
    }

    public static PrivateKey loadPrivateKey(String privateKeyBase64Der) throws Exception {
        String b64 = stripPem(privateKeyBase64Der);
        byte[] der = Base64.getDecoder().decode(b64);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
        return KeyFactory.getInstance("RSA").generatePrivate(spec);
    }

    /** Percent-encode like Swift allowed .alphanumerics + "-._~" */
    public 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++;
            if (isUnreserved(cp)) {
                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 boolean isUnreserved(int cp) {
        return (cp >= 'A' && cp <= 'Z')
                || (cp >= 'a' && cp <= 'z')
                || (cp >= '0' && cp <= '9')
                || cp == '-' || cp == '.' || cp == '_' || cp == '~';
    }
}
