package com.xingxi.unifiedpay.service;

import com.xingxi.unifiedpay.bean.*;
import com.xingxi.unifiedpay.configuration.XingxiUnifiedpayProperties;
import com.xingxi.unifiedpay.domain.XingxiUnifiedPaymentInfoDTO;
import com.xingxi.unifiedpay.domain.XingxiUnifiedpayCallbackResultDTO;
import com.xingxi.unifiedpay.domain.MercUnifiedpayDTO;
import com.xingxi.unifiedpay.open.XingxiUnifiedpayCallbackServiceI;
import com.xingxi.unifiedpay.repository.XingxiUnifiedpayRepository;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.time.LocalDateTime;

@Slf4j
@XingxiUnifiedpay(name = "WXPay", dependsOn = {"wxPayService"})
@RequiredArgsConstructor
class WXPayUnifiedpayServiceImpl implements UnifiedpayServiceI {
    private final XingxiUnifiedpayRepository biandanUnifiedpayRepository;
    private final XingxiUnifiedpayCallbackServiceI biandanUnifiedpayCallbackServiceI;
    private final XingxiUnifiedpayProperties biandanUnifiedpayProperties;
    private final WxPayService wxPayService;

    @Override
    public BiandanUnifiedpayOrderResponse unifiedpay(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayOrderRequest request) throws Exception {

        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayConfig wxPayConfig = wxPayService.getConfig();

        WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = requestToWxPayUnifiedOrderV3Request(mercUnifiedpayDTO, request);
        WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = wxPayService.unifiedOrderV3(TradeTypeEnum.JSAPI, wxPayUnifiedOrderV3Request);

        XingxiUnifiedPaymentInfoDTO biandanUnifiedPaymentInfoDTO = buildBiandanUnifiedPaymentInfoDTO(request, wxPayUnifiedOrderV3Request, wxPayUnifiedOrderV3Result);
        biandanUnifiedPaymentInfoDTO.setCallUnifiedPayMercRole(mercUnifiedpayDTO.getCallUnifiedPayMercRole());
        biandanUnifiedPaymentInfoDTO.setCallUnifiedpayMercId(mercUnifiedpayDTO.getCallUnifiedpayMercId());

        Object payInfo = wxPayUnifiedOrderV3Result.getPayInfo(TradeTypeEnum.JSAPI, wxPayConfig.getAppId(), wxPayConfig.getMchId(), wxPayConfig.getPrivateKey());
        BiandanUnifiedpayOrderResponse response = new BiandanUnifiedpayOrderResponse();
        response.setPaySign(payInfo);
        response.setOriginalResponseObj(wxPayUnifiedOrderV3Result);
        response.setBiandanUnifiedPaymentInfoDTO(biandanUnifiedPaymentInfoDTO);
        return response;
    }


    @Override
    public BiandanUnifiedpayContinue2payResponse continue2pay(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayContinue2payRequest request) throws Exception {

        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayConfig wxPayConfig = wxPayService.getConfig();
        String prepayId = request.getPrepayId();

        WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = new WxPayUnifiedOrderV3Result();
        wxPayUnifiedOrderV3Result.setPrepayId(prepayId);

        Object payInfo = wxPayUnifiedOrderV3Result.getPayInfo(
                TradeTypeEnum.JSAPI,
                wxPayConfig.getAppId(),
                wxPayConfig.getMchId(),
                wxPayConfig.getPrivateKey()
        );

        BiandanUnifiedpayContinue2payResponse response = new BiandanUnifiedpayContinue2payResponse();
        response.setPaySign(payInfo);
        response.setOriginalResponseObj(wxPayUnifiedOrderV3Result);
        return response;

    }


    @Override
    public BiandanUnifiedpayCallbackResponse callback(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayCallbackRequest request) throws Exception {

        HttpServletRequest httpServletRequest = request.getHttpServletRequest();
        SignatureHeader signatureHeader = httpRequestToSignatureHeader(httpServletRequest);
        String s = StreamUtils.copyToString(httpServletRequest.getInputStream(), Charset.defaultCharset());

        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = wxPayService.parseOrderNotifyV3Result(s, signatureHeader);
        WxPayOrderNotifyV3Result.DecryptNotifyResult decryptNotifyResult = wxPayOrderNotifyV3Result.getResult();

        XingxiUnifiedpayCallbackResultDTO biandanUnifiedpayCallbackResultDTO = decryptNotifyResultToBiandanUnifiedpayCallbackResultDTO(decryptNotifyResult);
        biandanUnifiedpayCallbackServiceI.callbackFollowUpProcessing(biandanUnifiedpayCallbackResultDTO);

        return null;

    }


