package com.bonus.core.pay.api.vo;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.bonus.constant.LeConstants;
import com.bonus.core.order.utils.LeNumUtil;
import com.bonus.core.pay.api.dto.UnifyPayDTO;
import com.bonus.core.pay.channel.account.vo.AccPayResultVO;
import com.bonus.core.pay.common.constants.PayChannelEnum;
import com.bonus.core.pay.common.constants.PayStateEnum;
import com.bonus.core.pay.common.model.TradeRecord;
import com.bonus.core.pay.common.vo.*;
import com.bonus.framework.config.deserializer.DateTimeDeserializer;
import com.bonus.framework.config.serializer.DateTimeSerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class UnifyPayVO {
   private Integer code;
   private String msg;
   private Long tradeId;
   private String outTradeNo;
   private BigDecimal amount;
   private BigDecimal realAmount;
   private Integer tradeType;
   private Integer payType;
   private Integer payChannel;
   private String payParamStr;
   private String qrCodeInfo;
   @JsonSerialize(
      using = DateTimeSerializer.YYYY_MM_DD_HH_MM_SS.class
   )
   @JsonDeserialize(
      using = DateTimeDeserializer.class
   )
   private LocalDateTime payTime;
   private Integer ifTrail;
   private Integer ifFastAsyncPay;
   private Long custId;
   private String custName;
   private String orgFullName;
   private BigDecimal accPayAmount;
   private BigDecimal outPayAmount;
   private AccPayResultVO accPayResultVO;
   private List<TradeChannelVO> detailList = CollUtil.newArrayList(new TradeChannelVO[0]);

   public String getMsg() {
      return CharSequenceUtil.isNotEmpty(this.msg) ? this.msg : PayStateEnum.getDesc(this.code);
   }

   public BigDecimal getAccPayAmount() {
      return (BigDecimal)this.detailList.stream().filter((s) -> {
         return PayStateEnum.isPaidState(s.getTradeState());
      }).filter((s) -> {
         return PayChannelEnum.ACC.getKey().equals(s.getPayChannel());
      }).map(TradeChannelVO::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
   }

   public BigDecimal getOutPayAmount() {
      return (BigDecimal)this.detailList.stream().filter((s) -> {
         return PayStateEnum.isPaidState(s.getTradeState());
      }).filter((s) -> {
         return !PayChannelEnum.ACC.getKey().equals(s.getPayChannel()) && !PayChannelEnum.COUPON.getKey().equals(s.getPayChannel());
      }).map(TradeChannelVO::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
   }

   public String getOutTradeNo() {
      return (String)this.detailList.stream().sorted(Comparator.comparing(TradeChannelVO::getTradeChannelId).reversed()).map(TradeChannelVO::getOutTradeNo).filter(CharSequenceUtil::isNotEmpty).findFirst().orElse((String) null);
   }

   public Integer getPayType() {
      if (PayStateEnum.isPaidState(this.code)) {
         Set<Integer> payTypes = (Set)this.detailList.stream().filter((s) -> {
            return PayStateEnum.isPaidState(s.getTradeState());
         }).sorted(Comparator.comparing(TradeChannelVO::getTradeChannelId)).map(TradeChannelVO::getPayType).collect(Collectors.toSet());
         return payTypes.isEmpty() ? this.payType : (Integer)CollUtil.getLast(payTypes);
      } else {
         return this.payType;
      }
   }

   public Integer getPayChannel() {
      if (PayStateEnum.isFinishedState(this.code)) {
         Set<Integer> payChannels = (Set)this.detailList.stream().filter((s) -> {
            return PayStateEnum.isPaidState(s.getTradeState());
         }).map(TradeChannelVO::getPayChannel).collect(Collectors.toSet());
         if (payChannels.isEmpty()) {
            return this.payChannel;
         } else {
            return payChannels.size() == 1 ? (Integer)CollUtil.getFirst(payChannels) : PayChannelEnum.MIXTURE.getKey();
         }
      } else {
         return this.payChannel;
      }
   }

   public BigDecimal getRealAmount() {
      return PayStateEnum.isPartPayState(this.code) ? this.amount : (BigDecimal)this.detailList.stream().filter((s) -> {
         return PayStateEnum.isPaySuccessState(s.getTradeState());
      }).map(TradeChannelVO::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
   }

   public LocalDateTime getPayTime() {
      LocalDateTime resultTime = null;
      if (PayStateEnum.isPaySuccessState(this.code) && CollUtil.isNotEmpty(this.detailList)) {
         resultTime = ((TradeChannelVO)CollUtil.getLast(this.detailList)).getTradeTime();
      }

      return resultTime != null ? resultTime : this.payTime;
   }

   public boolean isAsyncPay() {
      return CharSequenceUtil.isNotBlank(this.payParamStr) || CharSequenceUtil.isNotBlank(this.qrCodeInfo) || LeConstants.COMMON_YES.equals(this.ifFastAsyncPay) && PayStateEnum.isPayingState(this.code) || this.detailList.stream().anyMatch(TradeChannelVO::ifWechatScorePaying) || this.detailList.stream().anyMatch(TradeChannelVO::ifAliAsyncFacePaying);
   }

   public boolean hasUnpaidBalance() {
      return PayStateEnum.isFailedState(this.code) && this.accPayResultVO != null && this.accPayResultVO.getUnpaidBalance() != null && this.accPayResultVO.getUnpaidBalance().compareTo(BigDecimal.ZERO) > 0;
   }

   public BigDecimal calcUnpaidAmount() {
      BigDecimal paidAmount = (BigDecimal)this.detailList.stream().filter((s) -> {
         return PayStateEnum.isPaidState(s.getTradeState());
      }).map(TradeChannelVO::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
      return NumberUtil.sub(this.amount, paidAmount);
   }

   public boolean enoughPaid() {
      return this.calcUnpaidAmount().compareTo(BigDecimal.ZERO) <= 0;
   }

   public void fillChannelPayVO(ChannelPayVO channelPayVO) {
      this.setCode(channelPayVO.getCode());
      this.setMsg(channelPayVO.getMsg());
      if (channelPayVO.getAccPayResultVO() != null) {
         this.accPayResultVO = channelPayVO.getAccPayResultVO();
      }

      if (CharSequenceUtil.isNotEmpty(channelPayVO.getPayParamStr())) {
         this.setPayParamStr(channelPayVO.getPayParamStr());
      }

      if (CharSequenceUtil.isNotEmpty(channelPayVO.getQrCodeInfo())) {
         this.setQrCodeInfo(channelPayVO.getQrCodeInfo());
      }

      if (!LeNumUtil.isValidId(this.getCustId()) && LeNumUtil.isValidId(channelPayVO.getCustId())) {
         this.setCustId(channelPayVO.getCustId());
      }

      if (CharSequenceUtil.isBlank(this.getCustName()) && CharSequenceUtil.isNotBlank(channelPayVO.getCustName())) {
         this.setCustName(channelPayVO.getCustName());
      }

      if (CharSequenceUtil.isBlank(this.getOrgFullName()) && CharSequenceUtil.isNotBlank(channelPayVO.getOrgFullName())) {
         this.setOrgFullName(channelPayVO.getOrgFullName());
      }

   }

   public void fillMixPayResultVO(MixPayResultVO mixPayResultVO) {
      if (mixPayResultVO.getAccPayResultVO() != null) {
         this.accPayResultVO = mixPayResultVO.getAccPayResultVO();
      }

   }

   public MobilePayVO convertToMobilePayVO() {
      MobilePayVO payVO = new MobilePayVO();
      BeanUtil.copyProperties(this, payVO, new String[0]);
      return payVO;
   }

   public AndroidPayVO convertToAndroidPayVO() {
      AndroidPayVO payVO = new AndroidPayVO();
      BeanUtil.copyProperties(this, payVO, new String[0]);
      return payVO;
   }

   public static UnifyPayVO of(TradeRecord tradeRecord, List<TradeChannelVO> tradeChannels) {
      UnifyPayVO resultVO = new UnifyPayVO();
      BeanUtil.copyProperties(tradeRecord, resultVO, new String[0]);
      if (PayStateEnum.isPaidState(tradeRecord.getTradeState())) {
         resultVO.setPayTime(tradeRecord.getTradeTime());
      }

      resultVO.setCode(tradeRecord.getTradeState());
      resultVO.setMsg(tradeRecord.getTradeResult());
      resultVO.setDetailList(CollUtil.isNotEmpty(tradeChannels) ? CollUtil.newArrayList(tradeChannels) : CollUtil.newArrayList(new TradeChannelVO[0]));
      return resultVO;
   }

   public static UnifyPayVO newInstance(UnifyPayDTO payDTO, List<TradeChannelVO> tradeChannels) {
      UnifyPayVO payVO = new UnifyPayVO();
      payVO.setTradeId(payDTO.getTradeId());
      payVO.setCode(PayStateEnum.PAY_INPROCESS.getKey());
      payVO.setMsg(PayStateEnum.PAY_INPROCESS.getDesc());
      payVO.setTradeId(payDTO.getTradeId());
      payVO.setAmount(payDTO.getAmount());
      payVO.setPayType(payDTO.getPayType());
      payVO.setTradeType(payDTO.getTradeType());
      payVO.setPayChannel(payDTO.getPayChannel());
      payVO.setCustId(payDTO.getCustId());
      payVO.setCustName(payDTO.getCustName());
      payVO.setOrgFullName(payVO.getOrgFullName());
      payVO.setDetailList(CollUtil.isNotEmpty(tradeChannels) ? CollUtil.newArrayList(tradeChannels) : CollUtil.newArrayList(new TradeChannelVO[0]));
      return payVO;
   }

   public Integer getCode() {
      return this.code;
   }

   public Long getTradeId() {
      return this.tradeId;
   }

   public BigDecimal getAmount() {
      return this.amount;
   }

   public Integer getTradeType() {
      return this.tradeType;
   }

   public String getPayParamStr() {
      return this.payParamStr;
   }

   public String getQrCodeInfo() {
      return this.qrCodeInfo;
   }

   public Integer getIfTrail() {
      return this.ifTrail;
   }

   public Integer getIfFastAsyncPay() {
      return this.ifFastAsyncPay;
   }

   public Long getCustId() {
      return this.custId;
   }

   public String getCustName() {
      return this.custName;
   }

   public String getOrgFullName() {
      return this.orgFullName;
   }

   public AccPayResultVO getAccPayResultVO() {
      return this.accPayResultVO;
   }

   public List<TradeChannelVO> getDetailList() {
      return this.detailList;
   }

   public void setCode(final Integer code) {
      this.code = code;
   }

   public void setMsg(final String msg) {
      this.msg = msg;
   }

   public void setTradeId(final Long tradeId) {
      this.tradeId = tradeId;
   }

   public void setOutTradeNo(final String outTradeNo) {
      this.outTradeNo = outTradeNo;
   }

   public void setAmount(final BigDecimal amount) {
      this.amount = amount;
   }

   public void setRealAmount(final BigDecimal realAmount) {
      this.realAmount = realAmount;
   }

   public void setTradeType(final Integer tradeType) {
      this.tradeType = tradeType;
   }

   public void setPayType(final Integer payType) {
      this.payType = payType;
   }

   public void setPayChannel(final Integer payChannel) {
      this.payChannel = payChannel;
   }

   public void setPayParamStr(final String payParamStr) {
      this.payParamStr = payParamStr;
   }

   public void setQrCodeInfo(final String qrCodeInfo) {
      this.qrCodeInfo = qrCodeInfo;
   }

   @JsonDeserialize(
      using = DateTimeDeserializer.class
   )
   public void setPayTime(final LocalDateTime payTime) {
      this.payTime = payTime;
   }

   public void setIfTrail(final Integer ifTrail) {
      this.ifTrail = ifTrail;
   }

   public void setIfFastAsyncPay(final Integer ifFastAsyncPay) {
      this.ifFastAsyncPay = ifFastAsyncPay;
   }

   public void setCustId(final Long custId) {
      this.custId = custId;
   }

   public void setCustName(final String custName) {
      this.custName = custName;
   }

   public void setOrgFullName(final String orgFullName) {
      this.orgFullName = orgFullName;
   }

   public void setAccPayAmount(final BigDecimal accPayAmount) {
      this.accPayAmount = accPayAmount;
   }

   public void setOutPayAmount(final BigDecimal outPayAmount) {
      this.outPayAmount = outPayAmount;
   }

   public void setAccPayResultVO(final AccPayResultVO accPayResultVO) {
      this.accPayResultVO = accPayResultVO;
   }

   public void setDetailList(final List<TradeChannelVO> detailList) {
      this.detailList = detailList;
   }

}
