package com.cyksj.util;

import com.cyksj.common.exception.AesException;
import com.cyksj.common.util.Codec;
import com.cyksj.common.util.corp.PKCS7Encoder;
import com.cyksj.common.util.corp.SHA1;
import com.cyksj.config.corp.WeChatCorpFactory;
import com.cyksj.model.dto.WxCorpEncryptDto;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.Arrays;

/*
 *项目名: netflix
 *文件名: WxCorpUtil
 *创建者: JavaZou
 *创建时间:2022/10/13 14:37
 * 企业微信操作工具类
 */
public class WxCorpUtil {

	private static Charset CHARSET = Charset.forName("utf-8");

	private byte[] aesKey;

	private String token;

	private String receiveid;

	/**
	 * 构造函数
	 *
	 * @param token          企业微信后台，开发者设置的token
	 * @param encodingAesKey 企业微信后台，开发者设置的EncodingAESKey
	 * @param receiveid,     不同场景含义不同，详见文档
	 */
	public WxCorpUtil(String token, String encodingAesKey, String receiveid) throws AesException {
		if (encodingAesKey.length() != 43) {
			throw new AesException(AesException.IllegalAesKey);
		}

		this.token = token;
		this.receiveid = receiveid;
		aesKey = Codec.DoBase64.custom().setData(encodingAesKey + "=").decode();
	}

	/**
	 * @param msgSignature 企业微信加密签名 msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
	 * @param timestamp    时间戳
	 * @param nonce        随机数
	 * @param echoStr      加密的字符串 需要解密得到消息内容明文，解密后有random、msg_len、msg、receiveid四个字段，其中msg即为消息内容明文
	 * @return
	 */
	public String VerifyURL(String msgSignature, String timestamp, String nonce, String echoStr) throws AesException {
		if (!checkCorpSign(msgSignature, timestamp, nonce, echoStr)) {
			throw new AesException(AesException.ValidateSignatureError);
		}
		return decrypt(echoStr);
	}

	/**
	 * 检验消息的真实性，并且获取解密后的明文.
	 * <ol>
	 * 	<li>利用收到的密文生成安全签名，进行签名验证</li>
	 * 	<li>若验证通过，则提取xml中的加密消息</li>
	 * 	<li>对消息进行解密</li>
	 * </ol>
	 *
	 * @param msgSignature 签名串，对应URL参数的msg_signature
	 * @param timeStamp    时间戳，对应URL参数的timestamp
	 * @param nonce        随机串，对应URL参数的nonce
	 * @return 解密后的原文
	 * @throws AesException 执行失败，请查看该异常的错误码和具体的错误信息
	 */
	public String DecryptMsg(String msgSignature, String timeStamp, String nonce, WxCorpEncryptDto corpEncryptDto)
			throws Exception {
		// 验证安全签名
		if (!checkCorpSign(msgSignature, timeStamp, nonce, corpEncryptDto.getEncrypt())) {
			throw new AesException(AesException.ValidateSignatureError);
		}

		// 解密
		return decrypt(corpEncryptDto.getEncrypt());
	}

	public Boolean checkCorpSign(String msgSignature, String timestamp, String nonce, String echoStr) throws AesException {
		String signature = SHA1.getSHA1(token, timestamp, nonce, echoStr);
		return signature.equals(msgSignature);
	}

	/**
	 * 对密文进行解密.
	 *
	 * @param text 需要解密的密文
	 * @return 解密得到的明文
	 * @throws AesException aes解密失败
	 */
	String decrypt(String text) throws AesException {
		byte[] original;
		try {
			// 设置解密模式为AES的CBC模式
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
			IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
			cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);

			// 使用BASE64对密文进行解码
			byte[] encrypted = Codec.DoBase64.custom().setData(text).decode();

			// 解密
			original = cipher.doFinal(encrypted);
		} catch (Exception e) {
			throw new AesException(AesException.DecryptAESError);
		}

		String xmlContent, from_receiveid;
		try {
			// 去除补位字符
			byte[] bytes = PKCS7Encoder.decode(original);

			// 分离16位随机字符串,网络字节序和receiveid
			byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);

			int xmlLength = recoverNetworkBytesOrder(networkOrder);

			xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
			from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
					CHARSET);
		} catch (Exception e) {
			e.printStackTrace();
			throw new AesException(AesException.IllegalBuffer);
		}

		// receiveid不相同的情况
		if (WeChatCorpFactory.getCorpConfigStrategy(from_receiveid) == null) {
			throw new AesException(AesException.ValidateCorpidError);
		}
		return xmlContent;

	}

	// 还原4个字节的网络字节序
	int recoverNetworkBytesOrder(byte[] orderBytes) {
		int sourceNumber = 0;
		for (int i = 0; i < 4; i++) {
			sourceNumber <<= 8;
			sourceNumber |= orderBytes[i] & 0xff;
		}
		return sourceNumber;
	}
}
