package com.yonge.toolset.payment.original.wx;

import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import com.yonge.toolset.base.exception.BizException;
import com.yonge.toolset.base.result.BaseResult;
import com.yonge.toolset.base.util.StringUtil;
import com.yonge.toolset.payment.base.PaymentTemplate;
import com.yonge.toolset.payment.base.enums.*;
import com.yonge.toolset.payment.base.model.ClosePayment;
import com.yonge.toolset.payment.base.model.OpenAuth;
import com.yonge.toolset.payment.base.model.Payment;
import com.yonge.toolset.payment.base.model.RefundBill;
import com.yonge.toolset.payment.base.model.callback.ExecutePaymentCallBack;
import com.yonge.toolset.payment.base.model.callback.PaymentCallBack;
import com.yonge.toolset.payment.base.model.callback.RefundPaymentCallBack;
import com.yonge.toolset.payment.core.props.PaymentProperties;
import com.yonge.toolset.payment.core.service.SysConfigPaymentService;
import com.yonge.toolset.payment.original.wx.constant.WxpayConstant;
import com.yonge.toolset.utils.web.WebUtil;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.*;

@Component
public class OriginalWxAppTemplate implements PaymentTemplate {

    private final static Logger log = LoggerFactory.getLogger(OriginalWxAppTemplate.class);

    @Autowired
    private PaymentProperties paymentProperties;

    @Autowired
    private SysConfigPaymentService configPaymentService;

    @Resource(name = "studentWxService")
    private WxPayService studentWxService;
    @Resource(name = "teacherWxService")
    private WxPayService teacherWxService;

    @Override
    public BaseResult<Map<String, Object>> getOpenAuthMsg(OpenAuth openAuth) {
        return null;
    }

    @Override
    public BaseResult<Payment> executePayment(Payment payment) {
        String APP_ID = configPaymentService.getPaymentConfig(OpenEnum.ORIGINAL, payment.getPaymentClient(), WxpayConstant.WX_APPID).getParamValue();
        String MERCHANT_ID = configPaymentService.getPaymentConfig(OpenEnum.ORIGINAL, WxpayConstant.WX_MERCHANT_ID).getParamValue();

        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setAppid(APP_ID);
        request.setMchid(MERCHANT_ID);
        request.setDescription(payment.getOrderDesc());
        request.setOutTradeNo(payment.getPaymentNo());
        request.setNotifyUrl(paymentProperties.getNotifyUrl()
                + "/" + payment.getOpenType().getCode()
                + "/" + payment.getPaymentClient().getCode()
                + "/" + payment.getPayChannel().getCode()
                + "/executePayment");

        if (StringUtil.isEmpty(payment.getPayAmt())) {
            return BaseResult.failed("微信APP支付金额异常");
        }

        BigDecimal payAmt = new BigDecimal(payment.getPayAmt()).multiply(new BigDecimal("100"));

        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
        amount.setTotal(payAmt.abs().intValue());
        request.setAmount(amount);

        try {
            Object orderV3 = getWxPayService(payment.getPaymentClient())
                    .createOrderV3(TradeTypeEnum.APP, request);
            log.info("支付返回：{}", JSONObject.toJSONString(orderV3));
            Map<String, String> stringStringMap = SignUtils.xmlBean2Map(orderV3);
            String msg = buildMessage(
                    APP_ID,
                    Long.parseLong(stringStringMap.get("timestamp")),
                    stringStringMap.get("noncestr"),
                    stringStringMap.get("prepayid")
            );

            String sign = sign(msg.getBytes(StandardCharsets.UTF_8), payment.getPaymentClient());
            stringStringMap.put("sign", sign);

            payment.setPayInfo(JSONObject.toJSONString(stringStringMap));

            BaseResult<Payment> paymentBaseResult = queryPayment(payment);
            if (paymentBaseResult.getStatus()) {
                payment.setId(paymentBaseResult.getData().getId());
            }
            return BaseResult.succeed(payment);
        } catch (WxPayException e) {
            log.error("微信APP支付请求失败:{}", e.toString());
            return BaseResult.failed(e.toString());
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResult.failed("微信APP支付请求失败");
        }
    }

    @Override
    public BaseResult<Payment> queryPayment(Payment payment) {
        String MERCHANT_ID = configPaymentService.getPaymentConfig(OpenEnum.ORIGINAL, WxpayConstant.WX_MERCHANT_ID).getParamValue();

        WxPayOrderQueryV3Request param = new WxPayOrderQueryV3Request();
        param.setMchid(MERCHANT_ID);
        if (!StringUtil.isEmpty(payment.getId())) {
            param.setTransactionId(payment.getId());
        }
        param.setOutTradeNo(payment.getPaymentNo());

        try {
            WxPayOrderQueryV3Result res = getWxPayService(payment.getPaymentClient())
                    .queryOrderV3(param);
            log.info("查询返回：{}", JSONObject.toJSONString(res));

            payment.setId(res.getTransactionId());
            String tradeState = res.getTradeState();
            if ("SUCCESS".equals(tradeState)) {
                payment.setStatus(TradeStatusEnum.succeeded);
            } else if ("REFUND".equals(tradeState)) {
                payment.setStatus(TradeStatusEnum.close);
            } else if ("NOTPAY".equals(tradeState)) {
                payment.setStatus(TradeStatusEnum.pending);
            } else if ("CLOSED".equals(tradeState)) {
                payment.setStatus(TradeStatusEnum.close);
            }
            return BaseResult.succeed(payment);
        } catch (WxPayException e) {
            log.error("微信APP支付查询请求失败:{}", e.toString());
            return BaseResult.failed(e.toString());
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResult.failed("微信APP支付查询请求失败");
        }
    }

