package com.jeequan.jeepay.thirdparty.channel.dgpay;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchApplyment;
import com.jeequan.jeepay.core.entity.MchInfo;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.context.MchAppConfigContext;
import com.jeequan.jeepay.core.model.params.dgpay.DgpayIsvParams;
import com.jeequan.jeepay.core.model.params.dgpay.DgpayIsvsubMchParams;
import com.jeequan.jeepay.core.model.params.dgpay.DgpayNormalMchParams;
import com.jeequan.jeepay.core.model.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.DateKit;
import com.jeequan.jeepay.core.utils.ImageUtils;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.thirdparty.service.ConfigContextQueryService;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * 支付通道工具包
 *
 * @author xiaoyu
 * @date 2022/6/6 11:00
 */
@Slf4j
public class DgPayKit {

    public static final String STATE_SUCCESS = "00000000";
    public static final String STATE_ING = "00000100";
    public static final String BIZ_SUCCESS = "SUCCESS";
    public static final String STATE_ING_P = "P";
    public static final String STATE_SUCCESS_S = "S";
    public static final String STATE_FAIL_F = "F";

    public static final String BASE_REQ_URL = "https://api.huifu.com/v2";
    /**
     * 企业商户进件请求地址
     **/
    public static final String MCH_APPLY_ENT_URL = "/merchant/basicdata/ent";
    /**
     * 个人商户进件请求地址
     **/
    public static final String MCH_APPLY_INDV_URL = "/merchant/basicdata/indv";
    /**
     * 商户信息修改
     **/
    public static final String MCH_APPLY_MODIFY_URL = "/merchant/basicdata/modify";
    public static final String MCH_APPLY_UPDATE_URL = "/merchant/integrate/update";
    /**
     * 进件状态查询
     **/
    public static final String MCH_APPLY_QUERY_URL = "/merchant/basicdata/status/query";
    /**
     * 商户详细信息查询
     **/
    public static final String MCH_INFO_QUERY_URL = "/merchant/basicdata/query";
    /**
     * 图片上传
     **/
    public static final String MCH_UPLOAD_PICTURE_URL = "/supplementary/picture";
    /**
     * 商户业务开通
     **/
    public static final String MCH_BUSI_OPEN_URL = "/merchant/busi/open";
    /**
     * 微信商户配置
     **/
    public static final String MCH_BUSI_CONFIG_URL = "/merchant/busi/config";
    /**
     * 结算信息查询
     **/
    public static final String MCH_BUSI_SETTLE_QUERY_URL = "/merchant/basicdata/settlement/query";
    /**
     * 智能POS  webhook 支付推送事件类型
     **/
    public static List<String> POS_PAY_EVENT = new ArrayList<>();

    static {
        POS_PAY_EVENT.add("pay.union_scaned");
        POS_PAY_EVENT.add("pay.wx_scaned");
        POS_PAY_EVENT.add("pay.ali_scaned");
        POS_PAY_EVENT.add("pay.digit_scaned");
        POS_PAY_EVENT.add("pay.card_consume.pos");
    }

    /**
     * 智能POS  webhook 退款推送事件类型
     **/
    public static List<String> POS_REFUND_EVENT = new ArrayList<>();

    static {
        POS_REFUND_EVENT.add("refund.card_consume.pos");
        POS_REFUND_EVENT.add("refund.standard");
    }

    /**
     * 重写请求方法
     *
     * @param reqUrl
     * @param bizData
     * @return
     */
    public static JSONObject req(String reqUrl, DgpayIsvParams isvParams, JSONObject bizData) {
        JSONObject reqParams = initReq(isvParams, bizData);
        // 生成签名
        TreeMap treeMap = JSON.parseObject(bizData.toJSONString(), TreeMap.class);
        JSONObject treeJson = (JSONObject) JSON.toJSON(treeMap);
        String sign = sign(treeJson.toJSONString(), isvParams.getRsaPrivateKey());
        reqParams.put("sign", sign);
        String requestUrl = BASE_REQ_URL + reqUrl;
        log.info("【斗拱】请求地址：{}，请求参数：{}", requestUrl, reqParams);
        HttpResponse response = post(requestUrl, reqParams.toJSONString());
        log.info("【斗拱】响应结果：{}", response.body());
        return initResult(response.body(), isvParams);
    }