    @Override
    public BiandanUnifiedpayRefundResponse refund(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayRefundRequest request) throws Exception {

        String transactionId = request.getTransactionId();
        String outRefundNo = request.getOutRefundNo();
        String currency = request.getCurrency();
        Integer refundAmt = request.getRefundAmt();
        Integer orderOriginalTotalAmt = request.getOrderOriginalTotalAmt();

        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayConfig wxPayConfig = wxPayService.getConfig();

        WxPayRefundV3Request wxPayRefundV3Request = new WxPayRefundV3Request();
        wxPayRefundV3Request.setTransactionId(transactionId);
        wxPayRefundV3Request.setOutRefundNo(outRefundNo);

        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        amount.setCurrency(currency);
        amount.setRefund(refundAmt);
        amount.setTotal(orderOriginalTotalAmt);
        wxPayRefundV3Request.setAmount(amount);

        WxPayRefundV3Result wxPayRefundV3Result = wxPayService.refundV3(wxPayRefundV3Request);
        String refundId = wxPayRefundV3Result.getRefundId();
        String status = wxPayRefundV3Result.getStatus();

        BiandanUnifiedpayRefundResponse response = new BiandanUnifiedpayRefundResponse();
        response.setRefundId(refundId);
        response.setRefundStatus(status);
        response.setOriginalResponseObj(wxPayRefundV3Request);

        return response;
    }

    @Override
    public BiandanUnifiedpayQueryRefundResponse queryRefund(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayQueryRefundRequest request) throws Exception {

        String outRefundNo = request.getOutRefundNo();
        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayConfig wxPayConfig = wxPayService.getConfig();

        WxPayRefundQueryV3Request wxPayRefundQueryV3Request = new WxPayRefundQueryV3Request();
        wxPayRefundQueryV3Request.setOutRefundNo(outRefundNo);
        WxPayRefundQueryV3Result wxPayRefundQueryV3Result = wxPayService.refundQueryV3(wxPayRefundQueryV3Request);
        String refundId = wxPayRefundQueryV3Result.getRefundId();
        String status = wxPayRefundQueryV3Result.getStatus();

        BiandanUnifiedpayQueryRefundResponse response = new BiandanUnifiedpayQueryRefundResponse();
        response.setRefundId(refundId);
        response.setRefundStatus(status);
        response.setOriginalResponseObj(wxPayRefundQueryV3Request);
        return response;
    }


    @Override
    public BiandanUnifiedpayRedpackResponse sendMiniProgramRedpackV2(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayRedpackRequest request) throws Exception {
        return null;
    }

    @Override
    public BiandanUnifiedpayQueryOrderResponse queryOrder(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayQueryOrderRequest request) throws Exception {
        WxPayService wxPayService = this.switchoverTo(mercUnifiedpayDTO);
        WxPayConfig wxPayConfig = wxPayService.getConfig();
        String mchId = wxPayConfig.getMchId();
        String transactionId = request.getTransactionId();
        String outTradeNo = request.getOutTradeNo();

        WxPayOrderQueryV3Request wxPayOrderQueryV3Request = new WxPayOrderQueryV3Request();
        wxPayOrderQueryV3Request.setOutTradeNo(outTradeNo);
        wxPayOrderQueryV3Request.setMchid(mchId);
        wxPayOrderQueryV3Request.setTransactionId(transactionId);
        WxPayOrderQueryV3Result wxPayOrderQueryV3Result = wxPayService.queryOrderV3(wxPayOrderQueryV3Request);

        String tradeState = wxPayOrderQueryV3Result.getTradeState();
        String successTimeString = wxPayOrderQueryV3Result.getSuccessTime();
        String tradeType = wxPayOrderQueryV3Result.getTradeType();
        String bankType = wxPayOrderQueryV3Result.getBankType();
        String attach = wxPayOrderQueryV3Result.getAttach();

        BiandanUnifiedpayQueryOrderResponse response = new BiandanUnifiedpayQueryOrderResponse();
        response.setOriginalResponseObj(wxPayOrderQueryV3Request);
        response.setTransactionId(wxPayOrderQueryV3Result.getTransactionId());
        response.setTradeState(tradeState);
        response.setOutTradeNo(wxPayOrderQueryV3Result.getOutTradeNo());
        response.setSuccessTimeString(successTimeString);
        response.setTradeType(tradeType);
        response.setBankType(bankType);
        response.setAttach(attach);
        response.setCallUnifiedpayComponent("WXPay");
        return response;
    }

