/*
 * @Description: 密签工具类
 * @Author: yeh
 * @Date: 2023-10-18 16:45:21
 * 
 * 用到的三方工具：
 * npm install jsrsasign --save
 * npm install sm-crypto --save
 */
'use strict';

// sm-crypto和jsRsaSign对象引用变量，由init方法初始化
let smCrypto = null;
let rsaSign = null;

/**
 * EncryptionUtil初始化函数。项目在调用EncryptionUtil前，应首先调用此函数进行初始化
 * @Author: liusheng
 * @Date: 2023-12-20 09:08:12
 * @param {object} smCryptoObj sm-crypto实例
 * @param {object} rsaSignObj jsrsasign实例
 */
function initEncryptionUtil(smCryptoObj, rsaSignObj) {
	smCrypto = smCryptoObj;
	rsaSign = rsaSignObj;
}

const EncryptionUtil = {
	/**
	 * @Description: SM4加密
	 * @Author: yeh
	 * @Date: 2023-10-31 22:12:12
	 * @param {string} plaintext 参数1：明文
	 * @param {string} encryptKey 参数2：密钥
	 * @return {string} 密文
	 */
	sm4Encrypt(plainText, encryptKey) {
		return smCrypto.sm4.encrypt(plainText, encryptKey);
	},
	
	/**
	 * @Description: RSA签名
	 * @Author: yeh
	 * @Date: 2023-10-31 22:12:12
	 * @param {string} text 参数1：待签名数据字符串
	 * @param {string} signatureKey 参数2：签名密钥
	 * @return {string} 签名后的Base64字符串
	 */
	rsaSignature(text, signatureKey) {
		// 因为后端提供的是pck#8的秘钥对，所以这里使用 KEYUTIL.getKey来解析秘钥
		let privateKey = rsaSign.KEYUTIL.getKey(signatureKey);
		// 创建Signature对象，设置签名编码算法
		let sig = new rsaSign.KJUR.crypto.Signature({
			"alg": "SHA1withRSA",				// alg:对应的是规则 需要和后端统一
			"prov": "cryptojs/jsrsa",
			"prvkeypem": signatureKey
		});
		sig.init(privateKey)
		sig.updateString(text);									// 传入待加密字符串
		return rsaSign.hextob64(sig.sign());		// 生成密文
	},
	
	/**
	 * @Description: 加密请求body，适用于application/json类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} body 待加密的请求体对象
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @return {object[]} 加密后对应的[请求头+密文字符串]
	 */
	encryptBody(body, encKeyName, encryptKey) {
		let encStr = this.sm4Encrypt(JSON.stringify(body), encryptKey);
		let header = this.buildEncryptHeaders(encKeyName);
		return [header, encStr];
	},
	
	/**
	 * @Description: 加密请求对象参数，适用于<对象数据>与<文件>同时上传的请求
	 * @Author: liusheng
	 * @Date: 2024-2-20 11:12:12
	 * @param {object} objParam 待加密的请求对象参数，格式为{'name':xxx, 'value': object}}。注：只加密value
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @return {object[]} 加密后对应的[请求头+密文字符串]
	 */
	encryptObjectParam(objParam, encKeyName, encryptKey) {
		let encStr = this.sm4Encrypt(JSON.stringify(objParam.value), encryptKey);
		let header = this.buildEncryptHeaders(encKeyName, null, objParam.name);
		return [header, encStr];
	},
	
	/**
	 * @Description: 加密全部请求参数，适用于multipart/form-data类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} params 待加密的请求参数
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @return {object[]} 加密后对应的[请求头+密文请求参数tianjianfwData]
	 */
	encryptAllParams(params, encKeyName, encryptKey) {
		let sortedParams = {};
		Object.keys(params).sort().map(key => {
			sortedParams[key] = (params[key] instanceof Array) ? params[key] : Array.of(params[key]);
		});
		let encStr = this.sm4Encrypt(JSON.stringify(sortedParams), encryptKey);
		let header = this.buildEncryptHeaders(encKeyName);
		return [header, {'tianjianfwData' : encStr}];
	},
	
	/**
	 * @Description: 加密指定请求参数，适用于multipart/form-data类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} params 待加密的请求参数
	 * @param {array} fieldList 指定的请求参数名列表
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @return {object[]} 加密后对应的[请求头+请求参数]
	 */
	encryptParamList(params, fieldList, encKeyName, encryptKey) {
		fieldList.forEach(name => {
		   params[name] = this.sm4Encrypt(params[name], encryptKey);
		});
		let header = this.buildEncryptHeaders(encKeyName, fieldList);
		return [header, params];
	},
	
	/**
	 * @Description: 签名请求body，适用于application/json类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object/string} body 待签名的请求体对象
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object} 签名后对应的请求头
	 */
	signatureBody(body, signKeyName, signKey) {
		let signature = this.rsaSignature(typeof(body) == 'string' ? 
			body : JSON.stringify(body), signKey);
		return this.buildSignatureHeaders(signKeyName, signature);
	},
	
	/**
	 * @Description: 签名对象参数，适用于<对象数据>与<文件>同时上传的请求
	 * @Author: liusheng
	 * @Date: 2024-2-20 11:12:12
	 * @param {object} objParam 待签名的对象参数，格式为:{'name':xxx, 'value': object}}。注：只签名value
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object} 签名后对应的请求头
	 */
	signatureObjectParam(objParam, signKeyName, signKey) {
		let signature = this.rsaSignature(JSON.stringify(objParam.value), signKey);
		return this.buildSignatureHeaders(signKeyName, signature, objParam.name);
	},

	/**
	 * @Description: 签名全部请求参数，适用于application/x-www-form-urlencoded和multipart/form-data类型的请求
	 * @Description: 签名的格式为“按参数名称升序排列，参数值转换为数组”，以和后端验签格式相一致
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} params 待签名的请求参数
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object} 密签后对应的请求头
	 */
	signatureParams(params, signKeyName, signKey) {
		let sortedParams = {};
		Object.keys(params).sort().map(key => {
			if (params[key] instanceof Array) {
				sortedParams[key] = [];
				for (let name in params[key]) {
					sortedParams[key].push(String(params[key][name]));
				}
			} else {
				sortedParams[key] = Array.of(String(params[key]));
			}
		});
		let signature = this.rsaSignature(JSON.stringify(sortedParams), signKey);
		return this.buildSignatureHeaders(signKeyName, signature);
	},
	
	/**
	 * @Description: 密签请求body，适用于application/json类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} body 待密签的请求体对象
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object[]} 密签后对应的[请求头+密文字符串]
	 */
	encryptAndSignBody(body, encKeyName, encryptKey, signKeyName, signKey) {
		let [header1, encStr] = this.encryptBody(body, encKeyName, encryptKey);
		let header2 = this.signatureBody(encStr, signKeyName, signKey);
		return [Object.assign(header1, header2), encStr];
	},
	
	/**
	 * @Description: 密签请求的对象参数，适用于<对象数据>与<文件>同时上传的请求
	 * @Author: liusheng
	 * @Date: 2024-2-20 11:12:12
	 * @param {object} objParam 待密签的对象参数，格式为{'name':xxx, 'value': object}}。注：只密签value
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object[]} 密签后对应的[请求头+密文字符串]
	 */
	encryptAndSignObjectParam(objParam, encKeyName, encryptKey, signKeyName, signKey) {
		let [header1, encStr] = this.encryptObjectParam(objParam, encKeyName, encryptKey);
		objParam.value = encStr;
		let header2 = this.signatureObjectParam(objParam, signKeyName, signKey);
		return [Object.assign(header1, header2), objParam];
	},
	
	/**
	 * @Description: 密签全部请求参数，适用于application/x-www-form-urlencoded和multipart/form-data类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} params 待密签的请求参数
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object[]} 密签后对应的[请求头+密文请求参数]
	 */
	encryptAndSignAllParams(params, encKeyName, encryptKey, signKeyName, signKey) {
		let [header1, encdParams] = this.encryptAllParams(params, encKeyName, encryptKey);
		let header2 = this.signatureParams(encdParams, signKeyName, signKey);
		return [Object.assign(header1, header2), encdParams];
	},
	
	/**
	 * @Description: 密签指定请求参数，适用于multipart/form-data类型的请求
	 * @Author: liusheng
	 * @Date: 2023-11-7 16:12:12
	 * @param {object} params 待密签的请求参数
	 * @param {array} fieldList 指定的请求参数名列表
	 * @param {string} encKeyName 加密密钥的名称
	 * @param {string} encryptKey 加密密钥串
	 * @param {string} signKeyName 签名密钥的名称
	 * @param {string} signKey 签名密钥串
	 * @return {object[]} 密签后对应的[请求头+密文请求参数]
	 */
	encryptAndSignParamList(params, fieldList, encKeyName, encryptKey, signKeyName, signKey) {
		let [header1, encdParams] = this.encryptParamList(params, fieldList, encKeyName, encryptKey);
		let header2 = this.signatureParams(encdParams, signKeyName, signKey);
		return [Object.assign(header1, header2), encdParams];
	},
	
	buildEncryptHeaders(encKeyName, encFields = null, objectField) {
		let header = {
			'Time-Stamp': new Date().getTime(),
			'Encryption-Key-Name': encKeyName,
			'Encryption-Type': (!encFields) ? 'enc-all' : 'enc-fields',
		};
		
		if (encFields) {
			header['Encryption-Fields'] = encFields.toString();
		}
		
		if (objectField) {
			header['Es-Object-Field'] = objectField;
		}
		return header;
	},
	
	buildSignatureHeaders(signKeyName, signature, objectField = null) {
		let header = {
			'Time-Stamp': new Date().getTime(),
			'Enable-Sign': 'true',
			'Signature-Key-Name': signKeyName,
			'Signature': signature
		};

		if (objectField) {
			header['Es-Object-Field'] = objectField;
		}
		return header;
	}
};

export {initEncryptionUtil, EncryptionUtil}