package cn.pluss.platform.leshua;


import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author yuchen
 * 
 * @Description 
 */
@Slf4j
public class LeshuaSignature {

    private static final String SECRET_KEY = "key";
	
	/**
	 * 计算签名
	 * @param paramMap
	 * @param exludedSignParams 计算签名排除的参数
	 * @param key
	 * @return
	 */
    public static String getMD5Sign(Map<String,String> paramMap, Set<String> exludedSignParams, String key){
		if (paramMap == null || paramMap.isEmpty()) {
			log.warn("待签名参数map为空!");
			return null;
		}
		String str2Sign = buildParam4Sign(paramMap, exludedSignParams, key);
		String result = DigestUtils.md5Hex(str2Sign).toUpperCase();
		log.info("MD5签名原始串：{}，签名结果：{}", new Object[] { str2Sign, result });
		return result;
    }
    
    /**
	 * 计算签名(做encode处理)
	 * @param paramMap
	 * @param exludedSignParams 计算签名排除的参数
	 * @param key
	 * @return
     * @throws UnsupportedEncodingException 
	 */
    public static String getRequestMD5Sign(Map<String,String> paramMap, Set<String> exludedSignParams, String key) throws UnsupportedEncodingException{
		if (paramMap == null || paramMap.isEmpty()) {
			log.warn("待签名参数map为空!");
			return null;
		}
		String str2Sign = buildRequestParam4Sign(paramMap, exludedSignParams, key);
		String result = DigestUtils.md5Hex(str2Sign).toUpperCase();
		log.info("MD5签名原始串：{}，签名结果：{}", new Object[] { str2Sign, result });
		return result;
    }

    /**
     * 从API返回的XML数据里面重新计算一次签名
     * @param responseString
     * @param exludedSignParams
     * @param key
     * @return
     * @throws DocumentException
     */
    public static String getSignFromResponseString(String responseString, Set<String> exludedSignParams, String key) throws DocumentException {
        Map<String,String> map = getMapFromXML(responseString);
        //将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        return LeshuaSignature.getMD5Sign(map, exludedSignParams, key);
    }
    /**
     * 检查api返回数据的签名
     * @param responseString
     * @param exludedSignParams
     * @param key
     * @return
     * @throws DocumentException 
     * @throws UnsupportedEncodingException 
     */
	public static boolean checkIsSignValidFromResponseString(String responseString, Set<String> exludedSignParams, String key)
			throws DocumentException, UnsupportedEncodingException {
		Map<String, String> map = getMapFromXML(responseString);
		String signFromAPIResponse = map.get("sign");
		if (StringUtils.isEmpty(signFromAPIResponse)) {
			log.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		log.info("服务器回包里面的签名是：{}", signFromAPIResponse);
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		String signForAPIResponse = LeshuaSignature.getMD5Sign(map, exludedSignParams, key);

		if (!signForAPIResponse.equalsIgnoreCase(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			log.error("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		log.info("恭喜，API返回的数据签名验证通过!");
		return true;
	}
	
	private static String buildRequestParam4Sign(Map<String, String> paramMap, Set<String> exludedSignParams, String md5Key) throws UnsupportedEncodingException {
		Set<String> keySet = paramMap.keySet();
		StringBuilder param = new StringBuilder(20 * keySet.size());
		String[] keys = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keys);
		for (String key : keys) {
			if (exludedSignParams != null && exludedSignParams.contains(key)) {
				continue;
			}
			Object value = paramMap.get(key);
			// 排除值为null的情况
			if (value != null) {
				param.append(URLEncoder.encode(key,"UTF-8")).append("=").append(URLEncoder.encode(String.valueOf(value),"UTF-8")).append("&");
			}
		}
		param.append(URLEncoder.encode(SECRET_KEY,"UTF-8")).append("=").append(URLEncoder.encode(md5Key,"UTF-8"));
		return param.toString();
	}
    
    /**
     * 拼接用于签名的参数
     * @param paramMap
     * @param exludedSignParams
     * @param md5Key
     * @return
     */
	private static String buildParam4Sign(Map<String, String> paramMap, Set<String> exludedSignParams, String md5Key) {
		Set<String> keySet = paramMap.keySet();
		StringBuilder param = new StringBuilder(20 * keySet.size());
		String[] keys = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keys);
		for (String key : keys) {
			if (exludedSignParams != null && exludedSignParams.contains(key)) {
				continue;
			}
			Object value = paramMap.get(key);
			// 排除值为null的情况
			if (value != null) {
				param.append(key).append("=").append(value).append("&");
			}
		}
		param.append(SECRET_KEY).append("=").append(md5Key);
		return param.toString();
	}
	
	/**
	 * 将XML字符串转换成map
	 * @param xmlString
	 * @return
	 * @throws DocumentException 
	 * @throws ParserConfigurationException
	 * @throws IOException
	 * @throws SAXException
	 */
	public static Map<String, String> getMapFromXML(String xmlString) throws DocumentException  {
		// 这里用dom的方式解析回包的最主要目的是防止API新增回包字段
		org.dom4j.Document document = DocumentHelper.parseText(xmlString);
		org.dom4j.Element root = document.getRootElement();
		List<?> eleList = root.elements();
		if (eleList != null) {
			Map<String, String> map = new HashMap<String, String>(eleList.size());
			for (Object obj : eleList) {
				if (!(obj instanceof Element)) {
					log.warn("节点[{}]不是Element，忽略！", obj);
					continue;
				}
				Element ele = (Element) obj;
				map.put(ele.getName(), ele.getText());
			}
			return map;
		}
		return Collections.emptyMap();
		
	}
	
	
	/**
	 * 计算base64签名，用于商户进件等
	 * @param agentKey
	 * @param data
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String getBase64Sign(String agentKey, JSONObject data) throws UnsupportedEncodingException {
		StringBuilder sb = new StringBuilder();
		sb.append("lepos").append(agentKey).append(data);
		return Base64.encodeBase64String(DigestUtils.md5Hex(sb.toString()).getBytes("utf-8"));
	}

	/**
	 *
	 * 响应结果不参与计算签名的参数
	 * @return
	 */
	public static Set<String> resExcludedSignParams() {
		Set<String> exludedSignParams = new HashSet<String>(2);
		exludedSignParams.add("sign");
		exludedSignParams.add("resp_code");
		return exludedSignParams;
	}


	/**
	 *
	 * 响应结果不参与计算签名的参数
	 * @return
	 */
	public static Set<String> notifyExcludedSignParams() {
		Set<String> exludedSignParams = new HashSet<String>(2);
		exludedSignParams.add("sign");
		exludedSignParams.add("error_code");
		return exludedSignParams;
	}

}

