package com.liquidnet.service.dragon.channel.wepay.util;

import com.liquidnet.commons.lang.util.StringUtil;
import com.liquidnet.service.dragon.channel.wepay.config.WeixinConfigUtil;
import com.liquidnet.service.dragon.channel.wepay.req.WepayPrePayReq;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * @author AnJiabin <anjiabin@zhengzai.tv>
 * @version V1.0
 * @Description: TODO
 * @class: WepayUtil
 * @Package com.liquidnet.service.dragon.channel.wepay.util
 * @Copyright: LightNet @ Copyright (c) 2021
 * @date 2021/7/9 15:20
 */
@Slf4j
public class WepayUtil {    
    private static final Logger logger = LoggerFactory.getLogger(WepayUtil.class);

    private WepayUtil() {

    }

    /**
     * 微信被扫(扫码设备)
     *
     * @param outTradeNo
     * @param body
     * @param totalAmount
     * @param spbillCreateIp
     * @param authCode
     * @return
     */
    public static Map<String, Object> micropay(String outTradeNo, String body, BigDecimal totalAmount, String spbillCreateIp, String authCode) {
        String nonce_str = getnonceStr();
        Integer total_fee = totalAmount.multiply(BigDecimal.valueOf(100L)).intValue();

        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("appid", WeixinConfigUtil.appId);
        paramMap.put("mch_id", WeixinConfigUtil.mch_id);
        paramMap.put("nonce_str", nonce_str);
        paramMap.put("sign_type", "MD5");
        paramMap.put("body", body);
        paramMap.put("out_trade_no", outTradeNo);
        paramMap.put("total_fee", total_fee);
        paramMap.put("fee_type", "CNY");
        paramMap.put("spbill_create_ip", spbillCreateIp);
        paramMap.put("auth_code", authCode);
        paramMap.put("sign", getSign(paramMap, WeixinConfigUtil.partnerKey));
        String data = mapToXml(paramMap);
        logger.info("微信条码请求报文:{}", data);
        Map<String, Object> resultMap = httpXmlRequest("https://api.mch.weixin.qq.com/pay/micropay", "POST", data);
        logger.info("微信条码返回报文:{}", resultMap);
        if (resultMap == null || resultMap.isEmpty()) {
            return null;
        }
        SortedMap<String, Object> responseMap = new TreeMap<>();
        responseMap.putAll(resultMap);
        String resultSign = getSign(responseMap, WeixinConfigUtil.partnerKey);
        if (resultSign.equals(resultMap.get("sign"))) {
            resultMap.put("verify", "YES");
        } else {
            logger.info("返回报文验签失败,返回报文签名:{},返回签名:{}", resultSign, resultMap.get("sign"));
            resultMap.put("verify", "NO");
        }
        return resultMap;
    }

    /**
     * 小程序支付
     *
     * @return
     */
    public static Map<String, Object> appletPay(String outTradeNo, String body, BigDecimal totalAmount, String spbillCreateIp, String notifyUrl, String openid) {
        String nonce_str = getnonceStr();
        Integer totalFee = totalAmount.multiply(BigDecimal.valueOf(100L)).intValue();
        String tradeType = "JSAPI";

        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("appid", WeixinConfigUtil.xAppId);
        paramMap.put("mch_id", WeixinConfigUtil.xMchId);
        paramMap.put("nonce_str", nonce_str);
        paramMap.put("sign_type", "MD5");
        paramMap.put("body", body);
        paramMap.put("out_trade_no", outTradeNo);
        paramMap.put("total_fee", totalFee);
        paramMap.put("spbill_create_ip", spbillCreateIp);
        paramMap.put("notify_url", notifyUrl);
        paramMap.put("trade_type", tradeType);
        paramMap.put("openid", openid);
//        if (goodsDetails != null && !goodsDetails.isEmpty()) {
//            List<SortedMap<String, Object>> goodList = new ArrayList<>();
//            for (RoncooPayGoodsDetails goodsDetail : goodsDetails) {
//                SortedMap<String, Object> goodsDetailMap = new TreeMap<>();
//                goodsDetailMap.put("goods_id", goodsDetail.getGoodsId());
//                goodsDetailMap.put("quantity", goodsDetail.getNums());
//                goodsDetailMap.put("goods_name", goodsDetail.getGoodsName());
//                goodsDetailMap.put("price", goodsDetail.getSinglePrice());
//                goodList.add(goodsDetailMap);
//            }
//            JSONObject goodsDetailJson = new JSONObject();
//            goodsDetailJson.put("goods_detail", goodList);
//            paramMap.put("detail", goodsDetailJson.toJSONString());
//        }
        paramMap.put("sign", getSign(paramMap, WeixinConfigUtil.xPayKey));
        String data = mapToXml(paramMap);
        logger.info("微信小程序统一下单，请求报文:{}", data);
        Map<String, Object> resultMap = httpXmlRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", data);
        logger.info("微信小程序统一下单，返回报文:{}", resultMap);
        if (resultMap == null || resultMap.isEmpty()) {
            return null;
        }
        SortedMap<String, Object> responseMap = new TreeMap<>();
        responseMap.putAll(resultMap);
        String resultSign = getSign(responseMap, WeixinConfigUtil.xPayKey);
        if (resultSign.equals(resultMap.get("sign"))) {
            resultMap.put("verify", "YES");
        } else {
            logger.info("返回报文验签失败,返回报文签名:{},返回签名:{}", resultSign, resultMap.get("sign"));
            resultMap.put("verify", "NO");
        }
        return resultMap;
    }