    @Override
    public BaseResult<ClosePayment> closePayment(ClosePayment closePayment) {
        if (StringUtil.isEmpty(closePayment.getPaymentNo())) {
            return BaseResult.failed("缺失渠道关单必要参数");
        }

        String MERCHANT_ID = configPaymentService.getPaymentConfig(OpenEnum.ORIGINAL, WxpayConstant.WX_MERCHANT_ID).getParamValue();

        WxPayOrderCloseV3Request param = new WxPayOrderCloseV3Request();
        param.setMchid(MERCHANT_ID);
        param.setOutTradeNo(closePayment.getPaymentNo());
        closePayment.setHasNotify(false);
        try {
            getWxPayService(closePayment.getPaymentClient())
                    .closeOrderV3(param);
            closePayment.setStatus(TradeStatusEnum.succeeded);
            return BaseResult.succeed(closePayment);
        } catch (WxPayException e) {
            log.error("微信APP支付关单请求失败:{}", e.toString());
            return BaseResult.failed(e.toString());
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResult.failed("微信APP支付关单请求失败");
        }
    }

    @Override
    public BaseResult<RefundBill> refundPayment(RefundBill refundBill) {
        WxPayRefundV3Request param = new WxPayRefundV3Request();
        param.setTransactionId(refundBill.getTradeNo());
        param.setOutTradeNo(refundBill.getPaymentNo());
        param.setOutRefundNo(refundBill.getRefundNo());
        param.setReason(refundBill.getReason());

        param.setNotifyUrl(paymentProperties.getNotifyUrl()
                + "/" + refundBill.getOpenType().getCode()
                + "/" + refundBill.getPaymentClient().getCode()
                + "/" + refundBill.getPayChannel().getCode()
                + "/refundPayment");

        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        BigDecimal refundAmt = refundBill.getRefundAmt().multiply(new BigDecimal("100"));
        amount.setRefund(refundAmt.intValue());

        BigDecimal orderAmt = refundBill.getOrderAmt().multiply(new BigDecimal("100"));
        amount.setTotal(orderAmt.intValue());

        amount.setCurrency("CNY");
        param.setAmount(amount);

        try {
            WxPayRefundV3Result res = getWxPayService(refundBill.getPaymentClient())
                    .refundV3(param);
            log.info("退款返回：{}", JSONObject.toJSONString(res));

            String refund_id = res.getRefundId();
            refundBill.setId(refund_id);
            return BaseResult.succeed(refundBill);
        } catch (WxPayException e) {
            log.error("微信APP支付请求失败:{}", e.toString());
            throw new BizException(e.toString());
        } catch (Exception e) {
            e.printStackTrace();
            throw new BizException("微信APP支付关单请求失败");
        }
    }

