package com.jsowell.wxpay.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.jsowell.wxpay.common.WeChatPayParameter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


/**
 * 参考官网 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
 */
public class WechatPayUtils {
	/**
	 * 获取私钥。
	 *
	 * @param filename 私钥文件路径  (required)
	 * @return 私钥对象
	 */
	public static PrivateKey getPrivateKey(String filename) throws IOException {
		// System.out.println("filename:" + filename);
		String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
		try {
			String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
					.replace("-----END PRIVATE KEY-----", "")
					.replaceAll("\\s+", "");

			KeyFactory kf = KeyFactory.getInstance("RSA");
			return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("当前Java环境不支持RSA", e);
		} catch (InvalidKeySpecException e) {
			throw new RuntimeException("无效的密钥格式");
		}
	}

	/**
	 * 生成token 也就是生成签名
	 *
	 * @param method
	 * @param url
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static String getToken(String method, URL url, String body) throws Exception {
		String nonceStr = getNonceStr();
		long timestamp = System.currentTimeMillis() / 1000;
		String message = buildMessage(method, url, timestamp, nonceStr, body);
		String signature = sign(message.getBytes("utf-8"));

		return "WECHATPAY2-SHA256-RSA2048 " + "mchid=\"" + WeChatPayParameter.mchId + "\","
				+ "nonce_str=\"" + nonceStr + "\","
				+ "timestamp=\"" + timestamp + "\","
				+ "serial_no=\"" + WeChatPayParameter.mchSerialNo + "\","
				+ "signature=\"" + signature + "\"";
	}

	/**
	 * 生成token
	 *
	 * @param method
	 * @param url
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static Map<String, Object> getTokenWeixin(String method, URL url, String body, String prepay_id) throws Exception {
		String nonceStr = getNonceStr();
		long timestamp = System.currentTimeMillis() / 1000;
		String message = buildMessage(method, url, timestamp, nonceStr, body);
		String signature = sign(message.getBytes("utf-8"));

		Map<String, Object> map = new HashMap<>();
		map.put("timeStamp", String.valueOf(timestamp));
		map.put("nonceStr", nonceStr);
		map.put("package", "prepay_id=" + prepay_id);
		map.put("signType", "RSA");
		map.put("paySign", signature);

		return map;
	}


	/**
	 * 生成签名
	 *
	 * @param message
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] message) throws Exception {
		Signature sign = Signature.getInstance("SHA256withRSA");
		sign.initSign(WeChatPayParameter.privateKey);
		sign.update(message);
		return Base64.getEncoder().encodeToString(sign.sign());
	}

	/**
	 * 生成签名串
	 *
	 * @param method
	 * @param url
	 * @param timestamp
	 * @param nonceStr
	 * @param body
	 * @return
	 */
	public static String buildMessage(String method, URL url, long timestamp, String nonceStr, String body) {
		String canonicalUrl = url.getPath();
		if (url.getQuery() != null) {
			canonicalUrl += "?" + url.getQuery();
		}
		return method + "\n"
				+ canonicalUrl + "\n"
				+ timestamp + "\n"
				+ nonceStr + "\n"
				+ body + "\n";
	}

	/**
	 * 生成随机数
	 *
	 * @return
	 */
	public static String getNonceStr() {
		return UUID.randomUUID().toString()
				.replaceAll("-", "")
				.substring(0, 32);
	}

	/**
	 * 获取平台证书
	 *
	 * @return
	 */
	public static Map<String, X509Certificate> refreshCertificate() throws Exception {
		Map<String, X509Certificate> certificateMap = new HashMap();
		// 1: 执行get请求
		JsonNode jsonNode = HttpUtils.doGet(WeChatPayParameter.certificatesUrl);
		// 2: 获取平台验证的相关参数信息
		JsonNode data = jsonNode.get("data");
		if (data != null) {
			for (int i = 0; i < data.size(); i++) {
				JsonNode encrypt_certificate = data.get(i).get("encrypt_certificate");
				//对关键信息进行解密
				AesUtil aesUtil = new AesUtil(WeChatPayParameter.v3Key.getBytes());
				String associated_data = encrypt_certificate.get("associated_data").toString().replaceAll("\"", "");
				String nonce = encrypt_certificate.get("nonce").toString().replaceAll("\"", "");
				String ciphertext = encrypt_certificate.get("ciphertext").toString().replaceAll("\"", "");
				//证书内容
				String certStr = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
				//证书内容转成证书对象
				CertificateFactory cf = CertificateFactory.getInstance("X509");
				X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
						new ByteArrayInputStream(certStr.getBytes("utf-8"))
				);
				String serial_no = data.get(i).get("serial_no").toString().replaceAll("\"", "");
				certificateMap.put(serial_no, x509Cert);
			}
		}
		return certificateMap;
	}

	/**
	 * 验证签名
	 *
	 * @param certificate
	 * @param message
	 * @param signature
	 * @return
	 */
	public static boolean verify(X509Certificate certificate, byte[] message, String signature) {
		try {
			Signature sign = Signature.getInstance("SHA256withRSA");
			sign.initVerify(certificate);
			sign.update(message);
			return sign.verify(Base64.getDecoder().decode(signature));
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
		} catch (SignatureException e) {
			throw new RuntimeException("签名验证过程发生了错误", e);
		} catch (InvalidKeyException e) {
			throw new RuntimeException("无效的证书", e);
		}
	}

	/**
	 * 参考网站 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
	 *
	 * @param appId
	 * @param prepay_id
	 * @return
	 * @throws IOException
	 * @throws SignatureException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static HashMap<String, Object> getTokenWeixin(String appId, String prepay_id) throws Exception {
		// 获取随机字符串
		String nonceStr = getNonceStr();
		// 获取微信小程序支付package
		String packagestr = "prepay_id=" + prepay_id;
		long timestamp = System.currentTimeMillis() / 1000;
		//签名，使用字段appId、timeStamp、nonceStr、package计算得出的签名值
		String message = buildMessageTwo(appId, timestamp, nonceStr, packagestr);
		//获取对应的签名
		String signature = sign(message.getBytes("utf-8"));
		// 组装返回
		HashMap<String, Object> map = new HashMap<>();
		map.put("timeStamp", String.valueOf(timestamp));
		map.put("nonceStr", nonceStr);
		map.put("package", packagestr);
		map.put("signType", "RSA");
		map.put("paySign", signature);
		return map;
	}

	private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
		return appId + "\n"
				+ timestamp + "\n"
				+ nonceStr + "\n"
				+ packag + "\n";
	}

}