    /**
     * 处理统一响应结果
     *
     * @param result
     * @return
     */
    private static JSONObject initResult(String result, DgpayIsvParams isvParams) {
        JSONObject respData = JSON.parseObject(result);
        JSONObject bizData = respData.getJSONObject("data");
        TreeMap signData = JSON.parseObject(JSON.toJSONString(bizData), TreeMap.class);
        boolean verifyResult = DgPayKit.verify(JSON.toJSONString(signData), isvParams.getRsaPublicKey(), respData.getString("sign"));
        if (!verifyResult) {
            throw new BizException("签名校验异常");
        }
        if (!DgPayKit.STATE_SUCCESS.equals(bizData.getString("resp_code"))) {
            throw new BizException(bizData.getString("resp_desc"));
        }
        return bizData;
    }

    public static JSONObject initReq(DgpayIsvParams isvParams, JSONObject bizData) {
        JSONObject reqParams = new JSONObject();
        reqParams.put("sys_id", isvParams.getSysId());
        reqParams.put("product_id", isvParams.getProductId());
        reqParams.put("data", bizData);
        return reqParams;
    }

    /**
     * 放置 请求信息
     **/
    public static JSONObject request(MchAppConfigContext mchAppConfigContext, JSONObject dataParams, String reqUrl, String ifCode) {
        //
        dataParams.put("req_date", DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN));
        dataParams.put("req_seq_id", DateKit.currentTimeMillis());

