package com.slodon.b2b2c.controller.member.front;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.XmlUtil;
import com.alipay.api.internal.util.AlipaySignature;
import com.gexin.fastjson.JSON;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.slodon.b2b2c.core.constant.MemberConst;
import com.slodon.b2b2c.core.constant.PayConst;
import com.slodon.b2b2c.core.response.WxPayCallbackVO;
import com.slodon.b2b2c.core.util.AssertUtil;
import com.slodon.b2b2c.core.util.XmlUtils;
import com.slodon.b2b2c.member.example.MemberBalanceRechargeExample;
import com.slodon.b2b2c.member.pojo.MemberBalanceRecharge;
import com.slodon.b2b2c.model.member.MemberBalanceRechargeModel;
import com.slodon.b2b2c.starter.entity.AliPayProperties;
import com.slodon.b2b2c.starter.entity.SlodonRefundRequest;
import com.slodon.b2b2c.starter.entity.SlodonRefundResponse;
import com.slodon.b2b2c.starter.entity.WxPayProperties;
import com.slodon.b2b2c.starter.entity.cpcn.TxRefundRequest;
import com.slodon.b2b2c.starter.pay.SlodonPay;
import com.slodon.b2b2c.util.PayPropertiesUtil;
import cpcn.institution.tools.util.Base64;
import cpcn.institution.tools.util.StringUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import payment.api.notice.Notice5018Request;
import payment.api.notice.NoticeRequest;
import payment.api.notice.NoticeResponse;
import payment.api.util.GUIDGenerator;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author slodon
 */
@Api(tags = "front-充值回调")
@Slf4j
@RestController
@RequestMapping("v3/member/front/rechargeCallback")
public class FrontRechargeCallbackController {

    @Resource
    private MemberBalanceRechargeModel memberBalanceRechargeModel;
    @Resource
    private PayPropertiesUtil payPropertiesUtil;
    @Resource
    private SlodonPay slodonPay;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 微信返回  fail 失败，success 成功
     */
    private static final String RETURN_CODE_SUCCESS = "SUCCESS";
    private static final String RETURN_MSG_OK = "OK";
    private static final String RETURN_CODE_FAIL = "FAIL";