    /**
     * 生成随机字符串
     *
     * @return
     */
    public static String getnonceStr() {
        Random random = new Random();
        StringBuilder nonceStrBuilder = new StringBuilder();
        for (int i = 0; i < 31; i++) {
            nonceStrBuilder.append(random.nextInt(10));
        }
        return nonceStrBuilder.toString();
    }

    /**
     * 签名
     *
     * @param paramMap
     * @param key
     * @return
     */
    public static String getSign(SortedMap<String, Object> paramMap, String key) {
        StringBuilder signBuilder = new StringBuilder();
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            if (!"sign".equals(entry.getKey()) && !StringUtil.isEmpty(entry.getValue())) {
                signBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        signBuilder.append("key=").append(key);
        logger.info("微信待签名参数字符串:{}", signBuilder.toString());
        return MD5Util.encode(signBuilder.toString()).toUpperCase();
    }

    /**
     * 转xml格式
     *
     * @param paramMap
     * @return
     */
    public static String mapToXml(SortedMap<String, Object> paramMap) {
        StringBuilder dataBuilder = new StringBuilder("<xml>");
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            if (!StringUtil.isEmpty(entry.getValue())) {
                dataBuilder.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
            }
        }
        dataBuilder.append("</xml>");
        logger.info("Map转Xml结果:{}", dataBuilder.toString());
        return dataBuilder.toString();
    }

    /**
     * 发送xml数据,获取返回结果
     *
     * @param requestUrl
     * @param requestMethod
     * @param xmlStr
     * @return
     */
    public static Map<String, Object> httpXmlRequest(String requestUrl, String requestMethod, String xmlStr) {
        // 将解析结果存储在HashMap中
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            HttpsURLConnection urlCon = (HttpsURLConnection) (new URL(requestUrl)).openConnection();
            urlCon.setDoInput(true);
            urlCon.setDoOutput(true);
            // 设置请求方式（GET/POST）
            urlCon.setRequestMethod(requestMethod);

            if ("GET".equalsIgnoreCase(requestMethod)) {
                urlCon.connect();
            }

            urlCon.setRequestProperty("Content-Length", String.valueOf(xmlStr.getBytes().length));
            urlCon.setUseCaches(false);
            // 设置为gbk可以解决服务器接收时读取的数据中文乱码问题
            if (null != xmlStr) {
                OutputStream outputStream = urlCon.getOutputStream();
                outputStream.write(xmlStr.getBytes("UTF-8"));
                outputStream.flush();
                outputStream.close();
            }
            InputStream inputStream = urlCon.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            // 读取输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStreamReader);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            @SuppressWarnings("unchecked")
            List<Element> elementList = root.elements();
            // 遍历所有子节点
            for (Element e : elementList) {
                map.put(e.getName(), e.getText());
            }
            log.info("微信查询返回结果:"+map.toString());
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            urlCon.disconnect();
        } catch (MalformedURLException e) {
            log.error(e.getMessage());
        } catch (IOException e) {
            log.error(e.getMessage());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return map;
    }

