package com.usky.vpp.crypto;

import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import javax.crypto.Cipher;

/**
 * 运管平台 UN/DN 国密工具（SM2 加解密 + SM3withSM2 签名/验签）。
 * <p>加解密优先使用 BC {@code Cipher("SM2")}（与联调 SM3Util 一致，C1C2C3）；签名/验签针对密文字节。</p>
 */
public final class VppUnSmCryptoUtil {

    private static final String EC = "EC";
    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
    private static final String SM2 = "SM2";
    private static final String SIGNATURE_ALGORITHM = "SM3withSM2";

    static {
        if (Security.getProvider(BC) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    private VppUnSmCryptoUtil() {
    }

    public static final class EncryptedPayload {
        private final String cipherBase64;
        private final String signBase64;

        public EncryptedPayload(String cipherBase64, String signBase64) {
            this.cipherBase64 = cipherBase64;
            this.signBase64 = signBase64;
        }

        public String getCipherBase64() {
            return cipherBase64;
        }

        public String getSignBase64() {
            return signBase64;
        }
    }

    /** DN 请求 UN：UN 公钥加密 + DN 私钥对密文字节签名。 */
    public static EncryptedPayload encryptAndSignRequest(String plainJson,
                                                         String unPublicKeyBase64,
                                                         String dnPrivateKeyBase64) throws Exception {
        byte[] cipherBytes = encryptByPublicKey(plainJson, unPublicKeyBase64);
        String cipherBase64 = Base64.getEncoder().encodeToString(cipherBytes);
        String signBase64 = signCipherBytes(cipherBytes, dnPrivateKeyBase64);
        return new EncryptedPayload(cipherBase64, signBase64);
    }

    /** DN 解析 UN 响应：UN 公钥验签密文字节 + DN 私钥解密。 */
    public static String verifyAndDecryptResponse(String cipherBase64,
                                                  String signBase64,
                                                  String unPublicKeyBase64,
                                                  String dnPrivateKeyBase64) throws Exception {
        byte[] cipherBytes = decodeBase64(cipherBase64);
        if (!verifyCipherBytes(cipherBytes, signBase64, unPublicKeyBase64)) {
            throw new IllegalStateException("sign verify fail");
        }
        return new String(decryptByPrivateKey(cipherBytes, dnPrivateKeyBase64), StandardCharsets.UTF_8);
    }

    /** 仅 SM2 解密（已验签场景）。 */
    public static String decryptByDnPrivateKey(String cipherBase64, String dnPrivateKeyBase64) throws Exception {
        byte[] cipherBytes = decodeBase64(cipherBase64);
        return new String(decryptByPrivateKey(cipherBytes, dnPrivateKeyBase64), StandardCharsets.UTF_8);
    }

    public static String signCipherBytes(byte[] cipherBytes, String privateKeyBase64) throws Exception {
        return sign(cipherBytes, privateKeyBase64);
    }

    public static boolean verifyCipherBytes(byte[] cipherBytes, String signBase64, String publicKeyBase64)
            throws Exception {
        return verify(cipherBytes, publicKeyBase64, signBase64);
    }

    private static String sign(byte[] data, String privateKeyBase64) throws Exception {
        try {
            PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC);
            signature.initSign(privateKey);
            signature.update(data);
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception ex) {
            SM2 sm2 = SmUtil.sm2(privateKeyBase64.trim(), null);
            return Base64.getEncoder().encodeToString(sm2.sign(data));
        }
    }

    private static boolean verify(byte[] data, String publicKeyBase64, String signBase64) throws Exception {
        try {
            PublicKey publicKey = loadPublicKey(publicKeyBase64);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC);
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(decodeBase64(signBase64));
        } catch (Exception ex) {
            SM2 sm2 = SmUtil.sm2(null, publicKeyBase64.trim());
            return sm2.verify(data, decodeBase64(signBase64));
        }
    }

    /** 与 SM3Util.encryptByPublicKey 一致：BC Cipher SM2。 */
    private static byte[] encryptByPublicKey(String data, String publicKeyBase64) throws Exception {
        Exception last = null;
        try {
            return encryptByPublicKeyCipher(data, publicKeyBase64);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            return encryptByPublicKeyEngine(data, publicKeyBase64, SM2Engine.Mode.C1C2C3);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            return encryptByPublicKeyEngine(data, publicKeyBase64, SM2Engine.Mode.C1C3C2);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            SM2 sm2 = SmUtil.sm2(null, publicKeyBase64.trim());
            return sm2.encrypt(data.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
        } catch (Exception ex) {
            last = ex;
        }
        throw last != null ? last : new IllegalStateException("SM2 加密失败");
    }

    /** 与 SM3Util.decryptByPrivateKey 一致，并兼容 C1C3C2 密文。 */
    private static byte[] decryptByPrivateKey(byte[] cipherBytes, String privateKeyBase64) throws Exception {
        Exception last = null;
        try {
            return decryptByPrivateKeyCipher(cipherBytes, privateKeyBase64);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            return decryptByPrivateKeyEngine(cipherBytes, privateKeyBase64, SM2Engine.Mode.C1C2C3);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            return decryptByPrivateKeyEngine(cipherBytes, privateKeyBase64, SM2Engine.Mode.C1C3C2);
        } catch (Exception ex) {
            last = ex;
        }
        try {
            SM2 sm2 = SmUtil.sm2(privateKeyBase64.trim(), null);
            return sm2.decrypt(cipherBytes, KeyType.PrivateKey);
        } catch (Exception ex) {
            last = ex;
        }
        throw last != null ? last : new IllegalStateException("SM2 解密失败");
    }

    private static byte[] encryptByPublicKeyCipher(String data, String publicKeyBase64) throws Exception {
        PublicKey publicKey = loadPublicKey(publicKeyBase64);
        Cipher cipher = Cipher.getInstance(SM2, BC);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }

    private static byte[] decryptByPrivateKeyCipher(byte[] cipherBytes, String privateKeyBase64) throws Exception {
        PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
        Cipher cipher = Cipher.getInstance(SM2, BC);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(cipherBytes);
    }

    private static byte[] encryptByPublicKeyEngine(String data, String publicKeyBase64, SM2Engine.Mode mode)
            throws Exception {
        PublicKey publicKey = loadPublicKey(publicKeyBase64);
        ECPublicKeyParameters publicKeyParameters =
                (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
        SM2Engine engine = new SM2Engine(mode);
        engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
        byte[] input = data.getBytes(StandardCharsets.UTF_8);
        return engine.processBlock(input, 0, input.length);
    }

    private static byte[] decryptByPrivateKeyEngine(byte[] cipherBytes, String privateKeyBase64, SM2Engine.Mode mode)
            throws Exception {
        PrivateKey privateKey = loadPrivateKey(privateKeyBase64);
        ECPrivateKeyParameters privateKeyParameters =
                (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
        SM2Engine engine = new SM2Engine(mode);
        engine.init(false, privateKeyParameters);
        return engine.processBlock(cipherBytes, 0, cipherBytes.length);
    }

    private static PublicKey loadPublicKey(String publicKeyBase64) throws Exception {
        byte[] keyBytes = decodeBase64(publicKeyBase64);
        KeyFactory keyFactory = KeyFactory.getInstance(EC, BC);
        return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
    }

    private static PrivateKey loadPrivateKey(String privateKeyBase64) throws Exception {
        byte[] keyBytes = decodeBase64(privateKeyBase64);
        KeyFactory keyFactory = KeyFactory.getInstance(EC, BC);
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
    }

    private static byte[] decodeBase64(String value) {
        return Base64.getDecoder().decode(value.trim());
    }
}
