package com.ruoyi.common.third.utils;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.third.cache.CacheUtil;
import com.ruoyi.common.third.constants.EncryptConstants;
import com.ruoyi.common.third.constants.StringPool;
import com.ruoyi.common.third.enums.EncryptExceptionEnum;
import com.ruoyi.common.third.exception.EncryptException;
import com.ruoyi.common.third.properties.SecureEncryptProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

public class EncryptUtils {


	public static void initSMKey() {
		// 生成SM2的密钥对
		String backedPriKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PRI_KEY, String.class);
		String backedPubKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PUB_KEY, String.class);

		String frontPriKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PRI_KEY, String.class);
		String frontPubKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PUB_KEY, String.class);
		if (StringUtils.isNotBlank(backedPriKey) && StringUtils.isNotBlank(backedPubKey) && StringUtils.isNotBlank(frontPriKey)
				&& StringUtils.isNotBlank(frontPubKey)) {
			return;
		}

		Map<String, String> keyMapBackEnd = SM2Util.generateKeyPair();
		Map<String, String> keyMapFronEnd = SM2Util.generateKeyPair();


		// 后台存的是前台的公钥，后台的私钥
		frontPubKey = keyMapFronEnd.get(SM2Util.PUBKEY);
		backedPriKey = keyMapBackEnd.get(SM2Util.PRIKEY);

		// 前台存的是后台的公钥，前台的私钥
		backedPubKey = keyMapBackEnd.get(SM2Util.PUBKEY);// 前台存的是后台的公钥
		frontPriKey = keyMapFronEnd.get(SM2Util.PRIKEY);

		CacheUtil.put(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PRI_KEY, backedPriKey);
		CacheUtil.put(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PUB_KEY, backedPubKey);

		CacheUtil.put(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PRI_KEY, frontPriKey);
		CacheUtil.put(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PUB_KEY, frontPubKey);
	}

	/*** 后台加密
	 *
	 * @param data
	 * @param secureEncryptProperties
	 * @return
	 * @describe 前台的公钥 加密
	 */
	public static Map<String, String> encryptData(String clientId, String data, SecureEncryptProperties secureEncryptProperties) {
		String frontPubKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PUB_KEY, String.class);
		String backPriKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PRI_KEY, String.class);
		EncryptException.throwBy(isAnyBlank(frontPubKey, backPriKey), EncryptExceptionEnum.ENCRYPT_KEY_NULL.getExceptionCode(), EncryptExceptionEnum.ENCRYPT_KEY_NULL.getMessage());

		// 返回数据加密处理
		// 1、生成随机SM4密钥、iv
		String sm4Key = SM4Util.getRandomKey();
		Boolean sm4UseCbc = secureEncryptProperties.getEnableEncryptCbcMode();
		String sm4Iv = sm4UseCbc ? SM4Util.getRandomKey() : StringPool.EMPTY;
		// 2、 SM4加密数据
		String encryptedData = sm4UseCbc ? SM4CbcUtil.encrypt(data, sm4Key, sm4Iv)
				: SM4Util.encrypt(data, sm4Key);
		// 3、SM2公钥对SM4密钥、iv加密
		String key = SM2Util.encrypt(sm4Key, frontPubKey);
		String iv = sm4UseCbc ? SM2Util.encrypt(sm4Iv, frontPubKey) : StringPool.EMPTY;

		// 4、SM3对加密后数据签名
		String sign = "";
		if (secureEncryptProperties.getEnabledEncryptSign()) {
			//4.1 生成摘要
			String summary = SM3Util.digest(sm4UseCbc ? encryptedData + key + iv : encryptedData + key);
			//4.2 使用back-pri-key 利用SM2对摘要进行签名
			sign = SM2Util.sign(clientId, backPriKey, summary);
			EncryptException.throwBy(ObjectUtils.isEmpty(sign), EncryptExceptionEnum.ENCRYPT_SIGN_EXCEPTION.getExceptionCode(), EncryptExceptionEnum.ENCRYPT_SIGN_EXCEPTION.getMessage());
		}
		// 5、 封装json
		Map<String, String> json = new HashMap<>(3);
		json.put(secureEncryptProperties.getData(), encryptedData);
		json.put(secureEncryptProperties.getSign(), sign);
		json.put(secureEncryptProperties.getKey(), key);
		if (sm4UseCbc) {
			json.put(secureEncryptProperties.getIv(), iv);
		}
		return json;
	}


	/**
	 * 后台解密
	 *
	 * @describe 后台私钥
	 */
	public static String decryptDataByInputStream(String clientId, InputStream inputMessage, SecureEncryptProperties secureEncryptProperties) {
		String requestBody = null;
		try {
			requestBody = StreamUtils.copyToString(inputMessage, Charset.forName("UTF-8"));
		} catch (IOException e) {
			e.printStackTrace();
			EncryptException.throwBy(true, EncryptExceptionEnum.ENCRYPT_IOEXCEPTION.getExceptionCode(), EncryptExceptionEnum.ENCRYPT_IOEXCEPTION.getMessage());
		}
		JSONObject json = JSONObject.parseObject(requestBody);
		String data = json.getString(secureEncryptProperties.getData());
		String sign = json.getString(secureEncryptProperties.getSign());
		String key = json.getString(secureEncryptProperties.getKey());
		String iv = json.getString(secureEncryptProperties.getIv());

		String frontPubKey = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_FRONT_PUB_KEY, String.class);
		String privateKeyBackend = CacheUtil.get(EncryptConstants.CACHE_ENCRYPT_NAME, EncryptConstants.CACHE_BACKEND_PRI_KEY, String.class);
		EncryptException.throwBy(isAnyBlank(frontPubKey, privateKeyBackend), EncryptExceptionEnum.ENCRYPT_KEY_NULL.getExceptionCode(), EncryptExceptionEnum.ENCRYPT_KEY_NULL.getMessage());
		// 1. 判断SM4加解密是使用CBC还是ECB模式
		Boolean sm4UseCbc = secureEncryptProperties.getEnableEncryptCbcMode();
		// 2. 判断签名是否正确
		if (secureEncryptProperties.getEnabledEncryptSign()) {
			// 2.1 对data+key或data+key+iv通过SM3生成摘要
			String summary = SM3Util.digest(sm4UseCbc ? data + key + iv : data + key);
			// 2.2 进行验签
			boolean result = SM2Util.verifySign(clientId, frontPubKey, summary, sign);
			EncryptException.throwBy(!result, EncryptExceptionEnum.ENCRYPT_SIGN_EXCEPTION.getExceptionCode(), EncryptExceptionEnum.ENCRYPT_SIGN_EXCEPTION.getMessage());
		}
		// 3. 使用后端私钥通过SM2解密算法获取SM4密钥
		String realKey = SM2Util.decrypt(key, privateKeyBackend);
		// 4. 根据是使用CBC还是ECB模式，利用SM4密钥进行传输数据解密
		String realBody;
		if (sm4UseCbc) {
			// 对iv进行解密
			String realIv = SM2Util.decrypt(iv, privateKeyBackend);
			realBody = SM4CbcUtil.decrypt(data, realKey, realIv);
		} else {
			realBody = SM4Util.decrypt(data, realKey);
		}
		// 如果解析后的数据中开头和结尾为”,需要去除,用于解决传参为String类型时，加密后前后会多一个”
		if(realBody.startsWith(StringPool.QUOTE) && realBody.endsWith(StringPool.QUOTE)){
			realBody = realBody.substring(1,realBody.length()-1);
		}
		return realBody;
	}

	/**
	 * 判断是否需要加解密
	 * */
	public static boolean enableEncrypt(HttpHeaders headers, SecureEncryptProperties properties) {
		if (ObjectUtils.isEmpty(headers)) {
			return false;
		}
		if (!headers.containsKey(properties.getHeaderEncrypt())) {
			return false;
		}
		String headersFirst = headers.getFirst(properties.getHeaderEncrypt());
		if (ObjectUtils.isEmpty(headersFirst) || Objects.equals(headersFirst, StringPool.ZERO)) {
			return false;
		}
		if (Objects.equals(headersFirst, StringPool.ONE)) {
			return true;
		}
		return false;
	}

	public static void main(String[] args) {

		String sm4Key = SM4Util.getRandomKey();
		System.out.println("SM4Key= "+ sm4Key);
		Map<String, String> keyMapBackEnd = SM2Util.generateKeyPair();
		Map<String, String> keyMapFronEnd = SM2Util.generateKeyPair();


		// 后台存的是前台的公钥，后台的私钥
		String frontPubKey = keyMapFronEnd.get(SM2Util.PUBKEY);
		String backedPriKey = keyMapBackEnd.get(SM2Util.PRIKEY);

		// 前台存的是后台的公钥，前台的私钥
		String backedPubKey = keyMapBackEnd.get(SM2Util.PUBKEY);// 前台存的是后台的公钥
		String frontPriKey = keyMapFronEnd.get(SM2Util.PRIKEY);

		System.out.println("frontPubKey=" + frontPubKey);
		System.out.println("backedPriKey=" + backedPriKey);
		System.out.println("backedPubKey=" + backedPubKey);
		System.out.println("frontPriKey=" + frontPriKey);
	}

	private static boolean isAnyBlank(final CharSequence... css) {
		if (ObjectUtils.isEmpty(css)) {
			return true;
		}
		return Stream.of(css).anyMatch(EncryptUtils::isBlank);
	}

	public static boolean isBlank(final CharSequence cs) {
		return !org.springframework.util.StringUtils.hasText(cs);
	}
}