    /**
     * 生成预支付XML
     *
     * @param WepayPrePayReq
     * @param partnerKey
     * @return
     */
    public static String getPrePayXml(WepayPrePayReq WepayPrePayReq, String partnerKey) {

        getPrePaySign(WepayPrePayReq, partnerKey);// 生成预支付请求签名

        StringBuilder sb = new StringBuilder();
        sb.append("<xml><appid>").append(WepayPrePayReq.getAppid()).append("</appid>");
        sb.append("<body>").append(WepayPrePayReq.getBody()).append("</body>");
        sb.append("<device_info>").append(WepayPrePayReq.getDeviceInfo()).append("</device_info>");
        sb.append("<mch_id>").append(WepayPrePayReq.getMchId()).append("</mch_id>");
        sb.append("<nonce_str>").append(WepayPrePayReq.getNonceStr()).append("</nonce_str>");
        sb.append("<notify_url>").append(WepayPrePayReq.getNotifyUrl()).append("</notify_url>");
//        if (WeiXinTradeTypeEnum.NATIVE.name().equals(WepayPrePayReq.getTradeType())) {
//            sb.append("<product_id>").append(WepayPrePayReq.getProductId()).append("</product_id>");
//        } else if (WeiXinTradeTypeEnum.JSAPI.name().equals(WepayPrePayReq.getTradeType())) {
//            sb.append("<openid>").append(WepayPrePayReq.getOpenid()).append("</openid>");
//        }
        sb.append("<out_trade_no>").append(WepayPrePayReq.getOutTradeNo()).append("</out_trade_no>");
        sb.append("<spbill_create_ip>").append(WepayPrePayReq.getSpbillCreateIp()).append("</spbill_create_ip>");
        sb.append("<time_start>").append(WepayPrePayReq.getTimeStart()).append("</time_start>");
        sb.append("<time_expire>").append(WepayPrePayReq.getTimeExpire()).append("</time_expire>");
        sb.append("<total_fee>").append(WepayPrePayReq.getTotalFee()).append("</total_fee>");
//        sb.append("<trade_type>").append(WepayPrePayReq.getTradeType().name()).append("</trade_type>");
        sb.append("<sign>").append(WepayPrePayReq.getSign()).append("</sign>");
        sb.append("</xml>");

        return sb.toString();
    }

    /**
     * 微信拼接签名参数
     *
     * @param appid       公众账号ID
     * @param mch_id      商户号
     * @param device_info 设备号
     * @param trade_type  交易类型
     * @param prePay      预支付返回Map
     * @param partnerKey  签名EY
     * @return
     */
    public static String geWeiXintPrePaySign(String appid, String mch_id, String device_info, String trade_type,
                                             Map<String, Object> prePay, String partnerKey) {
        Map<String, Object> preParams = new HashMap<String, Object>();
        if (!StringUtil.isEmpty(prePay.get("return_code"))) {
            preParams.put("return_code", prePay.get("return_code"));
        }
        if (!StringUtil.isEmpty(prePay.get("return_msg"))) {
            preParams.put("return_msg", prePay.get("return_msg"));
        }
        if (!StringUtil.isEmpty(prePay.get("appid"))) {
            preParams.put("appid", appid);
        }
        if (!StringUtil.isEmpty(prePay.get("mch_id"))) {
            preParams.put("mch_id", mch_id);
        }
        if (!StringUtil.isEmpty(prePay.get("device_info"))) {
            preParams.put("device_info", device_info);
        }
        if (!StringUtil.isEmpty(prePay.get("nonce_str"))) {
            preParams.put("nonce_str", prePay.get("nonce_str"));
        }
        if (!StringUtil.isEmpty(prePay.get("result_code"))) {
            preParams.put("result_code", prePay.get("result_code"));
        }
        if ("FAIL".equals(prePay.get("result_code"))) {
            if (!StringUtil.isEmpty(prePay.get("err_code"))) {
                preParams.put("err_code", prePay.get("err_code"));
            }
            if (!StringUtil.isEmpty(prePay.get("err_code_des"))) {
                preParams.put("err_code_des", prePay.get("err_code_des"));
            }
        }
        if (!StringUtil.isEmpty(prePay.get("trade_type"))) {
            preParams.put("trade_type", trade_type);
        }
        if (!StringUtil.isEmpty(prePay.get("prepay_id"))) {
            preParams.put("prepay_id", prePay.get("prepay_id"));
        }
        if (!StringUtil.isEmpty(prePay.get("code_url"))) {
            preParams.put("code_url", prePay.get("code_url"));
        }
        String argPreSign = getStringByMap(preParams) + "&key=" + partnerKey;
        String preSign = MD5Util.encode(argPreSign).toUpperCase();
        return preSign;
    }

