package com.czg.third.wechat;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.PayCst;
import com.czg.exception.CzgException;
import com.czg.pay.*;
import com.czg.third.wechat.dto.config.WechatPayConfigDto;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.Signer;
import com.wechat.pay.java.core.util.NonceUtil;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * @author yjjie
 * @date 2025/12/26 09:15
 */
@Slf4j
public class WechatPayManager {

    private static final String PAY_SUCCESS = "SUCCESS";
    private static final String BAR_PAY_URL = "https://api.mch.weixin.qq.com/pay/micropay";

    /**
     * jsapi 小程序支付
     *
     * @param configDto 配置
     * @param paramsDto 参数
     */
    public static Map<String, Object> jsapiPay(WechatPayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) {
        if (configDto == null) {
            configDto = WechatPayConfigDto.getDefaultConfig();
        }

        try {
            JSONObject reqData = new JSONObject();
            reqData.put("sp_appid", paramsDto.getSubAppid());
            reqData.put("sp_mchid", configDto.getMerchantId());
            reqData.put("sub_mchid", merchantDTO.getWechatMerchantId());
            reqData.put("description", paramsDto.getSubject());
            reqData.put("out_trade_no", paramsDto.getMchOrderNo());
            reqData.put("notify_url", paramsDto.getNotifyUrl() + "/" + PayCst.Type.WECHAT);
            reqData.put("attach", paramsDto.getExtParam());

            JSONObject amount = new JSONObject();
            amount.put("total", paramsDto.getAmount());
            reqData.put("amount", amount);

            JSONObject payer = new JSONObject();
            payer.put("sp_openid", paramsDto.getUserId());
            reqData.put("payer", payer);

            String string = WechatReqUtils.postReq(configDto, "/v3/pay/partner/transactions/jsapi", reqData.toJSONString());
            log.info("微信支付请求结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), string);

            JSONObject object = JSONObject.parseObject(string);

            long timestamp = Instant.now().getEpochSecond();
            String nonceStr = NonceUtil.createNonce(32);
            String packageVal = "prepay_id=" + object.getString("prepay_id");
            String message =
                    paramsDto.getSubAppid() + "\n" + timestamp + "\n" + nonceStr + "\n" + packageVal + "\n";

            Config config = WechatConfig.getRsaConfig(configDto);
            Signer signer = config.createSigner();
            String sign = signer.sign(message).getSign();

            JSONObject res = new JSONObject();
            res.put("appId", paramsDto.getSubAppid());
            res.put("timeStamp", String.valueOf(timestamp));
            res.put("nonceStr", nonceStr);
            res.put("package", packageVal);
            res.put("signType", "RSA");
            res.put("paySign", sign);
            log.info("微信支付签名结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), res);
            return res;
        } catch (Exception e) {
            log.error("微信支付异常: orderNo = {}, e = {}", paramsDto.getMchOrderNo(), e.getMessage());
            throw new CzgException("微信支付异常");
        }
    }

    /**
     * 条码支付
     * 用户付款码规则：18位纯数字，前缀以10、11、12、13、14、15开头
     *
     * @param configDto 配置
     * @param paramsDto 参数
     */
    public static Map<String, Object> barPay(WechatPayConfigDto configDto, CzgPayBaseReq paramsDto, NativeMerchantDTO merchantDTO) {
        try {
            if (configDto == null) {
                configDto = WechatPayConfigDto.getDefaultConfig();
            }

            String nonceStr = NonceUtil.createNonce(32);

            Map<String, Object> payMap = new HashMap<>();
            Document document = DocumentHelper.createDocument();

            // 添加根元素
            Element xml = document.addElement("xml");

            addElementIfNotEmpty(xml, "appid", paramsDto.getSubAppid());
            payMap.put("appid", paramsDto.getSubAppid());
            addElementIfNotEmpty(xml, "mch_id", configDto.getMerchantId());
            payMap.put("mch_id", configDto.getMerchantId());
            addElementIfNotEmpty(xml, "sub_mch_id", merchantDTO.getWechatMerchantId());
            payMap.put("sub_mch_id", merchantDTO.getWechatMerchantId());
            addElementIfNotEmpty(xml, "nonce_str", nonceStr);
            payMap.put("nonce_str", nonceStr);
            addElementIfNotEmpty(xml, "body", paramsDto.getSubject());
            payMap.put("body", paramsDto.getSubject());
            addElementIfNotEmpty(xml, "out_trade_no", paramsDto.getMchOrderNo());
            payMap.put("out_trade_no", paramsDto.getMchOrderNo());
            addElementIfNotEmpty(xml, "total_fee", paramsDto.getAmount());
            payMap.put("total_fee", paramsDto.getAmount());
            addElementIfNotEmpty(xml, "spbill_create_ip", paramsDto.getClientIp());
            payMap.put("spbill_create_ip", paramsDto.getClientIp());
            addElementIfNotEmpty(xml, "auth_code", paramsDto.getAuthCode());
            payMap.put("auth_code", paramsDto.getAuthCode());

            if (StrUtil.isNotBlank(paramsDto.getExtParam())) {
                addElementIfNotEmpty(xml, "attach", paramsDto.getExtParam());
                payMap.put("attach", paramsDto.getExtParam());
            }

            String sign = signBarPayParam(configDto, payMap);
            addElementIfNotEmpty(xml, "sign", sign);

            OutputFormat format = OutputFormat.createPrettyPrint();
            XMLWriter writer = new XMLWriter(System.out, format);
            writer.write(document);

            String xmlStr = document.asXML();
            log.info("微信条码支付参数：{}", xmlStr);

            String post = HttpUtil.post(BAR_PAY_URL, xmlStr);
            log.info("微信条码支付请求结果: orderNo = {}, res = {}", paramsDto.getMchOrderNo(), post);

            JSONObject res = new JSONObject();
            res.put("orderNo", paramsDto.getMchOrderNo());

            SAXReader saxReader = new SAXReader();
            Document read = saxReader.read(stringToInputStream(post));
            Element rootElement = read.getRootElement();
            String returnCode = rootElement.elementText("return_code");

            String errCode = rootElement.elementText("err_code");
            String errCodeDes = rootElement.elementText("err_code_des");
            res.put("message", errCodeDes);

            if (StrUtil.isNotBlank(errCode)) {
                if ("SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode) || "USERPAYING".equals(errCode)) {
                    // 等待结果
                    log.info("微信条码支付等待结果: orderNo = {}, err {}", paramsDto.getMchOrderNo(), errCodeDes);
                    res.put("status", "TRADE_AWAIT");
                    return res;
                }

                res.put("status", "TRADE_FAIL");
                return res;
            }

            if (!PAY_SUCCESS.equals(returnCode)) {
                log.error("微信条码支付失败: orderNo = {}, msg = {}", paramsDto.getMchOrderNo(), rootElement.elementText("return_msg"));
                res.put("status", "TRADE_FAIL");
                return res;
            }

            // 支付成功
            log.info("微信条码支付成功: orderNo = {}, msg = {}", paramsDto.getMchOrderNo(), rootElement.elementText("return_msg"));
            res.put("status", "TRADE_SUCCESS");
            return res;
        } catch (CzgException e) {
            throw e;
        } catch (Exception e) {
            log.error("微信条码支付异常: orderNo = {}, e = {}", paramsDto.getMchOrderNo(), e.getMessage());
            throw new CzgException("微信支付异常");
        }
    }

    /**
     * 查询订单
     *
     * @param configDto     配置
     * @param orderNo       订单号
     * @param merchantDTO 支付商户信息
     *                      SUCCESS：支付成功
     *                      REFUND：转入退款
     *                      NOTPAY：未支付
     *                      CLOSED：已关闭
     *                      REVOKED：已撤销（仅付款码支付会返回）
     *                      USERPAYING：用户支付中（仅付款码支付会返回）
     *                      PAYERROR：支付失败（仅付款码支付会返回）
     */
    public static QueryOrderRespDTO queryOrder(WechatPayConfigDto configDto, String orderNo, NativeMerchantDTO merchantDTO) {
        try {
            if (configDto == null) {
                configDto = WechatPayConfigDto.getDefaultConfig();
            }

            String resp = WechatReqUtils.getReq(configDto, "/v3/pay/partner/transactions/out-trade-no/" + orderNo,
                    Map.of("sp_mchid", configDto.getMerchantId(), "sub_mchid", merchantDTO.getWechatMerchantId()));
            log.info("微信查询订单，订单号：{}， 响应：{}", orderNo, resp);

            QueryOrderRespDTO queryOrderRespDTO = new QueryOrderRespDTO()
                    .setOrderNo(orderNo)
                    .setOriginResp(resp);

            JSONObject object = JSONObject.parseObject(resp);
            String tradeState = object.getString("trade_state");
            queryOrderRespDTO.setErrorMsg(object.getString("trade_state_desc"));
            if (PAY_SUCCESS.equals(tradeState)) {
                queryOrderRespDTO.setStatus("TRADE_SUCCESS");
                return queryOrderRespDTO;
            }

            if ("USERPAYING".equals(tradeState)) {
                queryOrderRespDTO.setStatus("TRADE_AWAIT");
                return queryOrderRespDTO;
            }

            queryOrderRespDTO.setStatus("TRADE_FAIL");
            return queryOrderRespDTO;
        } catch (Exception e) {
            log.error("微信查询订单异常: orderNo = {}, e = {}", orderNo, e.getMessage());
            throw new CzgException("微信查询订单异常");
        }
    }

    /**
     * 退款
     *
     * @param configDto 配置
     * @param paramsDto 参数
     *                  SUCCESS: 退款成功
     *                  CLOSED: 退款关闭
     *                  PROCESSING: 退款处理中
     *                  ABNORMAL: 退款异常，退款到银行发现用户的卡作废或者冻结了，导致原路退款银行卡失败，可前往服务商平台-交易中心，手动处理此笔退款
     */
    public static RefundRespDTO refundOrder(WechatPayConfigDto configDto, CzgRefundReq paramsDto, String notifyUrl, NativeMerchantDTO merchantDTO) {
        try {
            if (configDto == null) {
                configDto = WechatPayConfigDto.getDefaultConfig();
            }

            JSONObject refundParam = new JSONObject();
            refundParam.put("sub_mchid", merchantDTO.getWechatMerchantId());
            refundParam.put("out_trade_no", paramsDto.getMchOrderNo());
            refundParam.put("out_refund_no", paramsDto.getMchRefundNo());
            refundParam.put("reason", paramsDto.getRefundReason());
            refundParam.put("notify_url", notifyUrl + "/" + PayCst.Type.WECHAT);

            JSONObject amount = new JSONObject();
            amount.put("total", paramsDto.getOrderTotalAmount());
            amount.put("refund", paramsDto.getRefundAmount());
            amount.put("currency", "CNY");
            refundParam.put("amount", amount);

            String resp = WechatReqUtils.postReq(configDto, "/v3/refund/domestic/refunds", refundParam.toJSONString());
            log.info("微信退款，订单号：{}， 响应：{}", paramsDto.getMchOrderNo(), resp);

            JSONObject object = JSONObject.parseObject(resp);

            String code = object.getString("code");
            String status = object.getString("status");
            if ("INVALID_REQUEST".equalsIgnoreCase(code) || StrUtil.isNotBlank(status)) {
                throw new CzgException("微信退款失败：" + object.getString("message"));
            }

            RefundRespDTO respDTO = new RefundRespDTO()
            .setMerchantRefundNo(object.getString("out_refund_no"))
                    .setThirdRefundNo(object.getString("refund_id"))
                    .setRefundTime(object.getString("success_time"))
                    .setOriginalData(resp)
                    .setPlatform(PayCst.Type.WECHAT);

            JSONObject resAmount = object.getJSONObject("amount");
            if (resAmount != null) {
                respDTO.setRefundAmount(resAmount.getLong("refund"));
            }

            switch (status) {
                case "SUCCESS":
                case "PROCESSING":
                    respDTO.setStatus("SUCCESS");
                    break;
                case "CLOSED":
                    respDTO.setStatus("CLOSED");
                    break;
                case "ABNORMAL":
                    respDTO.setStatus("INIT");
                    break;
                default:
                    respDTO.setStatus("FAIL");
                    break;
            }

            return respDTO;
        } catch (CzgException e) {
            throw e;
        } catch (Exception e) {
            log.error("微信退款异常: orderNo = {} ", paramsDto.getMchOrderNo(), e);
            throw new CzgException("微信退款异常");
        }
    }

    /**
     * 将String转换为InputStream
     *
     * @param str 待转换的字符串
     * @return 转换后的InputStream
     */
    private static InputStream stringToInputStream(String str) {
        if (str == null || str.isEmpty()) {
            return new ByteArrayInputStream(new byte[0]);
        }
        // 核心：先转字节数组，再封装为ByteArrayInputStream
        byte[] byteArray = str.getBytes(StandardCharsets.UTF_8);
        return new ByteArrayInputStream(byteArray);
    }

    private static String signBarPayParam(WechatPayConfigDto configDto, Map<String, Object> params) {
        TreeMap<String, Object> sortedParams = new TreeMap<>(params);
        String sortParam = sortedParams.entrySet().stream()
                // 将每个键值对转换为 "key=value" 格式的字符串
                .map(entry -> entry.getKey() + "=" + (entry.getValue() == null ? "" : entry.getValue()))
                // 使用 "&" 连接所有转换后的字符串
                .collect(Collectors.joining("&"));
        log.info("微信条码支付 加密前参数: {}", sortParam);

        String signStr = sortParam + "&key=" + configDto.getApiV2Key();
        return SecureUtil.md5(signStr).toUpperCase();
    }

    /**
     * 添加元素，包括空值
     */
    private static void addElementIfNotEmpty(Element parent, String name, Object value) {
        Element element = parent.addElement(name);
        element.addText(value == null ? "" : String.valueOf(value));
    }

    public static void main(String[] args) {
//        barPay(null, new PayParamsDto()
//                .setMerchantId("1738216504")
//                .setOrderNo("sa101293120sss1")
//                .setTitle("1213")
//                .setBody("1213")
//                .setAmount(1L)
//                .setAppId("wxd88fffa983758a30")
//                .setClientIp("127.0.0.1")
//                .setBarCode("130013153082460022"));

//        Map<String, Object> sss1 = queryOrder(null, "sa101293120sss1", "1738216504");
//        System.out.println(sss1);

//        refundOrder(null, new RefundParamsDto()
//                .setMerchantId("1738216504")
//                .setOrderNo("sa101293120sss1")
//                .setRefundOrderNo("sa101293120sss1")
//                .setOrderAmount(1L)
//                .setRefundAmount(1L)
//                .setRefundReason("测试")
//                .setRefundNotifyUrl("https://www.baidu.com"));
    }
}