    private XingxiUnifiedpayCallbackResultDTO decryptNotifyResultToBiandanUnifiedpayCallbackResultDTO(WxPayOrderNotifyV3Result.DecryptNotifyResult decryptNotifyResult) {
        String tradeState = decryptNotifyResult.getTradeState();
        String outTradeNo = decryptNotifyResult.getOutTradeNo();
        String successTimeString = decryptNotifyResult.getSuccessTime();
        String tradeType = decryptNotifyResult.getTradeType();
        String bankType = decryptNotifyResult.getBankType();
        String attach = decryptNotifyResult.getAttach();
        String transactionId = decryptNotifyResult.getTransactionId();

        XingxiUnifiedpayCallbackResultDTO biandanUnifiedpayCallbackResultDTO = new XingxiUnifiedpayCallbackResultDTO();
        biandanUnifiedpayCallbackResultDTO.setTradeState(tradeState);
        biandanUnifiedpayCallbackResultDTO.setOutTradeNo(outTradeNo);
        biandanUnifiedpayCallbackResultDTO.setSuccessTimeString(successTimeString);
        biandanUnifiedpayCallbackResultDTO.setTradeType(tradeType);
        biandanUnifiedpayCallbackResultDTO.setBankType(bankType);
        biandanUnifiedpayCallbackResultDTO.setAttach(attach);
        biandanUnifiedpayCallbackResultDTO.setTransactionId(transactionId);
        biandanUnifiedpayCallbackResultDTO.setCallUnifiedpayComponent("WXPayCallback");

        biandanUnifiedpayCallbackResultDTO.setOriginalObj(decryptNotifyResult);
        return biandanUnifiedpayCallbackResultDTO;

    }


    private XingxiUnifiedPaymentInfoDTO buildBiandanUnifiedPaymentInfoDTO(BiandanUnifiedpayOrderRequest biandanUnifiedpayOrderRequest, WxPayUnifiedOrderV3Request request, WxPayUnifiedOrderV3Result result) {

        Long sellerId = biandanUnifiedpayOrderRequest.getSellerId();
        Long orderId = biandanUnifiedpayOrderRequest.getOrderId();
        String orderNo = biandanUnifiedpayOrderRequest.getOrderNo();
        String orderStatus = biandanUnifiedpayOrderRequest.getOrderStatus();
        Integer txAmt = biandanUnifiedpayOrderRequest.getTxAmt();
        String unifiedpayOutTradeNo = biandanUnifiedpayOrderRequest.getUnifiedpayOutTradeNo();

        XingxiUnifiedPaymentInfoDTO paymentInfo = new XingxiUnifiedPaymentInfoDTO();
        paymentInfo.setSubCurrency("CNY");
        paymentInfo.setProfitSharing("0");
        paymentInfo.setPayKind("WXPay");
        paymentInfo.setSellerId(sellerId);
        paymentInfo.setMchid(request.getMchid());
        paymentInfo.setNotifyUrl(request.getNotifyUrl());
        paymentInfo.setWxOpenId(request.getPayer().getOpenid());
        paymentInfo.setOrderId(orderId);
        paymentInfo.setOrderStatus(orderStatus);
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setOutTradeNo(unifiedpayOutTradeNo);
        paymentInfo.setPayAmount(txAmt);
        paymentInfo.setTimeStart(LocalDateTime.now());
        paymentInfo.setTimeExpire(parseTimeExpire(request.getTimeExpire()));
        paymentInfo.setPrepayId(result.getPrepayId());

        return paymentInfo;
    }