    public static boolean notifySign(Map<String, String> result, String sign, String partnerKey) {
        String argNotifySign = getStringByStringMap(result) + "&key=" + partnerKey;
        String notifySign = MD5Util.encode(argNotifySign).toUpperCase();
        if (notifySign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取预支付请求签名
     *
     * @param WepayPrePayReq
     * @param partnerKey
     * @return
     */
    private static void getPrePaySign(WepayPrePayReq WepayPrePayReq, String partnerKey) {

        Map<String, Object> prePayMap = new HashMap<String, Object>();
        prePayMap.put("appid", WepayPrePayReq.getAppid());// 公众账号ID
        prePayMap.put("mch_id", WepayPrePayReq.getMchId()); // 商户号
        prePayMap.put("device_info", WepayPrePayReq.getDeviceInfo());
        prePayMap.put("nonce_str", WepayPrePayReq.getNonceStr()); // 随机字符串
        prePayMap.put("body", WepayPrePayReq.getBody()); // 商品描述
        prePayMap.put("out_trade_no", WepayPrePayReq.getOutTradeNo()); // 商户订单号
        prePayMap.put("total_fee", WepayPrePayReq.getTotalFee()); // 总金额
        prePayMap.put("spbill_create_ip", WepayPrePayReq.getSpbillCreateIp()); // 终端IP
        prePayMap.put("time_start", WepayPrePayReq.getTimeStart()); // 开始时间
        prePayMap.put("time_expire", WepayPrePayReq.getTimeExpire()); // 截止时间
        prePayMap.put("notify_url", WepayPrePayReq.getNotifyUrl()); // 接收财付通通知的URL
//        prePayMap.put("trade_type", WepayPrePayReq.getTradeType().name()); // 交易类型
//        if (WeiXinTradeTypeEnum.NATIVE.name().equals(WepayPrePayReq.getTradeType())) {
//            prePayMap.put("product_id", WepayPrePayReq.getProductId()); // 商品ID
//        } else if (WeiXinTradeTypeEnum.JSAPI.name().equals(WepayPrePayReq.getTradeType())) {
//            prePayMap.put("openid", WepayPrePayReq.getOpenid()); // openid
//        }

        String argPreSign = getStringByMap(prePayMap) + "&key=" + partnerKey;
        String preSign = MD5Util.encode(argPreSign).toUpperCase();
        WepayPrePayReq.setSign(preSign);
    }

    /**
     * 根据Map获取排序拼接后的字符串
     *
     * @param map
     * @return
     */
    public static String getStringByMap(Map<String, Object> map) {
        SortedMap<String, Object> smap = new TreeMap<String, Object>(map);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> m : smap.entrySet()) {
            sb.append(m.getKey()).append("=").append(m.getValue()).append("&");
        }
        sb.delete(sb.length() - 1, sb.length());
        return sb.toString();
    }

    public static String getStringByStringMap(Map<String, String> map) {
        SortedMap<String, Object> smap = new TreeMap<String, Object>(map);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> m : smap.entrySet()) {
            sb.append(m.getKey()).append("=").append(m.getValue()).append("&");
        }
        sb.delete(sb.length() - 1, sb.length());
        return sb.toString();
    }

    /**
     * 解析微信发来的请求（XML）
     *
     * @param inputStream
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> parseXml(InputStream inputStream) throws Exception {

        if (inputStream == null) {
            return null;
        }

        Map<String, String> map = new HashMap<String, String>();// 将解析结果存储在HashMap中
        SAXReader reader = new SAXReader();// 读取输入流
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();// 得到xml根元素
        List<Element> elementList = root.elements();// 得到根元素的所有子节点
        for (Element e : elementList) { // 遍历所有子节点
            map.put(e.getName(), e.getText());
        }

        inputStream.close(); // 释放资源
        inputStream = null;

        return map;
    }

    /**
     * 订单查询
     *
     * @param outTradeNo
     * @return
     */
    public static Map<String, Object> orderQuery(String outTradeNo, String appId, String MchId, String partnerKey) {
        Random random = new Random();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", appId);
        paramMap.put("mch_id", MchId);
        String nonce_str = "";
        for (int i = 0; i < 31; i++) {
            nonce_str += random.nextInt(10);
        }
        paramMap.put("nonce_str", nonce_str);
        paramMap.put("out_trade_no", outTradeNo);
        String signStr = getStringByMap(paramMap) + "&key=" + partnerKey;
        paramMap.put("sign", MD5Util.encode(signStr).toUpperCase());
        Set<String> ks = paramMap.keySet();
        StringBuilder sb = new StringBuilder("<xml>");
        for (String key : ks) {
            sb.append("<" + key + ">" + paramMap.get(key) + "</" + key + ">");
        }
        sb.append("</xml>");
        Map<String, Object> resultMap = httpXmlRequest("https://api.mch.weixin.qq.com/pay/orderquery", "POST",
                sb.toString());
        return resultMap;
    }
}
