package com.bonus.core.pay.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.bonus.core.common.constant.LeMqConstant;
import com.bonus.core.common.redis.RedisUtil;
import com.bonus.core.common.utils.JacksonUtil;
import com.bonus.core.common.utils.LogUtil;
import com.bonus.core.common.utils.TenantContextHolder;
import com.bonus.core.order.common.constants.OrderCacheConstants;
//import com.bonus.core.order.mq.MqUtil;
import com.bonus.core.order.mq.MqUtil;
import com.bonus.core.order.mq.constants.OrderMQConstants;
import com.bonus.core.pay.api.dto.UnifyPayDTO;
import com.bonus.core.pay.api.dto.UnifyPaySelectDTO;
import com.bonus.core.pay.api.dto.UnifyRefundDTO;
import com.bonus.core.pay.api.vo.UnifyPayVO;
import com.bonus.core.pay.api.vo.UnifyRefundVO;
import com.bonus.core.pay.common.business.PayBusiness;
import com.bonus.core.pay.common.business.RefundBusiness;
import com.bonus.core.pay.common.constants.PayCacheConstants;
import com.bonus.core.pay.common.constants.PayStateEnum;
import com.bonus.core.pay.common.constants.TradeTypeEnum;
import com.bonus.core.pay.common.model.TradeChannel;
import com.bonus.core.pay.common.model.TradeOrderRecharge;
import com.bonus.core.pay.common.model.TradeRecord;
import com.bonus.core.pay.common.po.PayResultPO;
import com.bonus.core.pay.common.po.PayResultQueryPO;
import com.bonus.core.pay.common.po.RefundResultPO;
import com.bonus.core.pay.common.service.PayTradeService;
import com.bonus.core.pay.common.service.TradeChannelService;
import com.bonus.core.pay.common.service.TradeRecordService;
import com.bonus.core.pay.common.vo.MobileNotifyVO;
import com.bonus.core.pay.common.vo.PayChannelSplitVO;
import com.bonus.core.pay.common.vo.TradeChannelVO;
import com.bonus.utils.id.Id;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class PayTradeServiceImpl implements PayTradeService {
   private static final Logger log = LoggerFactory.getLogger(PayTradeServiceImpl.class);
   @Lazy
   @Resource
   private TradeRecordService tradeRecordService;
   @Lazy
   @Resource
   private TradeChannelService tradeChannelService;
   @Autowired
   @Lazy
   protected PayBusiness payBusiness;
   @Autowired
   @Lazy
   protected RefundBusiness refundBusiness;

   @Override
   public boolean sendAutoPayResultQuery(UnifyPaySelectDTO paySelectDTO, LocalDateTime startTime, boolean endWithCancel) {
      log.warn("[交易记录结果查询]支付中发送查询 {}", paySelectDTO.getTradeId());
      PayResultQueryPO payResultQueryPO = new PayResultQueryPO();
      payResultQueryPO.setTradeId(paySelectDTO.getTradeId());
      payResultQueryPO.setTraceId(LogUtil.getCurrentTraceId());
      payResultQueryPO.setTenantId(TenantContextHolder.getTenantId());
      payResultQueryPO.setStartTime(startTime);
      payResultQueryPO.setIfAutoDelay(true);
      payResultQueryPO.setEndWithCancel(endWithCancel);
      payResultQueryPO.setPaySelectDTO(paySelectDTO);
      String key = OrderCacheConstants.payQueryTimesKey(paySelectDTO.getTradeId());
      Integer queryTriggerTime = RedisUtil.incr(key, 50L);
      int delay = OrderMQConstants.nextPayQueryDelay(queryTriggerTime);
      LocalDateTime endTime = startTime.plusSeconds((long) OrderMQConstants.PAY_QUERY_MAX_SECOND);
      if (delay != -1 && !endTime.isBefore(LocalDateTime.now())) {
         MqUtil.sendDelay(JacksonUtil.writeValueAsString(payResultQueryPO), LeMqConstant.Topic.PAY_ASYNC_PAY_QUERY, delay * 1000);
         return true;
      } else {
         log.warn("[订单异步保存]订单支付中查询结束 {}, {}", paySelectDTO.getTradeId(), startTime);
         return false;
      }
   }

   @Override
   public void sendResultMq(Long tradeId, int... delayMilliSeconds) {
      TradeRecord tradeRecord = this.tradeRecordService.getTradeRecordById(tradeId);

      try {
         if (TradeTypeEnum.isPayCategory(tradeRecord.getTradeType()) && PayStateEnum.isFinishedState(tradeRecord.getTradeState())) {
            PayResultPO payResultPO = PayResultPO.of(tradeRecord);
            if (ArrayUtil.isNotEmpty(delayMilliSeconds)) {
               MqUtil.sendDelay(JacksonUtil.writeValueAsString(payResultPO), LeMqConstant.Topic.PAY_RESULT, delayMilliSeconds[0]);
            } else {
               MqUtil.sendByTxEnd(JacksonUtil.writeValueAsString(payResultPO), LeMqConstant.Topic.PAY_RESULT);
            }
         } else if (TradeTypeEnum.isRefundCategory(tradeRecord.getTradeType()) && PayStateEnum.isFinishedState(tradeRecord.getTradeState())) {
            List<TradeChannelVO> tradeChannels = this.tradeChannelService.listChannelDetailByTradeId(tradeId);
            RefundResultPO payResultPO = RefundResultPO.of(tradeRecord, tradeChannels);
            if (ArrayUtil.isNotEmpty(delayMilliSeconds)) {
               MqUtil.sendDelay(JacksonUtil.writeValueAsString(payResultPO), LeMqConstant.Topic.REFUND_RESULT, delayMilliSeconds[0]);
            } else {
               MqUtil.sendByTxEnd(JacksonUtil.writeValueAsString(payResultPO), LeMqConstant.Topic.REFUND_RESULT);
            }
         }
      } catch (Exception var6) {
         log.error("[支付结果通知]发送失败", var6);
      }
   }

   @Transactional(
           propagation = Propagation.NOT_SUPPORTED
   )
   public void payResultHandler(MobileNotifyVO mobileNotifyVO) {
      log.info("[支付回调]处理结果: {}", mobileNotifyVO);
      TradeChannel tradeChannel = this.tradeChannelService.getByTradeChannelId(mobileNotifyVO.getTradeChannelId());
      if (tradeChannel == null) {
         log.warn("[支付回调处理] 未查询到渠道交易记录 {}", mobileNotifyVO.getTradeChannelId());
      } else if (PayStateEnum.isPayingState(mobileNotifyVO.getPayState())) {
         log.warn("[支付回调处理] 支付中");
      } else if (tradeChannel.getTradeState().equals(mobileNotifyVO.getPayState())) {
         log.warn("[支付回调处理] 支付状态未改变");
      } else if (!PayCacheConstants.lockTrade(tradeChannel.getTradeId())) {
         log.warn("[支付回调处理]支付中，跳过本次处理");
      } else {
         try {
            TradeRecord tradeRecord = this.tradeRecordService.getTradeRecordById(tradeChannel.getTradeId());
            if (PayStateEnum.isFinishedState(tradeRecord.getTradeState())) {
               TradeRecord newTradeRecord = this.packageTradeRecord(tradeRecord, tradeChannel);
               List<TradeOrderRecharge> newOrderRechargeList = this.packageTradeOrderRechargeList(tradeRecord, newTradeRecord.getId());
               this.tradeRecordService.insertTradeRecord(newTradeRecord);
               this.tradeRecordService.insertOrderRechargeTrade(newOrderRechargeList);
               this.tradeChannelService.updateTradeIdForRepair(tradeChannel.getTradeChannelId(), newTradeRecord.getId());
               tradeChannel.setTradeId(newTradeRecord.getId());
               tradeRecord = newTradeRecord;
            }

            this.tradeChannelService.updateTradeRecordChannelState(mobileNotifyVO.getTradeChannelId(),
                    mobileNotifyVO.getPayState(), mobileNotifyVO.getTradeResult(), mobileNotifyVO.getOutTradeNo(),
                    mobileNotifyVO.getPayAmount(), (JsonNode)null, mobileNotifyVO.getPayTime());
            List<TradeChannelVO> tradeChannelVOList = this.tradeChannelService.listChannelDetailByTradeId(tradeRecord.getId());
            if (TradeTypeEnum.isPayCategory(tradeRecord.getTradeType())) {
               UnifyPayDTO unifyPayDTO = UnifyPayDTO.of(tradeRecord);
               UnifyPayVO unifyPayVO = this.payBusiness.startPayFlow(unifyPayDTO, tradeChannelVOList);
               this.tradeRecordService.updateTradeRecordState(tradeChannel.getTradeId(), unifyPayVO);
            } else if (TradeTypeEnum.isRefundCategory(tradeRecord.getTradeType())) {
               TradeRecord originalTradeRecord = this.tradeRecordService.getTradeRecordById(tradeRecord.getRelatedTradeId());
               UnifyRefundDTO payRefundDTO = UnifyRefundDTO.ofNullable(tradeRecord);
               UnifyRefundVO unifyRefundVO = this.refundBusiness.startRefundFlow(originalTradeRecord,
                       tradeRecord.getId(), tradeRecord.getAmount(),
                       payRefundDTO != null ? payRefundDTO.getChannelAssignList()
                               : CollUtil.newArrayList(new UnifyRefundDTO.ChannelDetail[0]),
                       payRefundDTO != null ? payRefundDTO.getDetailList()
                               : CollUtil.newArrayList(new UnifyRefundDTO.Detail[0]), tradeChannelVOList);
               this.tradeRecordService.updateTradeRecordState(tradeChannel.getTradeId(), unifyRefundVO);
            }

            this.sendResultMq(tradeChannel.getTradeId());
         } finally {
            PayCacheConstants.unlockTrade(tradeChannel.getTradeId());
         }
      }
   }

   protected TradeRecord packageTradeRecord(TradeRecord oldTradeRecord, TradeChannel oldTradeChannel) {
      TradeRecord tradeRecord = new TradeRecord();
      tradeRecord.setId(Id.next());
      tradeRecord.setTradeState(PayStateEnum.PAY_INPROCESS.getKey());
      tradeRecord.setTradeResult(PayStateEnum.PAY_INPROCESS.getDesc());
      tradeRecord.setTradeType(oldTradeRecord.getTradeType());
      tradeRecord.setRelatedTradeId(oldTradeRecord.getId());
      tradeRecord.setCanteenId(oldTradeRecord.getCanteenId());
      tradeRecord.setCustId(oldTradeRecord.getCustId());
      tradeRecord.setSourceType(oldTradeRecord.getSourceType());
      tradeRecord.setCreateTime(oldTradeRecord.getCreateTime());
      tradeRecord.setTradeTime(oldTradeRecord.getTradeTime());
      tradeRecord.setAmount(oldTradeChannel.getAmount());
      tradeRecord.setPayType(oldTradeChannel.getPayType());
      tradeRecord.setPayChannel(oldTradeChannel.getPayChannel());
      tradeRecord.setOutTradeNo(oldTradeChannel.getOutTradeNo());
      tradeRecord.setRemark("数据修复");
      if (oldTradeRecord.getParam() != null) {
         UnifyPayDTO payDTO = (UnifyPayDTO)JacksonUtil.treeToValue(oldTradeRecord.getParam(), UnifyPayDTO.class);
         if (payDTO != null) {
            payDTO.setTradeId(tradeRecord.getId());
            payDTO.setRelatedTradeId(tradeRecord.getRelatedTradeId());
            payDTO.setPayChannelSplits(CollUtil.toList(PayChannelSplitVO.of(tradeRecord.getPayType(), tradeRecord.getPayChannel())));
         }

         tradeRecord.setParam(JacksonUtil.valueToTreeIgnoreNull(payDTO));
      }

      return tradeRecord;
   }

   protected List<TradeOrderRecharge> packageTradeOrderRechargeList(TradeRecord oldTradeRecord, Long newTradeId) {
      List<TradeOrderRecharge> tradeOrderRechargeList = this.tradeRecordService.queryOrderRechargeTrade(oldTradeRecord.getId());
      tradeOrderRechargeList.forEach((s) -> {
         s.setTradeId(newTradeId);
      });
      return tradeOrderRechargeList;
   }
}
