package com.czg;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.czg.entity.PolyBaseReq;
import com.czg.entity.PolyBaseResp;
import com.czg.entity.notify.CzgPayNotifyDTO;
import com.czg.entity.resp.*;
import com.czg.enums.CzgPayEnum;
import com.czg.pay.CzgPayBaseReq;
import com.czg.pay.CzgRefundReq;
import com.czg.pay.PayNotifyRespDTO;
import com.czg.resp.CzgRespCode;
import com.czg.resp.CzgResult;
import com.czg.utils.AssertUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * @author ww
 * @description 聚合支付类
 */
@Slf4j
public class PolyPayUtils {

    /**
     * h5支付
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     *                  CzgResult<CzgH5PayResp>
     */
    public static CzgResult<Map<String, Object>> h5Pay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) {
        return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.H5_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgH5PayResp.class));
    }

    /**
     * js支付
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     *                  CzgResult<CzgJsPayResp>
     */
    public static CzgResult<Map<String, Object>> jsPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) {
        return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.JS_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgJsPayResp.class));
    }

    /**
     * 小程序支付
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     *                  CzgResult<CzgLtPayResp>
     */
    public static CzgResult<Map<String, Object>> ltPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) {
        return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.LT_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgLtPayResp.class));
    }

    /**
     * PC扫码支付
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     *                  CzgResult<CzgScanPayResp>
     */
    public static CzgResult<Map<String, Object>> scanPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) {
        return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.SCAN_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgScanPayResp.class));
    }

    /**
     * 聚合反扫
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     *                  CzgResult<CzgMicroPayResp>
     */
    public static CzgResult<Map<String, Object>> microPay(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgPayBaseReq bizData) {
        return execPayResult(sendPolyPay(domain.concat(CzgPayEnum.MICRO_PAY.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgMicroPayResp.class));
    }

    /**
     * 订单查询
     *
     * @param domain     域名
     * @param appId      应用id tb_shop_merchant 表中的 app_id
     * @param appSecret  应用密钥 tb_shop_merchant 表中的 app_secret
     * @param payOrderId 平台订单号
     * @param mchOrderNo 商户订单号
     */
    public static CzgResult<CzgBaseResp> queryPayOrder(@NonNull String domain, @NonNull String appId, @NonNull String appSecret,
                                                       String payOrderId, String mchOrderNo) {
        JSONObject queryPayOrder = new JSONObject();
        queryPayOrder.put("payOrderId", payOrderId);
        queryPayOrder.put("mchOrderNo", mchOrderNo);
        return sendPolyPay(domain.concat(CzgPayEnum.TRADE.getUri()), PolyBaseReq.getInstance(appId, appSecret, queryPayOrder), CzgBaseResp.class);
    }


    /**
     * 订单退款
     *
     * @param domain    域名
     * @param appId     应用id tb_shop_merchant 表中的 app_id
     * @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
     */
    public static CzgResult<CzgRefundResp> refundOrder(@NonNull String domain, @NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData) {
        return sendPolyPay(domain.concat(CzgPayEnum.REFUND.getUri()), PolyBaseReq.getInstance(appId, appSecret, bizData), CzgRefundResp.class);
    }

    /**
     * 退款订单查询
     *
     * @param domain        域名
     * @param appId         应用id tb_shop_merchant 表中的 app_id
     * @param appSecret     应用密钥 tb_shop_merchant 表中的 app_secret
     * @param mchRefundNo   商户退款订单号 二选一
     * @param refundOrderId 平台退款订单号 二选一
     */
    public static CzgResult<CzgRefundResp> queryRefundOrder(@NonNull String domain, @NonNull String appId, @NonNull String appSecret,
                                                            String mchRefundNo, String refundOrderId) {
        JSONObject queryPayOrder = new JSONObject();
        queryPayOrder.put("mchRefundNo", mchRefundNo);
        queryPayOrder.put("refundOrderId", refundOrderId);
        return sendPolyPay(domain.concat(CzgPayEnum.QUERY_REFUND.getUri()), PolyBaseReq.getInstance(appId, appSecret, queryPayOrder), CzgRefundResp.class);
    }


    /**
     * 回调数据处理
     */
    public static JSONObject getCzg(PolyBaseResp respParams) {
        AssertUtil.isNull(respParams, "聚合交易 回调数据为空");
        log.info("聚合交易请求响应,{}", respParams);
        if (!"000000".equals(respParams.getCode())) {
            log.error("聚合回调响应失败,{}", respParams);
            return null;
        }
//        if (StrUtil.isNotBlank(respParams.getSign())) {
//            if (validateSign(respParams.getSign(), respParams.getBizData())) {
//                log.error("聚合回调 验签失败");
//            }
//        }
        return JSONObject.parse(respParams.getBizData());

    }


    /**
     * 回调数据处理
     */
    public static PayNotifyRespDTO getNotifyResp(PolyBaseResp respParams) {
        AssertUtil.isNull(respParams, "聚合交易 回调数据为空");
        log.info("聚合交易请求响应,{}", respParams);
        if (!"000000".equals(respParams.getCode())) {
            log.error("聚合回调响应失败,{}", respParams);
            return null;
        }

        CzgPayNotifyDTO dto = JSONObject.parseObject(respParams.getBizData(), CzgPayNotifyDTO.class);

        return new PayNotifyRespDTO()
                .setMchOrderNo(dto.getMchOrderNo())
                .setThirdOrderNo(dto.getPayOrderId())
                .setAmount(dto.getAmount())
                .setPlatform(dto.getPayType())
                .setExtData(dto.getExtParam())
                .setPaySuccessTime(dto.getPayTime())
                .setErrorMsg("")
                .setStatus(dto.getState())
                .setOriginalData(respParams.getBizData());
    }

    /**
     * 默认Post
     *
     * @param url    请求地址
     * @param params 参数实体
     * @return {
     * "code": "状态码",  200为成功
     * "msg": "描述"
     * "data": "返回Json数据",
     * }
     */
    private static <T> CzgResult<T> sendPolyPay(String url, PolyBaseReq params, Class<T> clazz) {
        CzgResult<T> result = CzgResult.success();
        Map<String, Object> reqMap = BeanUtil.beanToMap(params, false, false);
        params.setSign(MD5.create().digestHex(sortFields(new TreeMap<>(reqMap))));
        log.info("聚合交易请求参数,{}", JSONObject.toJSONString(params));
        try (HttpResponse resp = HttpRequest.post(url).body(JSONObject.toJSONString(params)).execute()) {
            if (resp.isOk()) {
                // 获取响应体
                String respStr = resp.body();
                if (StrUtil.isNotEmpty(respStr)) {
                    log.info("聚合交易请求响应元数据,{}", respStr);
                    PolyBaseResp respParams = JSONObject.parseObject(respStr, PolyBaseResp.class);
                    log.info("聚合交易请求响应,{}", respParams);

                    result.setCode("000000".equals(respParams.getCode()) ? 200 : Integer.parseInt(respParams.getCode()));
                    result.setMsg(respParams.getMsg());
                    if ("000000".equals(respParams.getCode()) && StrUtil.isNotBlank(respParams.getSign())) {
//                        if (validateSign(respParams.getSign(), JSONObject.toJSONString(respParams))) {
//                            result.setCode(CzgRespCode.FAILURE.getCode());
//                            result.setMsg("验签失败");
//                            log.info("验签失败");
//                        }
                        result.setData(JSONObject.parseObject(respParams.getBizData(), clazz));
                    }
                } else {
                    result.setCode(resp.getStatus());
                    result.setMsg("聚合交易请求失败");
                    log.error("聚合交易请求失败，状态码: {}", resp.getStatus());
                }
            } else {
                result.setCode(resp.getStatus());
                result.setMsg("聚合交易请求失败");
            }
        } catch (Exception e) {
            log.error("聚合交易请求异常", e);
        }
        return result;
    }

    /**
     * @param sign        签名
     * @param dataJsonStr 业务数据
     * @return false 验签通过 true 验签失败
     */
    private static boolean validateSign(String sign, String dataJsonStr) {
        Map<String, Object> dataMap = JSONObject.parseObject(dataJsonStr, new TypeReference<>() {
        });
        String newSign = MD5.create().digestHex((sortFields(new TreeMap<>(dataMap))));
        return !StrUtil.equals(sign, newSign);
    }

    private static String sortFields(TreeMap<String, Object> map) {
        if (CollectionUtil.isEmpty(map)) {
            log.error("聚合支付参数为空！！！");
            throw new RuntimeException("参数为空");
        }
        String sortParam = map.entrySet().stream()
                // 过滤掉键为 "sign" 的元素
                .filter(entry -> !"sign".equals(entry.getKey()) && !"appSecret".equals(entry.getKey()))
                // 将每个键值对转换为 "key=value" 格式的字符串
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                // 使用 "&" 连接所有转换后的字符串
                .collect(Collectors.joining("&"));
        if (map.containsKey("appSecret")) {
            sortParam = sortParam.concat("&" + "appSecret=" + map.get("appSecret"));
        }
        return sortParam;
    }
    private static CzgResult<Map<String, Object>> execPayResult(CzgResult<? extends CzgBaseResp> res) {
        CzgResult<Map<String, Object>> result = CzgResult.success();
        if (res.getCode() != 200 || res.getData() == null) {
            result.setCode(500);
            result.setMsg(res.getMsg());
            return result;
        }

        CzgBaseResp data = res.getData();

        Map<String, Object> map = new HashMap<>();
        switch (data) {
            case CzgMicroPayResp ignored -> {
                if ("TRADE_SUCCESS".equals(data.getState())) {
                    return result;
                } else if ("TRADE_AWAIT".equals(data.getState())) {
                    result.setCode(CzgRespCode.WAIT_PAY.getCode());
                    result.setMsg("等待用户付款");
                } else {
                    result.setCode(CzgRespCode.FAILURE.getCode());
                }
            }
            case CzgH5PayResp h5PayResp ->
                    map = JSONObject.parseObject(JSONObject.toJSONString(h5PayResp.getPayInfo()));
            case CzgJsPayResp jsPayResp ->
                    map = JSONObject.parseObject(JSONObject.toJSONString(jsPayResp.getPayInfo()));
            case CzgLtPayResp ltPayResp ->
                    map = JSONObject.parseObject(JSONObject.toJSONString(ltPayResp.getPayInfo()));
            case CzgScanPayResp scanPayResp ->
                    map = JSONObject.parseObject(JSONObject.toJSONString(scanPayResp.getPayInfo()));
            default -> throw new IllegalStateException("Unexpected value: " + data);
        }

        result.setData(map);

        return result;
    }
}
