package com.jeequan.jeepay.thirdparty.channel.yspay.utils;

import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.MechanismKit;
import cfca.sadk.algorithm.sm2.SM2PublicKey;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.org.bouncycastle.util.encoders.Base64Kit;
import cfca.sadk.util.EnvelopeUtil;
import cfca.sadk.util.KeyUtil;
import cfca.sadk.util.Signature;
import cfca.sadk.x509.certificate.X509Cert;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;


/**
 * @类名称：GMSignUtil
 * @类描述：国密算法SM2签名工具类
 * @作者：zhanggy
 * @日期：2021年8月11日
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GMSignUtils {

	/**
	 * 国密算法数据加密(SM2公钥加密：长度通常限定为136字节内)
	 * @param src
	 * @param data
	 * @throws Exception
	 */
	public static String encryptData(String src, String data) throws Exception {
		String encryptMsg = null;
		GMCertInfo certInfo = getVerifyCertInfo(src);
		try {
			final String deviceName = JCrypto.JSOFT_LIB;
			JCrypto.getInstance().initialize(deviceName, null);
			final Session session = JCrypto.getInstance().openSession(deviceName);
			// 一个或者多个加密证书
			X509Cert[] recvcerts = new X509Cert[] { certInfo.getX509Cert() };
			byte[] base64Bytes = EnvelopeUtil.envelopeMessage(data.getBytes("UTF-8"), Mechanism.SM4_ECB, recvcerts, session);
			encryptMsg = new String(base64Bytes);
		} catch (Exception e) {
			throw new Exception("数据SM2加密失败");
		}
		return encryptMsg;
	}

	/**
	 * 国密算法数据加密(SM2公钥加密：长度通常限定为136字节内)
	 * @param data
	 * @throws Exception
	 */
	public static String encryptDataStr(String crtStr,String data) throws Exception {
		String encryptMsg = null;
		GMCertInfo certInfo = getVerifyCertInfoStr(crtStr);
		try {
			final String deviceName = JCrypto.JSOFT_LIB;
			JCrypto.getInstance().initialize(deviceName, null);
			final Session session = JCrypto.getInstance().openSession(deviceName);
			// 一个或者多个加密证书
			X509Cert[] recvcerts = new X509Cert[] { certInfo.getX509Cert() };
			byte[] base64Bytes = EnvelopeUtil.envelopeMessage(data.getBytes("UTF-8"), Mechanism.SM4_ECB, recvcerts, session);
			encryptMsg = new String(base64Bytes);
		} catch (Exception e) {
			throw new Exception("数据SM2加密失败");
		}
		return encryptMsg;
	}

	/**
	 * 获取支付平台公钥证书
	 * @param src
	 * @return
	 * @throws Exception
	 */
	public static GMCertInfo getVerifyCertInfo(String src) throws Exception {
		GMCertInfo certInfo = new GMCertInfo();
		try{
			certInfo.ReadPublicKeyFromX509CertificateSM2(src);
		} catch (Exception e) {
			throw new Exception("银盛平台公钥证书加载失败");
		}

		return certInfo;
	}

	/**
	 * 获取支付平台公钥证书
	 * @param pubKey
	 * @return
	 * @throws Exception
	 */
	public static GMCertInfo getVerifyCertInfoStr(String pubKey) throws Exception {
		GMCertInfo certInfo = new GMCertInfo();
		try{
			certInfo.ReadPublicKeyFromX509CertificateSM2Str(pubKey);
		} catch (Exception e) {
			throw new Exception("银盛平台公钥证书加载失败");
		}

		return certInfo;
	}

	/**
	 * 加载公钥证书
	 * @return
	 *
	 */
	public synchronized static GMCertInfo initValidateCert(byte[] bs,String certId) {
		GMCertInfo certInfo = new GMCertInfo();
		try {
			certInfo.ReadPublicKeyFromX509CertificateSM2(bs);
			return certInfo;
		} catch (Exception e) {
			log.info("银盛平台公钥证书加载失败", e);
		}
		return certInfo;
	}

	/**
	 * 国密算法验证签名（报文）
	 * @param certInfo
	 * @param bcheck
	 * @param xmlMsg
	 * @throws Exception
	 */
	public static boolean verifyMsgSignSM2(GMCertInfo certInfo, byte[] bcheck, byte[] xmlMsg){
		boolean bFlag;
		try{
			SM2PublicKey pubKey = certInfo.getPubKeySM2();
			final String deviceName = JCrypto.JSOFT_LIB;
			JCrypto.getInstance().initialize(deviceName, null);
			Session session = JCrypto.getInstance().openSession(deviceName);
			final Signature util = new Signature();
			final String signAlg = MechanismKit.SM3_SM2;//SM3WithSM2
			// 校验，必须指定签名算法
			bFlag = util.p1VerifyMessage(signAlg,xmlMsg,bcheck,pubKey,session);
		} catch (Exception e) {
			bFlag = false;
			log.info("国密验签失败", e);
		}
		return bFlag;
	}

	/**
	 * 获取商户私钥证书
	 * @param src
	 * @return
	 * @throws Exception
	 */
	public synchronized static GMCertInfo getSignCertInfo(String src) throws Exception {
		//商户私钥证书
		String certfile = src;
		log.info("证书路经---"+certfile);
		GMCertInfo certInfo = new GMCertInfo();
		try {
			byte[] priKeySm2 = YspayKit.readFile(certfile);
			certInfo.setPriKey(priKeySm2);
		} catch (Exception e) {
			throw new Exception("商户签名证书加载失败");
		}

		return certInfo;
	}

	/**
	 * 获取商户私钥证书
	 * @param priKeyStr
	 * @return
	 * @throws Exception
	 */
	public synchronized static GMCertInfo getSignCertInfoStr(String priKeyStr) throws Exception {
		//商户私钥证书
		GMCertInfo certInfo = new GMCertInfo();
		try {
			byte[] priKeySm2 = Base64Kit.decode(priKeyStr);
			certInfo.setPriKey(priKeySm2);
		} catch (Exception e) {
			throw new Exception("商户签名证书加载失败");
		}

		return certInfo;
	}

	/**
	 * 国密算法签名（报文）
	 * @param sm2FilePath
	 * @param msg
	 * @return 返回 256长度base64编码
	 * @throws Exception
	 */
	public synchronized static String signMsgSM2(String sm2FilePath,String passWord, String msg) throws Exception {
		GMCertInfo certInfo = getSignCertInfo(sm2FilePath);
		// 返回的是256位
		byte[] signed;
		try {
			final String deviceName = JCrypto.JSOFT_LIB;
			JCrypto.getInstance().initialize(deviceName, null);
			Session session = JCrypto.getInstance().openSession(deviceName);
			PrivateKey priKey = KeyUtil.getPrivateKeyFromSM2(certInfo.getPriKey(),passWord);
//			PrivateKey priKey = KeyUtil.getPrivateKeyFromSM2(certInfo.getPriKey(),"ys123456");
			byte[] sourceData = msg.getBytes();
			Signature util = new Signature();
			String signAlg = Mechanism.SM3_SM2;//SM3WithSM2
			// 签名，必须指定签名算法，返回BASE64签名结果
			signed = util.p1SignMessage(signAlg, sourceData, priKey, session);
		} catch (Exception e) {
			throw new Exception("报文SM2签名失败");
		}
		String checkValue = new String(signed);
		return String.format("%-256s", checkValue);
	}

	/**
	 * 国密算法签名（报文）
	 * @param msg
	 * @return 返回 256长度base64编码
	 * @throws Exception
	 */
	public synchronized static String signMsgSM2Str(String priKeyStr,String passWord, String msg) throws Exception {
		GMCertInfo certInfo = getSignCertInfoStr(priKeyStr);
		// 返回的是256位
		byte[] signed;
		try {
			final String deviceName = JCrypto.JSOFT_LIB;
			JCrypto.getInstance().initialize(deviceName, null);
			Session session = JCrypto.getInstance().openSession(deviceName);
			PrivateKey priKey = KeyUtil.getPrivateKeyFromSM2(certInfo.getPriKey(),passWord);
//			PrivateKey priKey = KeyUtil.getPrivateKeyFromSM2(certInfo.getPriKey(),"ys123456");
			byte[] sourceData = msg.getBytes();
			Signature util = new Signature();
			String signAlg = Mechanism.SM3_SM2;//SM3WithSM2
			// 签名，必须指定签名算法，返回BASE64签名结果
			signed = util.p1SignMessage(signAlg, sourceData, priKey, session);
		} catch (Exception e) {
			throw new Exception("报文SM2签名失败");
		}
		String checkValue = new String(signed);
		return String.format("%-256s", checkValue);
	}

	public static String getSignDataStr1(Map<String, String> map) {
		List<String> keys = new ArrayList<>(map.keySet());
		Collections.sort(keys);
		StringBuilder sb = new StringBuilder();
		for(String key : keys){
			if("sign".equals(key)) {
				continue;
			}
			sb.append(key).append("=");
			sb.append(map.get(key));
			sb.append("&");
		}
		if(sb.length() > 0) {
			sb.setLength(sb.length() - 1);
		}
		return sb.toString();
	}
}