    private WxPayUnifiedOrderV3Request requestToWxPayUnifiedOrderV3Request(MercUnifiedpayDTO mercUnifiedpayDTO, BiandanUnifiedpayOrderRequest request) {

        String unifiedpayCallbackUrl = biandanUnifiedpayProperties.getUnifiedpayCallbackUrl();
        if (!StringUtils.hasLength(unifiedpayCallbackUrl)) {
            log.debug("支付回调地址不能为空,格式列如[https://xxx.com/wxpay/%s/callback],域名自行替换");
            throw new RuntimeException("支付回调地址不能为空");
        }

        String wxOpenId = request.getPayerOpenid();
        Long sellerId = request.getSellerId();
        String unifiedpayOutTradeNo = request.getUnifiedpayOutTradeNo();
        Integer txAmt = request.getTxAmt();

        WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = new WxPayUnifiedOrderV3Request();
        wxPayUnifiedOrderV3Request.setDescription("恩瑞斯购物");
        wxPayUnifiedOrderV3Request.setOutTradeNo(unifiedpayOutTradeNo);

        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
        payer.setOpenid(wxOpenId);
        wxPayUnifiedOrderV3Request.setPayer(payer);

        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
        amount.setCurrency("CNY");
        amount.setTotal(txAmt);
        wxPayUnifiedOrderV3Request.setAmount(amount);
        wxPayUnifiedOrderV3Request.setTimeExpire(timeExpire());
        wxPayUnifiedOrderV3Request.setNotifyUrl(String.format(unifiedpayCallbackUrl, sellerId));

        return wxPayUnifiedOrderV3Request;

    }

    private SignatureHeader httpRequestToSignatureHeader(HttpServletRequest request) {
        String serial = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        SignatureHeader signatureHeader = new SignatureHeader();
        signatureHeader.setSerial(serial);
        signatureHeader.setSignature(signature);
        signatureHeader.setNonce(nonceStr);
        signatureHeader.setTimeStamp(timestamp);
        return signatureHeader;
    }

    private WxPayService switchoverTo(MercUnifiedpayDTO mercUnifiedpayDTO) {
        WxPayConfig wxPayConfig = buildWXPayConfig(mercUnifiedpayDTO);
        String wxMchId = wxPayConfig.getMchId();
        if (!wxPayService.switchover(wxMchId)) {
            log.debug("缓存中没有找到商户[" + wxMchId + "]配置,新添加[" + wxMchId + "]到缓存中");
            wxPayService.addConfig(wxMchId, wxPayConfig);
        }

        return this.wxPayService.switchoverTo(wxMchId);
    }

    private WxPayConfig buildWXPayConfig(MercUnifiedpayDTO mercUnifiedpayDTO) {

//        Long callUnifiedpayMercId = mercUnifiedpayDTO.getCallUnifiedpayMercId();
//        MercUnifiedpayConfigDTO mercUnifiedpayConfigDTO = biandanUnifiedpayRepository.selectMercUnifiedpayConfigDTOByCallUnifiedpayMercId(callUnifiedpayMercId);

//        MercUnifiedpayConfigDTO mercUnifiedpayConfigDTO = new MercUnifiedpayConfigDTO();
//        mercUnifiedpayConfigDTO.setApiV3Key(wxPayService.getApi());
//        mercUnifiedpayConfigDTO.setMchId(mercWxapp.getWxMchId());
//        mercUnifiedpayConfigDTO.setAppId(mercWxapp.getWxPayId());
//        mercUnifiedpayConfigDTO.setPrivateKeyContent(getCertBytesFromOSSByURL(mercWxapp.getWxKeyFile()));
//        mercUnifiedpayConfigDTO.setPrivateCertContent(getCertBytesFromOSSByURL(mercWxapp.getWxCertFile()));
//
//        String appId = mercUnifiedpayConfigDTO.getAppId();
//        String wxMchId = mercUnifiedpayConfigDTO.getMchId();
//        String wxApiV3key = mercUnifiedpayConfigDTO.getApiV3Key();
//        byte[] privateKeyContent = mercUnifiedpayConfigDTO.getPrivateKeyContent();
//        byte[] privateCertContent = mercUnifiedpayConfigDTO.getPrivateCertContent();

        WxPayConfig wxPayConfig = wxPayService.getConfig();
//        wxPayConfig.setAppId(appId);
//        wxPayConfig.setMchId(wxMchId);
//        wxPayConfig.setApiV3Key(wxApiV3key);
//        wxPayConfig.setPrivateKeyContent(privateKeyContent);
//        wxPayConfig.setPrivateCertContent(privateCertContent);

        ByteArrayInputStream inputStream = new ByteArrayInputStream(wxPayConfig.getPrivateKeyContent());
        wxPayConfig.setPrivateKey(PemUtils.loadPrivateKey(inputStream));
        return wxPayConfig;
    }

}
