package net.yyc.common.ding.utils;

import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
public class SignUtils {

	private static String CHAR_SET = "UTF-8";

	public SignUtils() {
	}

	private static String fromStream(InputStream is, String charset) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
		StringBuffer buffer = new StringBuffer();

		String line = "";
		while ((line = in.readLine()) != null) {
			buffer.append(line);
		}

		return new String(buffer.toString().getBytes("UTF-8"), charset);
	}


	/**
	 * 签名算法HmacSha256
	 */
	public static final String HMAC_SHA256 = "HmacSHA256";

	/**
	 * 编码
	 */
	public static final String ENCODING = "UTF-8";
	/**
	 * 换⾏符
	 */
	private static char LF = '\n';

	private static final String HTTP_METHOD_POST = "post";
	private static final String HTTP_METHOD_PUT = "put";

	/**
	 * 获取请求签名
	 *
	 * @param uri        原始HTTP请求PATH（例：/oapi/v1/employ/freelances/check）
	 * @param httpMethod 原始HTTP请求方法（例：POST）
	 * @param paramsMap  原始HTTP请求所有Query+Form参数（POST请求：Maps.newHashMap()）
	 * @param body       原始HTTP请求Body体（仅当请求为POST/PUT且非表单请求才需要设置此属性,表单形式的需要将参数放到paramsMap中）
	 * @param appSecret      用户id
	 * @return 签名结果
	 * @throws Exception
	 */
//	public static String serviceSign(DingConfig config, String httpMethod,
//									 Map<String, Object> paramsMap, byte[] body) throws Exception {
//		String bodyMd5 = buildBodyMd5(httpMethod, body);
//		String resourceToSign = buildResource(config.getQueryUrl(), paramsMap);
//		String stringToSign = buildStringToSign(resourceToSign,
//				httpMethod, bodyMd5);
//		Mac hmacSha256 = Mac.getInstance(HMAC_SHA256);
//		String appSecret = config.getAppSecret();
//		byte[] keyBytes = appSecret.getBytes(ENCODING);
//		hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.
//				length, HMAC_SHA256));
//		return new String(Base64.encodeBase64(hmacSha256.doFinal
//				(stringToSign.getBytes(ENCODING))), ENCODING);
//	}


	public static String serviceSign(String uri,String appSecret, String httpMethod, Map<String, Object> paramsMap, byte[] body) throws Exception {
		String bodyMd5 = buildBodyMd5(httpMethod, body);
		String resourceToSign = buildResource(uri, paramsMap);
		String stringToSign = buildStringToSign(resourceToSign,
				httpMethod, bodyMd5);

		Mac hmacSha256 = Mac.getInstance(HMAC_SHA256);
//		String appSecret = "5T8134269D467IGa0ZC7s907666r2C43";
		byte[] keyBytes = appSecret.getBytes(ENCODING);
		hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.
				length, HMAC_SHA256));
		return new String(Base64.encodeBase64(hmacSha256.doFinal
				(stringToSign.getBytes(ENCODING))), ENCODING);
	}


	public static String buildBodyMd5(String httpMethod, byte[] body) throws IOException {
		if (body == null) {
			return null;
		}
		if (!httpMethod.equalsIgnoreCase(HTTP_METHOD_POST) && !httpMethod.equalsIgnoreCase(HTTP_METHOD_PUT)) {
			return null;
		}
		InputStream inputStream = new ByteArrayInputStream(body);
		byte[] bodyBytes = IOUtils.toByteArray(inputStream);
		if (bodyBytes != null && bodyBytes.length > 0) {
			return base64AndMD5(bodyBytes).trim();
		}
		return null;
	}

	public static String base64AndMD5(byte[] bytes) {
		try {
			final MessageDigest md = MessageDigest.getInstance(
					"MD5");
			md.reset();
			md.update(bytes);
			final Base64 base64 = new Base64();
			return new String(base64.encode(md.digest()));
		} catch (final NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("unknown algorith m MD5");
		}
	}

	public static String buildResource(String uri, Map<String, Object> paramsMap) {
		StringBuilder builder = new StringBuilder();
		builder.append(uri);
		TreeMap<String, Object> sortMap = new TreeMap<>();
		sortMap.putAll(paramsMap);
		if (sortMap.size() > 0) {
			builder.append('?');
			builder.append(buildMapToSign(sortMap));
		}
		return builder.toString();
	}

	public static String buildStringToSign(String resourceToSign, String method, String bodyMd5) {
		StringBuilder sb = new StringBuilder();
		sb.append(method).append(LF);
		if (StringUtils.isNotBlank(bodyMd5)) {
			sb.append(bodyMd5);
		}
		sb.append(LF);
		sb.append(resourceToSign);
		return sb.toString();
	}

	/**
	 * 将Map转换为用&及=拼接的字符串
	 */
	public static String buildMapToSign(Map<String, Object> paramMap) {
		StringBuilder builder = new StringBuilder();

		for (Map.Entry<String, Object> e : paramMap.entrySet()) {
			if (builder.length() > 0) {
				builder.append('&');
			}
			String key = e.getKey();
			Object value = e.getValue();
			if (value != null) {
				if (value instanceof List) {
					List list = (List) value;
					if (list.size() == 0) {
						builder.append(key);
					} else {
						builder.append(key).append("=").append(String.valueOf(list.get(0)));
					}
				} else if (value instanceof Object[]) {
					Object[] objs = (Object[]) value;
					if (objs.length == 0) {
						builder.append(key);
					} else {
						builder.append(key).append("=").append(String.valueOf(objs[0]));
					}
				} else {
					builder.append(key).append("=").append(String.valueOf(value));
				}
			}
		}
		return builder.toString();
	}

	/**
	 * 加签（人力家方加签）
	 * @param privateKey  人力家私钥
	 * @param data  请求data
	 * @return
	 */
	public static String sign(String privateKey, String data){
		try {
			byte[] result = RsaCoreUtil.SHA1_SIGN_RSA_ENCRY.sign(data.getBytes(CHAR_SET), privateKey);
			return Base64.encodeBase64String(result);
		} catch (UnsupportedEncodingException e) {
			log.info("sign with exception, e : " + e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 验签
	 * @param rsaPublicKey 人力家公钥（人力家提供）
	 * @param refData 请求data
	 * @param sign 请求sign
	 * @return
	 */
	public static boolean verify(String rsaPublicKey, String refData, String sign) {
		try {
			return RsaCoreUtil.SHA1_SIGN_RSA_ENCRY.verify(refData.getBytes("UTF-8"),
					Base64.decodeBase64(sign), rsaPublicKey);
		} catch (UnsupportedEncodingException e) {
			log.info("verify with exception, e : ", e);
			throw new RuntimeException("verify exception");
		}
	}

}