    @Override
    public BaseResult<PaymentCallBack> analysisNotice(OpenEnum openType, PaymentClientEnum paymentClient, PayChannelEnum payChannel, MethodNameEnum methodName, HttpServletRequest request) {
        //支付回调
        SignatureHeader header = new SignatureHeader();
        header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
        header.setNonce(request.getHeader("Wechatpay-Nonce"));
        header.setSerial(request.getHeader("Wechatpay-Serial"));
        header.setSignature(request.getHeader("Wechatpay-Signature"));

        String params = WebUtil.getBodyData(request);
        log.info("微信APP回调:{}", params);

        PaymentCallBack paymentCallBack = new PaymentCallBack();
        paymentCallBack.setMethodName(methodName);

        Map<String, Object> resMsg = new HashMap<>();
        try {
            if (MethodNameEnum.executePayment.equals(methodName)) {
                WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = getWxPayService(paymentClient).parseOrderNotifyV3Result(params, header);
                log.info("微信APP回调-支付请求:{}", JSONObject.toJSONString(wxPayOrderNotifyV3Result));

                //封装支付回调对象
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
                paymentCallBack.setId(result.getTransactionId());
                paymentCallBack.setPaymentNo(result.getOutTradeNo());

                String tradeState = result.getTradeState();
                if ("SUCCESS".equals(tradeState)) {
                    paymentCallBack.setStatus(TradeStatusEnum.succeeded);
                } else if ("NOTPAY".equals(tradeState) || "CLOSED".equals(tradeState) || "REFUND".equals(tradeState)) {
                    paymentCallBack.setStatus(TradeStatusEnum.close);
                    paymentCallBack.setErrMsg(result.getTradeStateDesc());
                } else if ("PAYERROR".equals(tradeState)) {
                    paymentCallBack.setStatus(TradeStatusEnum.failed);
                    paymentCallBack.setErrMsg(result.getTradeStateDesc());
                }

                ExecutePaymentCallBack executePayment = new ExecutePaymentCallBack();
                if (!StringUtil.isEmpty(result.getAmount().getTotal())) {

                    BigDecimal payAmt = new BigDecimal(result.getAmount().getTotal()).divide(new BigDecimal("100"));
                    executePayment.setPayAmt(payAmt.toString());

                    //微信不返回手续费，通过
                    String WX_FEE_RATE = configPaymentService.getPaymentConfig(OpenEnum.ORIGINAL, WxpayConstant.WX_FEE_RATE).getParamValue();

                    BigDecimal feeRate = new BigDecimal(StringUtil.isEmpty(WX_FEE_RATE) ? "0.006" : WX_FEE_RATE);

                    executePayment.setFeeAmt(payAmt.multiply(feeRate).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
                }
                if (!StringUtil.isEmpty(result.getSuccessTime())) {
                    Date date = new DateTime(result.getSuccessTime()).toDate();
                    executePayment.setTradeTime(date);
                }
                paymentCallBack.setExecutePaymentCallBack(executePayment);
            } else if (MethodNameEnum.refundPayment.equals(methodName)) {
                WxPayRefundNotifyV3Result wxPayRefundNotifyV3Result = getWxPayService(paymentClient).parseRefundNotifyV3Result(params, header);
                log.info("微信APP回调-退款请求:{}", JSONObject.toJSONString(wxPayRefundNotifyV3Result));
                //封装退款回调对象
                WxPayRefundNotifyV3Result.DecryptNotifyResult result = wxPayRefundNotifyV3Result.getResult();
                paymentCallBack.setId(result.getTransactionId());
                paymentCallBack.setPaymentNo(result.getOutTradeNo());

                String refundStatus = result.getRefundStatus();
                if ("SUCCESS".equals(refundStatus)) {
                    paymentCallBack.setStatus(TradeStatusEnum.succeeded);
                } else if ("CLOSED".equals(refundStatus)) {
                    paymentCallBack.setStatus(TradeStatusEnum.failed);
                    paymentCallBack.setErrMsg("退款关闭");
                } else if ("ABNORMAL".equals(refundStatus)) {
                    paymentCallBack.setStatus(TradeStatusEnum.failed);
                    paymentCallBack.setErrMsg("退款失败");
                }

                RefundPaymentCallBack refundPayment = new RefundPaymentCallBack();
                refundPayment.setBillNo(result.getOutRefundNo());
                refundPayment.setTransNo(result.getRefundId());
                refundPayment.setRefundAmt(
                        new BigDecimal(result.getAmount().getRefund())
                                .divide(new BigDecimal("100"))
                                .toString()
                );

                if (!StringUtil.isEmpty(result.getSuccessTime())) {
                    Date date = new DateTime(result.getSuccessTime()).toDate();
                    refundPayment.setTradeTime(date);
                }
                paymentCallBack.setRefundPaymentCallBack(refundPayment);
            }
            resMsg.put("code", "SUCCESS");
            paymentCallBack.setResMsg(resMsg);
            return BaseResult.succeed(paymentCallBack);
        } catch (WxPayException e) {
            resMsg.put("code", "FAIL");
            resMsg.put("message", "失败");
            paymentCallBack.setResMsg(resMsg);
            log.error("微信回调对象转换失败, param is {} , err is {}", params, e.toString());
            return BaseResult.failed(HttpStatus.INTERNAL_SERVER_ERROR, paymentCallBack, e.toString());
        } catch (Exception e) {
            e.printStackTrace();
            resMsg.put("code", "FAIL");
            resMsg.put("message", "失败");
            paymentCallBack.setResMsg(resMsg);
            return BaseResult.failed(HttpStatus.INTERNAL_SERVER_ERROR, paymentCallBack, "解析异常");
        }
    }

    String sign(byte[] message, PaymentClientEnum paymentClient) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = getWxPayService(paymentClient).getConfig().getPrivateKey();
        sign.initSign(privateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    String buildMessage(String appid, long timestamp, String nonceStr, String prepay_id) {
        return appid + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + prepay_id + "\n";
    }

    private WxPayService getWxPayService(PaymentClientEnum paymentClient) {
        if (PaymentClientEnum.STUDENT.equals(paymentClient)) {
            return this.studentWxService;
        }
        if (PaymentClientEnum.TEACHER.equals(paymentClient)) {
            return this.teacherWxService;
        }
        throw new BizException("未知的客户端类型");
    }
}
