package com.yida.data.common.core.utils;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.yida.data.common.core.common.ResultMsgType;
import com.yida.data.common.core.entity.constant.ConstructionPayConstant;
import com.yida.data.common.core.entity.pay.ConstructionPayOrder;
import com.yida.data.common.core.entity.smart.ConstructionPayNotifyDTO;
import com.yida.data.common.core.entity.system.ConstructionPayConfig;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.construction.RSASig;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

/**
 * 建设银行二维码支付工具类
 *
 * @author ZYJ
 * @date 2023/6/19 15:10
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ConstructionPayUtil {

    /**
     * 二维码下单
     *
     * @param config   建行学校信息配置
     * @param payOrder 建行支付订单类
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/6/20 11:18
     */
    public static String getQrPayUrl(ConstructionPayConfig config, ConstructionPayOrder payOrder) {
        // 封装参数
        Map<String, Object> map = Maps.newLinkedHashMap();
        // 商户代码
        map.put("MERCHANTID", config.getMerchantId());
        // 商户柜台代码
        map.put("POSID", config.getPosId());
        // 分行代码
        map.put("BRANCHID", config.getBranchId());
        // 订单号
        map.put("ORDERID", payOrder.getOrderNo());
        // 支付金额. 单位: 元
        map.put("PAYMENT", payOrder.getPayMoney());
        // 币种. 缺省为 01－人民币（只支持人民币支付）
        map.put("CURCODE", "01");
        // 交易码
        map.put("TXCODE", ConstructionPayConstant.QR_PAY_CODE);
        map.put("REMARK1", "");
        map.put("REMARK2", "");
        // H5支付
        map.put("RETURNTYPE", "3");
        // 订单超时时间. yyyyMMddHHmmss
        map.put("TIMEOUT", DateUtil.format(DateUtil.offsetMinute(payOrder.getOrderTime(), 15), DatePattern.PURE_DATETIME_PATTERN));
        // 加密串
        String paramsStr = Joiner.on("&").withKeyValueSeparator("=").join(map);
        String md5Str = paramsStr + "&PUB=" + config.getPublicKey().substring(config.getPublicKey().length() - 30);
        log.info("加密串: {}", md5Str);
        // MD5加密
        String md5 = DigestUtil.md5Hex(md5Str.getBytes());
        paramsStr = paramsStr + "&MAC=" + md5;

        String s = HttpUtil.get(ConstructionPayConstant.QR_PAY_URL + "&" + paramsStr);
        log.info("H5接口返回值: {}", s);
        // 接口
        JSONObject response = JSONUtil.parseObj(s);
        log.info("建行二维码下单请求参数: {}, 返回值: {}", map, response);
        if (!response.containsKey(ConstructionPayConstant.SUCCESS_KEY) ||
                !ConstructionPayConstant.SUCCESS_CODE.equals(response.getStr(ConstructionPayConstant.SUCCESS_KEY))) {
            log.error("建行二维码下单失败");
            throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
        }
        // 获取真实支付地址
        JSONObject realResponse = JSONUtil.parseObj(HttpUtil.get(response.getStr(ConstructionPayConstant.PAY_URL)));
        log.info("获取建行二维码支付实际地址返回值: {}", realResponse);
        if (!realResponse.containsKey(ConstructionPayConstant.SUCCESS_KEY) ||
                !ConstructionPayConstant.SUCCESS_CODE.equals(realResponse.getStr(ConstructionPayConstant.SUCCESS_KEY))) {
            log.error("获取建行二维码支付实际地址失败");
            throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
        }
        return URLDecoder.decode(realResponse.getStr(ConstructionPayConstant.QR_URL), CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 订单查询接口
     *
     * @param config  建行学校信息配置
     * @param orderNo 订单号
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/6/20 16:03
     */
    public static JSONObject queryOrder(ConstructionPayConfig config, String orderNo) {
        try {
            // 封装参数
            Map<String, Object> map = Maps.newLinkedHashMap();
            // 商户代码
            map.put("MERCHANTID", config.getMerchantId());
            // 分行代码
            map.put("BRANCHID", config.getBranchId());
            // 商户柜台代码
            map.put("POSID", config.getPosId());
            // 订单日期
            map.put("ORDERDATE", "");
            // 开始时间
            map.put("BEGORDERTIME", "");
            // 截止时间
            map.put("ENDORDERTIME", "");
            // 订单号
            map.put("ORDERID", orderNo);
            // 密码. 参与加密但不赋值, 加密完成后赋值
            map.put("QUPWD", "");
            // 交易码
            map.put("TXCODE", ConstructionPayConstant.QUERY_ORDER_CODE);
            // 流水类型. 0-支付流水, 1-退款流水
            map.put("TYPE", "0");
            // 流水状态. 0-未结算流水, 1-已结算流水
            map.put("KIND", "0");
            // 交易状态. 0-失败, 1-成功, 2-不确定, 3-全部（已结算流水查询不支持全部）
            map.put("STATUS", "1");
            // 查询方式s 1-页面形式, 2-文件返回形式 (提供 TXT 和 XML 格式文件的下载), 3-XML页面形式
            map.put("SEL_TYPE", "3");
            // 页码
            map.put("PAGE", "1");
            // 操作员
            map.put("OPERATOR", "");
            // 预留字段
            map.put("CHANNEL", "");
            // 加密串
            String paramsStr = Joiner.on("&").withKeyValueSeparator("=").join(map);
            log.info("加密串: {}", paramsStr);
            // MD5加密
            String md5 = DigestUtil.md5Hex(paramsStr.getBytes());
            map.put("QUPWD", config.getPrivateCode());
            map.put("MAC", md5);
            // 接口
            String xmlStr = HttpUtil.get(ConstructionPayConstant.URL, map);
            log.info("建行查询订单接口xmlStr: {}", xmlStr);
            // xml转json
            JSONObject response = xmlToJson(xmlStr);
            log.info("建行查询订单接口请求参数: {}, 返回值: {}", map, response);

            if (response.containsKey(ConstructionPayConstant.RETURN_CODE_NAME)
                    && !ConstructionPayConstant.RETURN_SUCCESS_CODE.equals(response.getStr(ConstructionPayConstant.RETURN_CODE_NAME))
                    && !ConstructionPayConstant.NO_ORDER_CODE.equals(response.getStr(ConstructionPayConstant.RETURN_CODE_NAME))
            ) {
                log.error("建行查询订单接口失败，code:{},msg:{},params:{}", response.get(ConstructionPayConstant.RETURN_CODE_NAME),
                        response.get(ConstructionPayConstant.RETURN_MSG), JSONUtil.toJsonStr(map));
                throw new FebsException(ResultMsgType.ORDER_QUERY_FAIL.getValue());
            }

            return response;
        } catch (Exception e) {
            log.error("建行查询订单接口失败", e);
            throw new FebsException(ResultMsgType.ORDER_QUERY_FAIL.getValue());
        }
    }

    /**
     * 生成订单号
     * 规则: 最长30位,  {商户代码后9位}{时间(yyyyMMddmmHHssSSS)(17位)}{4位随机数}
     *
     * @param merchantId 商户代码
     * @return java.lang.String
     * @author ZYJ
     * @date 2022/6/16 14:32
     */
    public static String getOrderNumber(String merchantId) {
        return merchantId.substring(merchantId.length() - 9) +
                DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + RandomUtil.randomNumbers(4);
    }

    /**
     * xml字符串转json
     *
     * @param xmlStr xml字符串
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/6/20 14:40
     */
    public static JSONObject xmlToJson(String xmlStr) {
        JSONObject jsonObject = JSONUtil.createObj();
        if (StrUtil.isEmpty(xmlStr)) {
            throw new IllegalArgumentException("xml is empty");
        } else {
            // 去除xml无效字符
            xmlStr = cleanXml(xmlStr);
            Document document = XmlUtil.parseXml(xmlStr);
            Element element = document.getDocumentElement();
            if (element != null) {
                NodeList nodeList = element.getChildNodes();
                for (int i = 0; i < nodeList.getLength(); i++) {
                    Node node = nodeList.item(i);
                    getChildNodesInfo(node, jsonObject);
                }
            }
        }
        return jsonObject;
    }

    /**
     * 递归查询所有子节点信息
     *
     * @param rootNode   根节点
     * @param jsonObject 返回的json数据
     * @author ZYJ
     * @date 2023/6/20 15:55
     */
    public static void getChildNodesInfo(Node rootNode, JSONObject jsonObject) {
        NodeList nodeList = rootNode.getChildNodes();
        int length = nodeList.getLength();
        if (length <= 1) {
            String nodeName = rootNode.getNodeName();
            // 去除无效字符
            if ("#text".equalsIgnoreCase(nodeName)) {
                return;
            }
            String nodeText = rootNode.getTextContent();
            jsonObject.set(nodeName, nodeText);
        } else {
            JSONObject childObject = JSONUtil.createObj();
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                getChildNodesInfo(node, childObject);
            }
            jsonObject.set(rootNode.getNodeName(), childObject);
        }
    }

    /**
     * 去除xml无效字符
     *
     * @param xmlStr xml字符串
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/6/20 14:36
     */
    public static String cleanXml(String xmlStr) {
        return XmlUtil.cleanInvalid(xmlStr.trim()
                .replaceAll("\n\t", "")
                .replaceAll("\n", "")
                .replaceAll("\t", "")
                .replaceAll("\t\t", "")
        );
    }

    public static boolean validateSign(ConstructionPayNotifyDTO dto, String sign, String publicKey) {
        try {
            // 封装验签参数
            Map<String, Object> map = Maps.newLinkedHashMap();
            map.put("POSID", dto.getPOSID());
            map.put("BRANCHID", dto.getBRANCHID());
            map.put("ORDERID", dto.getORDERID());
            map.put("PAYMENT", dto.getPAYMENT());
            map.put("CURCODE", dto.getCURCODE());
            map.put("REMARK1", dto.getREMARK1());
            map.put("REMARK2", dto.getREMARK2());
            map.put("ACC_TYPE", dto.getACC_TYPE());
            map.put("SUCCESS", dto.getSUCCESS());
            map.put("TYPE", dto.getTYPE());
            map.put("REFERER", dto.getREFERER());
            map.put("CLIENTIP", dto.getCLIENTIP());
            String paramsStr = Joiner.on("&").withKeyValueSeparator("=").join(map);
            // TODO 2023/7/28 添加第三方包后可验签成功, 此处需要处理spring-cloud项目如何添加本地jar包
            RSASig rsaSig = new RSASig();
            rsaSig.setPublicKey(publicKey);
            return rsaSig.verifySignature(sign, paramsStr);
        } catch (Exception e) {
            log.error("验签失败", e);
            return false;
        }
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        ConstructionPayConfig config = ConstructionPayConfig.builder()
                .merchantId("105000348168188")
                .posId("078024457")
                .branchId("430000000")
                .publicKey("30819c300d06092a864886f70d010101050003818a003081860281804e9b9f299771a0c16f2e5d66e0dee653b172ab9cad1ce32053ed1db1af4249cd4475ac5a2e4a92baf8d015e56a8fa7e3df6432a0c952af8cb79d4a0171bef382468dcdf0f149e3a6a1c72aabad028c8c6a2d9e0fd85cadd908a1ce5ba5047d3dd40c49115a4833194086574f74ce1f106eed451e41c10cdec7bb69c90294ecf5020111")
                .privateCode("Jh123456")
                .build();
        ConstructionPayOrder order = ConstructionPayOrder.builder()
                .orderNo("2023072814460000001")
                .payMoney(new BigDecimal("0.01"))
                .orderTime(new Date())
                .build();
        System.out.println(getQrPayUrl(config, order));
//        queryOrder(config, "9823740924298749823742");
//        String sign = "2275646a3c214e250fd03fc21c50f9c50b22b43fcf74c22db0946647ae70ed7950fdad20d172cd411999bddccb70f25d7d4104c4e8b8c372b900e842a8c6d688cfc77362e9261154b473b6b621b15f4770f48a5fcd8f0776ac335a522e5c830d3d5002c9969aca2810c2664df3d1b90539095247c5bcd05dbaa710bb4b7fbdd0";
//        String paramStr = "POSID=075570790&BRANCHID=430000000&ORDERID=2023072814460000001&PAYMENT=0.01&CURCODE=01&REMARK1=&REMARK2=&ACC_TYPE=WX&SUCCESS=Y&TYPE=1&REFERER=&CLIENTIP=11.168.99.30";
//        String publicKey = "30819c300d06092a864886f70d010101050003818a003081860281804e9b9f299771a0c16f2e5d66e0dee653b172ab9cad1ce32053ed1db1af4249cd4475ac5a2e4a92baf8d015e56a8fa7e3df6432a0c952af8cb79d4a0171bef382468dcdf0f149e3a6a1c72aabad028c8c6a2d9e0fd85cadd908a1ce5ba5047d3dd40c49115a4833194086574f74ce1f106eed451e41c10cdec7bb69c90294ecf5020111";
//
//
//        RSASig rsaSig = new RSASig();
////        rsaSig.setPublicKey(publicKey.substring(publicKey.length() - 30));
//        rsaSig.setPublicKey(publicKey);
//        System.out.println(rsaSig.verifySignature(sign, paramStr));
    }
}