    /**
     * 支付宝充值回调
     *
     */
    @PostMapping("ali")
    public void alipayNotify(HttpServletRequest request, HttpServletResponse response) {

        try {
            //获取支付宝POST过来反馈信息
            Map<String, String> params = new HashMap<>(16);
            Map requestParams = request.getParameterMap();
            for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                params.put(name, valueStr);
            }
            //调用SDK,验证签名
            AliPayProperties aliPayProperties = payPropertiesUtil.getAliPayProperties();
            //证书验签
            boolean signVerified = AlipaySignature.rsaCertCheckV1(params, aliPayProperties.aliPayPublicCertPath, aliPayProperties.inputCharset, aliPayProperties.signType);
            if (signVerified) {
                //验证成功
                // TODO 验签成功后，按照支付结果异步通知中的描述，对支付结果中的业务内容进行二次校验，校验成功后在response中返回success并继续商户自身业务处理，校验失败返回failure
                //获取支付宝的通知返回参数，可参考技术文档中页面跳转异步通知参数列表(以下仅供参考)//
                //商户订单号
                String outTradeNo = params.get("out_trade_no");
                //支付宝交易号
                String tradeNo = params.get("trade_no");
                //交易金额
                String totalAmount = params.get("total_amount");
                //交易状态
                String tradeStatus = params.get("trade_status");
                //——请根据您的业务逻辑来编写程序（以下代码仅作参考）——
                if (tradeStatus.equals("TRADE_FINISHED")) {
                    log.debug("订单已完成");
                    //请不要修改或删除
                    response.getWriter().println(RETURN_CODE_SUCCESS);
                } else if (tradeStatus.equals("TRADE_SUCCESS")) {
                    log.debug("支付成功");

                    //判断该笔订单是否在商户网站中已经做过处理
                    MemberBalanceRecharge memberBalanceRecharge = this.getMemberBalanceRechargeBySn(outTradeNo);
                    if (memberBalanceRecharge.getPayState().equals(MemberConst.PAY_STATE_2)) {
                        //订单已支付，执行退款操作
                        if (memberBalanceRecharge.getPaymentCode().contains(PayConst.METHOD_ALIPAY.toUpperCase())) {
                            //重复回调，不处理，直接返回成功
                            response.getWriter().println(RETURN_CODE_SUCCESS);
                            return;
                        }
                        //其他方式已支付，如余额、微信，执行退款
                        this.aliRefund(memberBalanceRecharge.getRechargeSn(), memberBalanceRecharge.getTradeSn(), new BigDecimal(totalAmount));
                        response.getWriter().println(RETURN_CODE_SUCCESS);
                        return;
                    }

                    // 1-开启测试, 0-关闭测试
                    if ("1".equals(stringRedisTemplate.opsForValue().get("pay_test_enable"))) {
                        //不校验支付额
                    } else {
                        //获取充值订单
                        if (!totalAmount.equals(this.getRechargeAmountDb(outTradeNo).toString())) {
                            log.error("订单支付金额不一致paySn:" + outTradeNo);
                            response.getWriter().println(RETURN_CODE_FAIL);
                            //订单金额不一致不往下执行
                            return;
                        }
                    }
                    boolean result = memberBalanceRechargeModel.payAfter(outTradeNo, tradeNo);
                    if (!result) {
                        response.getWriter().println(RETURN_CODE_FAIL);
                    }
                } else {
                    response.getWriter().println(RETURN_CODE_FAIL);
                }
                response.getWriter().println(RETURN_CODE_SUCCESS);
            } else {
                response.getWriter().println(RETURN_CODE_FAIL);
            }
        } catch (Exception e) {
            log.error("支付宝回调失败", e);
            try {
                response.getWriter().println(RETURN_CODE_FAIL);
            } catch (IOException e1) {
                log.error("支付宝回调失败", e1);
            }
        }
    }

    /**
     * 微信充值回调
     *
     */
    @RequestMapping(value = "/wx", method = {RequestMethod.GET, RequestMethod.POST})
    public void wxNotify(HttpServletRequest request, HttpServletResponse response) {
        log.debug("-----------------------------------------------------------------------------");
        log.debug("微信通知开始");
        log.debug("-----------------------------------------------------------------------------");
        ServletInputStream in;

        try {
            // post 过来的xml
            in = request.getInputStream();
            //转换微信post过来的xml内容
            String notifyData = IoUtil.read(in, StandardCharsets.UTF_8);

            WxPayCallbackVO vo = XmlUtils.convertToObject(notifyData, WxPayCallbackVO.class);

            String outTradeNo = vo.getOut_trade_no();
            //格式为paySn_payType，截取paySn
            String rechargeSn = outTradeNo.split("_")[0];
            String payType = outTradeNo.split("_")[1];
            log.error("通知开始...." + rechargeSn);

            //微信支付配置
            WxPayProperties wxPayProperties = payPropertiesUtil.getWxPayProperties(payType);
            WxPayServiceImpl wxPayService = new WxPayServiceImpl();
            WxPayConfig wxPayConfig = new WxPayConfig();
            wxPayConfig.setAppId(StringUtils.trimToNull(wxPayProperties.getAppId()));
            wxPayConfig.setMchId(StringUtils.trimToNull(wxPayProperties.getMchId()));
            wxPayConfig.setMchKey(StringUtils.trimToNull(wxPayProperties.getMchKey()));
            wxPayService.setConfig(wxPayConfig);

            //处理xml
            WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(notifyData);

            //验证签名
            notifyResult.checkResult(wxPayService, notifyResult.getSignType(), true);

            MemberBalanceRecharge memberBalanceRecharge = this.getMemberBalanceRechargeBySn(rechargeSn);
            if (memberBalanceRecharge.getPayState().equals(MemberConst.PAY_STATE_2)) {
                //已支付
                if (memberBalanceRecharge.getPaymentCode().contains(PayConst.METHOD_WX.toUpperCase()) &&
                        memberBalanceRecharge.getPaymentCode().contains(rechargeSn)) {
                    //同一客户端重复回调，不做处理
                    sendMsg(response);
                    return;
                }
                //不同客户端重复支付，执行退款
                this.wxRefund(memberBalanceRecharge.getRechargeSn(), memberBalanceRecharge.getTradeSn(), payType, notifyResult.getTotalFee());
                sendMsg(response);
                return;
            }

            // 签名正确
            // 进行处理。
            // 注意特殊情况：订单已经退款，但收到了支付结果成功的通知，不应把商户侧订单状态从退款改成支付成功
            //安全校验(比较订单金额)
            BigDecimal totalFee = new BigDecimal(notifyResult.getTotalFee());
            totalFee = totalFee.divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
            // 1-开启测试, 0-关闭测试
            if ("1".equals(stringRedisTemplate.opsForValue().get("pay_test_enable"))) {
                //不校验支付额
            } else {
                BigDecimal totalFeeDb = memberBalanceRecharge.getPayAmount();
                if (totalFeeDb.compareTo(totalFee) != 0) {
                    log.error("订单支付金额不一致paySn:" + notifyResult.getOutTradeNo());
                    response.getWriter().println(RETURN_CODE_FAIL);
                    return;
                }
            }
            //更改订单状态
            boolean result = memberBalanceRechargeModel.payAfter(rechargeSn, notifyResult.getTransactionId());
            if (!result) {
                response.getWriter().println(RETURN_CODE_FAIL);
            }

            sendMsg(response);

        } catch (Exception e) {
            log.error("微信回调失败", e);
        }
    }

    /**
     * 订单支付中金回调
     */
    @PostMapping("zj")
    public void txPayCallback(HttpServletRequest request, HttpServletResponse response) {
        log.debug("=========================================");
        log.debug("中金支付回调开始");
        log.debug(JSON.toJSONString(request.getParameterMap()));
        log.debug("=========================================");

        try {
            // 1 获得参数message和signature
            String message = request.getParameter("message");
            String signature = request.getParameter("signature");
            String isDgEnv=request.getParameter("isDgEnv");
            String digitalEnvelope=request.getParameter("digitalEnvelope");
            String signAlgorithm=request.getParameter("signAlgorithm");
            String signSN=request.getParameter("signSN");
            String encryptSN=request.getParameter("encryptSN");

            // 2 生成交易结果对象
            NoticeRequest noticeRequest;
            if("YES".equals(isDgEnv)) {
                //数字信封解密验签
                noticeRequest = new NoticeRequest(message, signature,digitalEnvelope,signSN,encryptSN,signAlgorithm);
            }else {
                //非数字信封解密验签
                if(StringUtil.isNotEmpty(signSN)) {
                    message = message +","+signSN+","+signAlgorithm;
                }
                noticeRequest = new NoticeRequest(message, signature);
            }

            Notice5018Request nr = new Notice5018Request(noticeRequest.getDocument());
            log.debug("=========================================");
            log.debug(JSON.toJSONString(nr));
            log.debug("=========================================");

            /*
            支付状态:
            10=未支付
            20=支付处理中
            30=支付成功
            40=支付失败
            50=订单关闭
             */
            String status = nr.getStatus();
            if ("30".equals(status)){
                //只处理支付成功的情况
                String rechargeSn = nr.getOrderNo();
                rechargeSn = rechargeSn.substring(0,rechargeSn.length()-6);

                MemberBalanceRecharge memberBalanceRecharge = this.getMemberBalanceRechargeBySn(rechargeSn);
                BigDecimal payAmount = new BigDecimal(nr.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);//回调金额
                if (memberBalanceRecharge.getPayState().equals(MemberConst.PAY_STATE_2)) {
                    //已支付
                    if (nr.getTxSN().equals(memberBalanceRecharge.getTradeSn())){
                        //重复回调
                        PrintWriter out = response.getWriter();
                        this.response(out);
                        out.flush();
                        out.close();
                        return;
                    }
                    //其他方式支付，执行退款
                    this.zjRefund(payAmount,rechargeSn,nr.getTxSN());
                    PrintWriter out = response.getWriter();
                    this.response(out);
                    out.flush();
                    out.close();
                    return;
                }

                if (!nr.getTxSN().equals(memberBalanceRecharge.getTradeSn())){
                    //支付流水号不同，废弃的支付流水号
                    this.zjRefund(payAmount,rechargeSn,nr.getTxSN());
                    PrintWriter out = response.getWriter();
                    this.response(out);
                    out.flush();
                    out.close();
                    return;
                }
                // 1-开启测试, 0-关闭测试
                if ("1".equals(stringRedisTemplate.opsForValue().get("pay_test_enable"))) {
                    //不校验支付额
                } else {
                    if (payAmount.compareTo(memberBalanceRecharge.getPayAmount()) != 0) {
                        log.error("订单支付金额不一致paySn:" + rechargeSn);
                        PrintWriter out = response.getWriter();
                        this.response(out);
                        out.flush();
                        out.close();
                        return;
                    }
                }

                memberBalanceRechargeModel.payAfter(rechargeSn, nr.getTxSN());
            }
            PrintWriter out = response.getWriter();
            this.response(out);
            out.flush();
            out.close();
        }catch (Exception e){
            log.error("中金支付回调异常：" , e);

        }
    }

    /**
     * 给支付平台发送回调成功信息，发送后支付平台不再进行回调
     * @param out 响应
     */
    private void response(PrintWriter out){
        String xmlString = new NoticeResponse().getMessage();
        String base64String = new String(Base64.encode(xmlString.getBytes(StandardCharsets.UTF_8)));
        out.print(base64String);
    }

    /**
     * 中金支付退款
     * @param refundAmount 退款金额
     * @param refundSn 退款单号
     * @param paymentTxSn 支付时的交易流水号
     */
    public void zjRefund(BigDecimal refundAmount, String refundSn, String paymentTxSn) throws Exception {
        TxRefundRequest txRefundRequest = new TxRefundRequest();
        txRefundRequest.setInstitutionId(stringRedisTemplate.opsForValue().get("zj_pay_institution_id"));
        txRefundRequest.setTxSn(GUIDGenerator.genGUID());
        txRefundRequest.setPaymentTxSn(paymentTxSn);
        txRefundRequest.setRemark("");

        SlodonRefundRequest refundRequest = new SlodonRefundRequest();
        refundRequest.setRefundSn(refundSn);
        refundRequest.setRefundAmount(refundAmount);
        refundRequest.setTxRefundRequest(txRefundRequest);
        slodonPay.refund(refundRequest, PayConst.METHOD_ZJ);
    }

    /**
     * 查询充值信息
     *
     * @param rechargeSn 充值编号
     * @return 充值信息
     */
    private MemberBalanceRecharge getMemberBalanceRechargeBySn(String rechargeSn) {
        MemberBalanceRechargeExample example = new MemberBalanceRechargeExample();
        example.setRechargeSn(rechargeSn);
        List<MemberBalanceRecharge> list = memberBalanceRechargeModel.getMemberBalanceRechargeList(example, null);
        AssertUtil.notEmpty(list, "未生成充值订单");
        return list.get(0);
    }

    /**
     * 获取三方支付实际应支付金额
     *
     * @param rechargeSn 充值支付号，数据库表member_balance_recharge中的recharge_sn字段
     * @return 支付金额
     */
    private BigDecimal getRechargeAmountDb(String rechargeSn) {
        //查询充值明细
        return this.getMemberBalanceRechargeBySn(rechargeSn).getPayAmount();
    }

    /**
     * 给微信反回应答报文，防止重复回调
     *
     */
    private void sendMsg(HttpServletResponse response) {
        LinkedHashMap<String, String> responseMap = new LinkedHashMap<>();
        responseMap.put("return_code", RETURN_CODE_SUCCESS);
        responseMap.put("return_msg", RETURN_MSG_OK);
        String mapToXml = XmlUtil.mapToXmlStr(responseMap);
        response.setContentType("text/html;charset=utf-8");
        try (PrintWriter out = response.getWriter()) {
            out.print(mapToXml);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行微信退款
     *
     * @param refundSn 退款单号
     * @param tradeSn  交易流水号
     * @param payType  支付类型
     * @param totalFee 支付金额，单位分
     */
    private void wxRefund(String refundSn, String tradeSn, String payType, Integer totalFee) {
        SlodonRefundRequest refundRequest = new SlodonRefundRequest();

        //微信退款
        refundRequest.setWxPayProperties(payPropertiesUtil.getWxPayProperties(payType));
        //微信需要设置原支付金额
        BigDecimal payAmount = new BigDecimal(totalFee).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
        refundRequest.setTotalAmount(payAmount);
        refundRequest.setRefundAmount(payAmount);
        refundRequest.setTradeSn(tradeSn);
        refundRequest.setRefundSn(refundSn);
        try {
            SlodonRefundResponse refundResponse = slodonPay.refund(refundRequest, PayConst.METHOD_WX.toUpperCase());
            if (!refundResponse.getSuccess()) {
                log.error("微信重复支付退款失败，交易流水号：{}，失败原因：{}", tradeSn, refundResponse.getMsg());
            }
        } catch (Exception e) {
            log.error("微信重复支付退款失败，交易流水号：{}", tradeSn, e);
        }
    }

    /**
     * 支付宝退款
     *
     * @param refundSn     退款单号
     * @param tradeSn      交易流水号
     * @param refundAmount 退款金额
     */
    private void aliRefund(String refundSn, String tradeSn, BigDecimal refundAmount) {
        SlodonRefundRequest refundRequest = new SlodonRefundRequest();
        refundRequest.setAlipayProperties(payPropertiesUtil.getAliPayProperties());
        refundRequest.setTradeSn(tradeSn);
        refundRequest.setRefundSn(refundSn);
        refundRequest.setRefundAmount(refundAmount);
        try {
            SlodonRefundResponse refundResponse = slodonPay.refund(refundRequest, PayConst.METHOD_ALIPAY.toUpperCase());
            if (!refundResponse.getSuccess()) {
                log.error("支付宝重复支付退款失败，交易流水号：{}，失败原因：{}", tradeSn, refundResponse.getMsg());
            }
        } catch (Exception e) {
            log.error("支付宝重复支付退款失败，交易流水号：{}", tradeSn, e);
        }
    }
}