        try {

            JSONObject configParams = getConfigParams(mchAppConfigContext, ifCode);

            JSONObject reqParams = new JSONObject();
            // 请求参数带参
            reqParams.put("sys_id", configParams.getString("sysId"));
            reqParams.put("product_id", configParams.getString("productId"));
            if (dataParams.getBoolean("noMchId") != null && dataParams.getBoolean("noMchId")) {
            } else {
                if (StringUtils.isEmpty(dataParams.getString("huifu_id")) && StringUtils.isNotEmpty(configParams.getString("huifuId"))) {
                    dataParams.put("huifu_id", configParams.getString("huifuId"));
                }
            }
            dataParams.remove("noMchId");
            // 请求数据
            reqParams.put("data", dataParams);
            // 生成签名
            TreeMap treeMap = JSON.parseObject(dataParams.toJSONString(), TreeMap.class);
            JSONObject treeJson = (JSONObject) JSON.toJSON(treeMap);
            String sign = sign(treeJson.toJSONString(), configParams.getString("rsaPrivateKey"));
            reqParams.put("sign", sign);
            String requestUrl = BASE_REQ_URL + reqUrl;
            log.info("【斗拱】请求地址：{}，请求参数：{}", requestUrl, reqParams);
            HttpResponse response = post(requestUrl, reqParams.toJSONString());
            String res = response.body();
            log.info("【斗拱】响应结果：{}", res);
            return JSON.parseObject(res);
        } catch (BizException e) {
            throw new BizException("[斗拱支付]参数请求异常:" + e.getMessage());
        }
    }

    /**
     * 支付请求
     **/
    public static JSONObject payRequest(MchAppConfigContext mchAppConfigContext, JSONObject dataParams, String reqUrl, String ifCode) {
        JSONObject configParams = getConfigParams(mchAppConfigContext, ifCode);
        dataParams.put("channel_no", configParams.getString("channelNo"));
        dataParams.put("pay_scene", configParams.getString("payScene"));
        JSONObject reqParams = new JSONObject();
        // 请求参数带参
        reqParams.put("sys_id", configParams.getString("sysId"));
        reqParams.put("product_id", configParams.getString("productId"));
        if (dataParams.getBoolean("noMchId") != null && dataParams.getBoolean("noMchId")) {
        } else {
            if (StringUtils.isEmpty(dataParams.getString("huifu_id")) && StringUtils.isNotEmpty(configParams.getString("huifuId"))) {
                dataParams.put("huifu_id", configParams.getString("huifuId"));
            }
        }
        dataParams.remove("noMchId");
        // 请求数据
        reqParams.put("data", dataParams);
        // 生成签名
        TreeMap treeMap = JSON.parseObject(dataParams.toJSONString(), TreeMap.class);
        JSONObject treeJson = (JSONObject) JSON.toJSON(treeMap);
        String sign = sign(treeJson.toJSONString(), configParams.getString("rsaPrivateKey"));
        reqParams.put("sign", sign);
        String requestUrl = BASE_REQ_URL + reqUrl;
        log.info("【斗拱】请求地址：{}，请求参数：{}", requestUrl, reqParams);
        HttpResponse response = post(requestUrl, reqParams.toJSONString());
        String res = response.body();
        log.info("【斗拱】响应结果：{}", res);
        JSONObject resp = JSON.parseObject(res);
        JSONObject data = resp.getJSONObject("data");
        if (!STATE_SUCCESS.equals(data.getString("resp_code")) && !STATE_ING.equals(data.getString("resp_code"))) {
            String msg = StringUtils.isNotEmpty(data.getString("bank_message")) ? data.getString("bank_message") : data.getString("resp_desc");
            throw new BizException(msg);
        }
        return resp;
    }

    /**
     * 放置 请求信息
     **/
    public static String uploadFile(String isvNo, String fileType, String fileUrl, String ifCode) {
        try {
            if (StringUtils.isEmpty(fileUrl)) {
                return "";
            }

            MchAppConfigContext mchAppConfigContext = new MchAppConfigContext();
            mchAppConfigContext.setMchType(MchInfo.TYPE_ISVSUB);
            mchAppConfigContext.setMchApplyment(new MchApplyment().setIsvNo(isvNo));

            JSONObject configParams = getConfigParams(mchAppConfigContext, ifCode);

            JSONObject reqParams = new JSONObject();
            // 请求参数带参
            reqParams.put("product_id", configParams.getString("productId"));
            reqParams.put("sys_id", configParams.getString("sysId"));

            JSONObject dataParams = new JSONObject();
            dataParams.put("req_seq_id", DateKit.currentTimeMillis());
            dataParams.put("req_date", DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN));
            dataParams.put("file_type", fileType);
            dataParams.put("picture", "1.jpg");
            // 请求数据
            reqParams.put("data", dataParams);
            // 生成签名
            TreeMap treeMap = JSON.parseObject(dataParams.toJSONString(), TreeMap.class);
            JSONObject treeJson = (JSONObject) JSON.toJSON(treeMap);
            String sign = sign(treeJson.toJSONString(), configParams.getString("rsaPrivateKey"));
            reqParams.put("sign", sign);
            String requestUrl = BASE_REQ_URL + MCH_UPLOAD_PICTURE_URL;
            log.info("【斗拱】上传图片，请求地址：{}，请求参数：{}", requestUrl, reqParams);

            // 下载文件
            byte[] imgFileByteArray = null;
            try {
                ByteArrayOutputStream baos = ImageUtils.compressNetPic(fileUrl, 2048 * 1024);
                imgFileByteArray = baos.toByteArray();
            } catch (IORuntimeException e) {
                log.info("斗拱入网压缩图片异常, 图片链接为{}", fileUrl, e);
                throw new BizException("银盛入网压缩图片失败, " + e.getMessage());
            }

            @Cleanup HttpResponse response = HttpUtil.createPost(requestUrl)
                    .form("picture", imgFileByteArray, "1.jpg")
                    .form("sys_id", configParams.getString("sysId"))
                    .form("product_id", configParams.getString("productId"))
                    .form("sign", sign)
                    .form("data", dataParams.toJSONString())
                    .execute();
            String result = response.body();
            log.info("【斗拱】上传图片，响应结果：{}", result);
            JSONObject resJson = JSON.parseObject(result);
            JSONObject resData = resJson.getJSONObject("data");
            String respCode = resData.getString("resp_code");
            String respDesc = resData.getString("resp_desc");
            if (!DgPayKit.STATE_SUCCESS.equals(respCode)) {
                throw new BizException(respDesc);
            }

            return resData.getString("file_id");
        } catch (Exception e) {
            throw new BizException("[斗拱支付]上传文件请求异常:" + e.getMessage());
        }
    }

    /**
     * 公共参数
     **/
    public static JSONObject dataParams(PayOrder payOrder, String notifyUrl) {
        JSONObject dataParams = new JSONObject();
        // 请求日期
        dataParams.put("req_date", DateUtil.format(payOrder.getCreatedAt(), DatePattern.PURE_DATE_PATTERN));
        // 请求流水号
        dataParams.put("req_seq_id", payOrder.getPayOrderId());
        // 支付金额 元 保留两位小数
        dataParams.put("trans_amt", AmountUtil.convertCent2Dollar(payOrder.getFindAmt()));
        // 商品描述
        dataParams.put("goods_desc", payOrder.getBody());

        // 安全信息
        JSONObject riskJson = new JSONObject();
        riskJson.put("ip_addr", payOrder.getClientIp());
        dataParams.put("risk_check_data", riskJson.toJSONString());
        // 订单失效时间
        dataParams.put("time_expire", DateUtil.format(payOrder.getExpiredTime(), DatePattern.PURE_DATETIME_PATTERN));
        // 异步回调地址
        dataParams.put("notify_url", notifyUrl);
        return dataParams;
    }


    public static JSONObject getConfigParams(MchAppConfigContext mchAppConfigContext, String ifCode) {
        ConfigContextQueryService configContextQueryService = SpringBeansUtil.getBean(ConfigContextQueryService.class);

        JSONObject configJson = null;
        // 特约商户
        if (mchAppConfigContext.isIsvsubMch()) {
            DgpayIsvParams isvParams;
            isvParams = (DgpayIsvParams) configContextQueryService.queryIsvParams(mchAppConfigContext.getMchApplyment().getIsvNo(), ifCode);

            if (isvParams == null) {
                throw new BizException("服务商参数未配置");
            }

            configJson = (JSONObject) JSON.toJSON(isvParams);

            DgpayIsvsubMchParams mchParams = (DgpayIsvsubMchParams) configContextQueryService.queryIsvsubMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode);
            if (mchParams != null) {
                configJson.put("huifuId", mchAppConfigContext.getMchApplyment().getChannelMchNo());
                configJson.put("payScene", StringUtils.defaultString(mchParams.getPayScene(), "02"));
            }

            configJson.put("productId", isvParams.getProductId());
            configJson.put("channelNo", isvParams.getChannelNo());
        } else {
            // 普通商户
            DgpayNormalMchParams mchParams = (DgpayNormalMchParams) configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode);
            if (mchParams == null) {
                throw new BizException("商户参数未配置");
            }

            configJson = (JSONObject) JSON.toJSON(mchParams);
            configJson.put("sysId", mchParams.getHuifuId());
            configJson.put("productId", mchParams.getProductId());
            configJson.put("payScene", StringUtils.defaultString(mchParams.getPayScene(), "02"));
        }
        return configJson;
    }

    /**
     * @author: xiaoyu
     * @date: 2022/4/13 15:43
     * @describe: 发送请求
     */
    public static HttpResponse post(String url, String params) {
        HttpResponse response = HttpUtil.createPost(url)
                .header("content-type", "application/json;charset=utf-8")
                .timeout(20000)
                .charset("utf-8")
                .body(params)
                .execute();
        return response;
    }

    public static String sign(String data, String privateKeyBase64) {
        try {
            byte[] bytes = Base64.getDecoder().decode(privateKeyBase64);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initSign(privateKey);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception var7) {
            var7.printStackTrace();
            return null;
        }
    }

    /**
     * RSA公钥验签
     *
     * @param data            待签名字符串
     * @param publicKeyBase64 公钥（Base64编码）
     * @return 验签结果
     * @throws Exception
     */
    public static boolean verify(String data, String publicKeyBase64, String sign) {
        // Base64 --> Key
        try {
            byte[] bytes = Base64.getDecoder().decode(publicKeyBase64);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
            KeyFactory keyFactory;
            keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            // verify
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initVerify(publicKey);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (Exception e) {
            log.error("Exception", e);
            return false;
        }

    }

    /**
     * 转换
     */
    public static String covertChannelId(String channelType) {
        return StringUtils.defaultIfBlank(channelType, CS.AUTO_POS_PAY_TYPE.CARD).toLowerCase();
    }

    /**
     * 转换
     */
    public static String covertMobilePayType(String payType) {
        switch (payType) {
            case CS.AUTO_POS_PAY_CHANNEL.ALIPAY:
                return "A";
            case CS.AUTO_POS_PAY_CHANNEL.WECHAT:
                return "W";
            case CS.AUTO_POS_PAY_CHANNEL.QR_CASHIER:
                return "J";
            case CS.AUTO_POS_PAY_CHANNEL.DCEP:
                return "D";
            default:
                return "U";
        }
    }

    /**
     * 异常信息返回描述处理
     **/
    public static void channelMsgError(JSONObject resData, ChannelRetMsg channelRetMsg) {
        if (StringUtils.isEmpty(resData.getString("bank_message"))) {

            channelRetMsg.setChannelErrMsg(resData.get("resp_desc").toString());
            channelRetMsg.setChannelErrCode(resData.get("resp_code").toString());
        } else {

            channelRetMsg.setChannelErrMsg(resData.getString("bank_message"));
            channelRetMsg.setChannelErrCode(resData.get("bank_code").toString());
        }
    }


}
