package com.red.circle.console.app.service.app.user;

import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.console.app.convertor.WalletAppConvertor;
import com.red.circle.console.app.dto.clienobject.user.UserFreightBalanceCO;
import com.red.circle.console.app.dto.clienobject.user.UserFreightBalanceRunningWaterCO;
import com.red.circle.console.app.excel.model.freight.UserFreightBalanceRunningWaterExportModel;
import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.inner.endpoint.UserFreightRechargeRecordClient;
import com.red.circle.other.inner.asserts.ImErrorCode;
import com.red.circle.other.inner.endpoint.material.props.BadgeBackpackClient;
import com.red.circle.other.inner.endpoint.material.props.BadgeSourceClient;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.DateFormatConstant;
import com.red.circle.tool.core.date.LocalDateTimeUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.endpoint.user.region.UserRegionClient;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.cmd.user.UserFreightBalanceCmd;
import com.red.circle.other.inner.model.cmd.user.UserRegionConfigMapCmd;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.other.inner.model.dto.user.reigon.RegionConfigDTO;
import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.AddFreightBalanceRunningWaterCmd;
import com.red.circle.wallet.inner.model.cmd.FreightAgentReviewCmd;
import com.red.circle.wallet.inner.model.cmd.UpdateFreightBalanceInfoCmd;
import com.red.circle.wallet.inner.model.cmd.UserFreightBalancePageQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserFreightBalanceRunningWaterExportQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserFreightBalanceRunningWaterPageQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserFreightSellerCmd;
import com.red.circle.wallet.inner.model.cmd.UserFreightSellerRunningWaterPageQryCmd;
import com.red.circle.wallet.inner.model.dto.UserFreightBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserFreightBalanceRunningWaterDTO;
import com.red.circle.wallet.inner.model.enums.FreightBalanceOrigin;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户货运账户 服务实现类
 * </p>
 *
 * @author pengliang
 * @since 2021-07-07
 */
@Service
@RequiredArgsConstructor
public class UserFreightBalanceServiceImpl implements
    UserFreightService {

  private final UserService userService;
  private final RedisService redisService;
  private final UserRegionClient userRegionClient;
  private final UserProfileClient userProfileClient;
  private final FreightGoldClient freightGoldClient;
  private final BadgeSourceClient badgeSourceClient;
  private final WalletAppConvertor walletAppConvertor;
  private final BadgeBackpackClient badgeBackpackClient;
  private final UserFreightRechargeRecordClient userFreightRechargeRecordClient;

  @Override
  public PageResult<UserFreightBalanceCO> pageFreight(UserFreightBalancePageQryCmd query) {

    PageResult<UserFreightBalanceDTO> freighPageResult = freightGoldClient.pageFreight(query)
        .getBody();

    if (freighPageResult.checkRecordsEmpty()) {
      return freighPageResult.emptyRecords();
    }

    Map<Long, Long> mapSellerQuantity = freightGoldClient.mapSellerQuantityByFreightIds(
        freighPageResult.getRecords().stream().map(UserFreightBalanceDTO::getId).collect(
            Collectors.toSet())).getBody();

    Map<Long, UserProfileDTO> userBaseInfoMap = ResponseAssert.requiredSuccess(
        userProfileClient.mapByUserIds(
            freighPageResult.getRecords().stream().map(UserFreightBalanceDTO::getUserId)
                .collect(
                    Collectors.toSet())));

    return freighPageResult.convert(userFreightBalance -> {
      UserFreightBalanceCO co = walletAppConvertor.toUserFreightBalanceCO(userFreightBalance);
      co.setUserBaseInfo(userBaseInfoMap.get(userFreightBalance.getUserId()));
      co.setRealSellerQuantity(
          Optional.ofNullable(mapSellerQuantity.get(userFreightBalance.getId()))
              .orElse(0L)
      );
      return co;
    });
  }

  @Override
  public void ship(UserFreightBalanceCmd cmd) {

    // 金额必须大于0
    //    ResponseAssert.isTrue(ErrorCodeEnum.AMOUNT_MUST_BE_BE_THAN_ZERO,
    //        BigDecimalUtils.nonNullAndGte(param.getEarnPoints(), BigDecimal.ONE));

    UserProfileDTO userProfileDTO = ResponseAssert.requiredSuccess(
        userProfileClient.getByUserId(cmd.getUserId()));
    ResponseAssert.notNull(ImErrorCode.NOT_FOUND_USER_INFO, userProfileDTO);

    ResponseAssert.isTrue(UserErrorCode.USER_INFO_NOT_FOUND,
        Objects.equals(userProfileDTO.getOriginSys(), cmd.getSysOrigin()));

    String lockKey = "back_send_freight_ship";
    try {
      // 操作冲突
      ResponseAssert.isTrue(CommonErrorCode.OPERATION_CONFLICT, redisService.lock(lockKey, 60));

      // 操作失败
      ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE,
          incrCandyBalance(cmd.getSysOrigin(), cmd.getUserId(), cmd.getEarnPoints()));

      // 插入流水
      if (ArithmeticUtils.gte(cmd.getEarnPoints(), BigDecimal.ONE)) {

        BigDecimal balance = getAvailableBalance(cmd.getUserId());

        freightGoldClient.addRunningWater(new AddFreightBalanceRunningWaterCmd()
            .setSysOrigin(cmd.getSysOrigin())
            .setUserId(cmd.getUserId())
            .setAcceptUserId(cmd.getUserId())
            .setType(0)
            .setQuantity(cmd.getEarnPoints())
            .setUsdQuantity(BigDecimal.ZERO)
            .setBalance(balance)
            .setRechargeType(cmd.getRechargeType())
            .setAmount(cmd.getAmount())
            .setOrigin(FreightBalanceOrigin.PURCHASE.name())
            .setRemark(cmd.getRemark())
            .setCreateUser(cmd.getOperationUser())
            .setUpdateUser(cmd.getOperationUser()));

        // 记录充值金额: 货运代理充值500以上则发送货运代理徽章
        BigDecimal amount = userFreightRechargeRecordClient.inrNowMonthAmount(cmd.getUserId(),
                ArithmeticUtils.lteZero(cmd.getAmount()) ? BigDecimal.ZERO : cmd.getAmount())
            .getBody();
//        sendFreightShipBadge(cmd.getUserId(), amount);
      }
    } finally {
      redisService.unlock(lockKey);
    }
  }

  @Override
  public void deduction(UserFreightBalanceCmd param) {
    // 余额不足
    ResponseAssert.isTrue(WalletErrorCode.REQUIRED_GT_ZERO,
        ArithmeticUtils.gte(param.getEarnPoints(), BigDecimal.ZERO));

    // 操作失败
    ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE,
        decrCandyBalance(param.getSysOrigin(), param.getUserId(), param.getEarnPoints()));

    // 插入流水
    BigDecimal balance = getAvailableBalance(param.getUserId());
    ResponseAssert.requiredSuccess(
        freightGoldClient.addRunningWater(new AddFreightBalanceRunningWaterCmd()
            .setSysOrigin(param.getSysOrigin())
            .setUserId(param.getUserId())
            .setAcceptUserId(param.getUserId())
            .setType(1)
            .setQuantity(param.getEarnPoints())
            .setBalance(balance)
            .setUsdQuantity(BigDecimal.ZERO)
            .setOrigin(FreightBalanceOrigin.DEDUCTION.name())
            .setRemark(param.getRemark())
            .setCreateUser(param.getOperationUser())
            .setUpdateUser(param.getOperationUser())));

    //余额如果少于1250则收回徽章
    removeFreightShipBadge(param.getUserId(), balance);

  }

  @Override
  public void switchStatus(Long id, Boolean status) {

    UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess(
        freightGoldClient.getFreightBalanceById(id));
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, balance);

    balance.setClose(status);
    balance.setUpdateTime(TimestampUtils.now());

    ResponseAssert.isTrue(CommonErrorCode.UPDATE_FAILURE, ResponseAssert.requiredSuccess(
        freightGoldClient.updateFreightBalance(
            walletAppConvertor.toUserFreightBalanceCmd(balance))));

    Long freightBadgeId = getFreightBadgeId();
    if (status && freightBadgeId != null) {
      //移除徽章
      badgeBackpackClient
          .deleteBadge(balance.getUserId(), freightBadgeId);
      return;
    }

    sendFreightShipBadge(balance.getUserId());
  }

  private Long getFreightBadgeId() {
    return badgeSourceClient.getFreightBadgeId().getBody();
  }

  private boolean incrCandyBalance(String sysOrigin, Long userId,
      BigDecimal quantity) {
    UserFreightBalanceDTO freightBalance = freightGoldClient.getFreightBalance(userId)
        .getBody();

    if (Objects.isNull(freightBalance)) {

      ResponseAssert.isFalse(
          UserErrorCode.USER_IS_SELLER, ResponseAssert.requiredSuccess(
              freightGoldClient.existsSeller(userId)));

      //获得货运代理徽章
      Long badgeId = getFreightBadgeId();
      if (Objects.nonNull(badgeId) && quantity.compareTo(BigDecimal.valueOf(1250)) >= 0) {
        //发送货运代理徽章
        badgeBackpackClient.giveBadgePermanent(badgeId, userId);
      }

      // 增加新开货运代理审查记录
      freightGoldClient.addFreightAgentReview(new FreightAgentReviewCmd()
          .setSysOrigin(sysOrigin)
          .setUserId(userId)
      );

    }
    if (Objects.equals(Boolean.TRUE, ResponseAssert.requiredSuccess(
        freightGoldClient.incrCandyBalance(SysOriginPlatformEnum.valueOf(sysOrigin), userId,
            quantity)))) {
      return Boolean.TRUE;
    }

    return Boolean.FALSE;
  }


  private boolean decrCandyBalance(String sysOrigin, Long userId,
      BigDecimal quantity) {
    return ResponseAssert.requiredSuccess(
        freightGoldClient.decrCandyBalance(SysOriginPlatformEnum.toEnum(sysOrigin), userId,
            quantity));
  }

  private BigDecimal getAvailableBalance(Long userId) {
    return Optional.ofNullable(
            ResponseAssert.requiredSuccess(freightGoldClient.getFreightBalance(userId)))
        .map(balance -> balance.getEarnPoints()
            .subtract(balance.getConsumptionPoints())
            .setScale(2, RoundingMode.DOWN))
        .orElse(BigDecimal.ZERO);
  }

  @Override
  public void deleteByUserId(Long userId) {

    UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess(
        freightGoldClient.getByUserId(userId));

    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, balance);

    ResponseAssert.requiredSuccess(freightGoldClient.deleteFreightBalance(userId));

    freightGoldClient.deleteFreightBalanceRunningWater(userId);

    //获得货运代理徽章
    Long badgeId = getFreightBadgeId();
    if (Objects.isNull(badgeId)) {
      return;
    }

    //移除徽章
    badgeBackpackClient.deleteBadge(balance.getUserId(), badgeId);
  }

  @Override
  public Boolean isFreightAgent(Long userId) {

    return ResponseAssert.requiredSuccess(freightGoldClient.checkFreightAgent(userId));
  }

  @Override
  public void showStatus(Long id, Boolean status) {

    UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess(
        freightGoldClient.getFreightBalanceById(id));
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, balance);

    balance.setH5Display(status);
    balance.setUpdateTime(TimestampUtils.now());
    ResponseAssert.isTrue(CommonErrorCode.UPDATE_FAILURE, ResponseAssert.requiredSuccess(
        freightGoldClient.updateFreightBalance(
            walletAppConvertor.toUserFreightBalanceCmd(balance))));
  }

  @Override
  public void switchDealerStatus(Long id, Boolean status) {
    UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess(
        freightGoldClient.getFreightBalanceById(id));
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, balance);

    balance.setDealer(status);
    balance.setUpdateTime(TimestampUtils.now());
    ResponseAssert.isTrue(CommonErrorCode.UPDATE_FAILURE, ResponseAssert.requiredSuccess(
        freightGoldClient.updateFreightBalance(
            walletAppConvertor.toUserFreightBalanceCmd(balance))));
  }

  @Override
  public void switchSuperDealerStatus(Long id, Boolean status) {
    UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess(
        freightGoldClient.getFreightBalanceById(id));
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, balance);

    balance.setSuperDealer(status);
    balance.setUpdateTime(TimestampUtils.now());
    ResponseAssert.isTrue(CommonErrorCode.UPDATE_FAILURE, ResponseAssert.requiredSuccess(
        freightGoldClient.updateFreightBalance(
            walletAppConvertor.toUserFreightBalanceCmd(balance))));
  }

  @Override
  public void updateSellerQuantity(UserFreightSellerCmd param) {
    if (!freightGoldClient.updateSellerQuantity(param).getBody()){
      ResponseAssert.failure(CommonErrorCode.UPDATE_FAILURE);
    }
  }

  @Override
  public PageResult<UserFreightBalanceRunningWaterCO> pageRunningWater(
      UserFreightBalanceRunningWaterPageQryCmd query) {

    PageResult<UserFreightBalanceRunningWaterDTO> runningWaterPage = freightGoldClient.pageRunningWater(
        query).getBody();

    if (runningWaterPage.checkRecordsEmpty()) {
      return runningWaterPage.emptyRecords();
    }

    Map<Long, UserProfileDTO> userBaseInfoMap = getUserBaseInfoMap(runningWaterPage);

    Map<Long, String> opsUserNicknameMap = userService.mapBackUserNickname(
        runningWaterPage.getRecords().stream()
            .map(UserFreightBalanceRunningWaterDTO::getCreateUser)
            .filter(Objects::nonNull)
            .collect(Collectors.toSet())
    );

    return runningWaterPage.convert(runningWater -> {
      UserFreightBalanceRunningWaterCO co = walletAppConvertor.toUserFreightBalanceRunningWaterCO(
          runningWater);
      UserProfileDTO acceptUser = userBaseInfoMap.get(runningWater.getAcceptUserId());
      UserProfileDTO userBaseInfo = userBaseInfoMap.get(runningWater.getUserId());
      co.setOriginName(FreightBalanceOrigin.getDesc(runningWater.getOrigin()));
      co.setAcceptUser(acceptUser);
      co.setUserBaseInfo(userBaseInfo);
      if (Objects.nonNull(runningWater.getCreateUser())) {
        co.setOperationUserNickname(opsUserNicknameMap.get(runningWater.getCreateUser()));
      }
      return co;
    });
  }

  private Map<Long, UserProfileDTO> getUserBaseInfoMap(
      PageResult<UserFreightBalanceRunningWaterDTO> runningWaterPage) {

    Set<Long> userIds = runningWaterPage.getRecords().stream()
        .map(UserFreightBalanceRunningWaterDTO::getAcceptUserId).collect(
            Collectors.toSet());

    userIds.addAll(runningWaterPage.getRecords().stream()
        .map(UserFreightBalanceRunningWaterDTO::getUserId).collect(
            Collectors.toSet()));

    return ResponseAssert.requiredSuccess(userProfileClient.mapByUserIds(userIds));
  }

  @Override
  public PageResult<UserFreightBalanceRunningWaterCO> pageFreightSellerRunningWater(
      UserFreightSellerRunningWaterPageQryCmd query) {

    PageResult<UserFreightBalanceRunningWaterDTO> runningWaterPage = ResponseAssert.requiredSuccess(
        freightGoldClient.pageFreightSellerRunningWater(query));

    if (runningWaterPage.checkRecordsEmpty()) {
      return runningWaterPage.emptyRecords();
    }

    Map<Long, UserProfileDTO> userBaseInfoMap = getUserBaseInfoMap(runningWaterPage);

    Map<Long, String> opsUserNicknameMap = userService.mapBackUserNickname(
        runningWaterPage.getRecords().stream().map(UserFreightBalanceRunningWaterDTO::getCreateUser)
            .filter(Objects::nonNull)
            .collect(Collectors.toSet())
    );

    return runningWaterPage.convert(runningWater -> {
      UserFreightBalanceRunningWaterCO co = walletAppConvertor.toUserFreightBalanceRunningWaterCO(
          runningWater);
      UserProfileDTO acceptUser = userBaseInfoMap.get(runningWater.getAcceptUserId());
      UserProfileDTO userBaseInfo = userBaseInfoMap.get(runningWater.getUserId());

      co.setOriginName(FreightBalanceOrigin.getDesc(runningWater.getOrigin()));
      co.setAcceptUser(acceptUser);
      co.setUserBaseInfo(userBaseInfo);
      if (Objects.nonNull(runningWater.getCreateUser())) {
        co.setOperationUserNickname(opsUserNicknameMap.get(runningWater.getCreateUser()));
      }
      return co;
    });
  }

  @Override
  public Collection<?> listExport(UserFreightBalanceRunningWaterExportQryCmd query) {

    List<UserFreightBalanceRunningWaterDTO> waters = ResponseAssert.requiredSuccess(
        freightGoldClient.listExportBalanceRunningWater(query));

    if (CollectionUtils.isEmpty(waters)) {
      return Lists.newArrayList();
    }

    Set<Long> userIds = Stream.of(
        waters.stream()
            .map(UserFreightBalanceRunningWaterDTO::getAcceptUserId).collect(
                Collectors.toSet()),
        waters.stream()
            .map(UserFreightBalanceRunningWaterDTO::getUserId).collect(
                Collectors.toSet())
    ).flatMap(Collection::stream).collect(Collectors.toSet());

    Map<Long, UserProfileDTO> userBaseInfoMap = ResponseAssert.requiredSuccess(
        userProfileClient.mapByUserIds(userIds));

    Map<Long, RegionConfigDTO> regionConfigMap = ResponseAssert.requiredSuccess(
        userRegionClient.mapUserRegionConfig(
            new UserRegionConfigMapCmd().setUserIds(userIds)));

    return waters.stream().map(water -> {

      UserFreightBalanceRunningWaterExportModel exportModel = walletAppConvertor.toUserFreightBalanceRunningWaterExportModel(
          water);

      UserProfileDTO acceptUser = userBaseInfoMap.get(water.getAcceptUserId());
      UserProfileDTO sendUser = userBaseInfoMap.get(water.getUserId());

      exportModel.setCurrentRegion(Optional.ofNullable(regionConfigMap.get(water.getUserId()))
          .map(RegionConfigDTO::getRegionName)
          .orElse("未知"));
      exportModel.setCurrentRegionCode(Optional.ofNullable(regionConfigMap.get(water.getUserId()))
          .map(RegionConfigDTO::getRegionCode)
          .orElse("未知"));
      exportModel.setOriginName(FreightBalanceOrigin.getDesc(water.getOrigin()));
      exportModel.setAccount(getAccountDescribe(sendUser));
      exportModel.setNickname(Optional.ofNullable(sendUser)
          .map(UserProfileDTO::getUserNickname).orElse("?"));

      exportModel.setAcceptAccount(getAccountDescribe(acceptUser));
      exportModel.setAcceptNickname(Optional.ofNullable(acceptUser)
          .map(UserProfileDTO::getUserNickname).orElse("?"));
      exportModel.setCreateTime(
          LocalDateTimeUtils.format(LocalDateTimeUtils.convert(water.getCreateTime()),
              DateFormatConstant.yyyy_MM_dd_HH_mm_ss));
      return exportModel;
    }).collect(Collectors.toList());
  }


  private String getAccountDescribe(UserProfileDTO userProfile) {

    if (Objects.isNull(userProfile)) {
      return "?";
    }

    if (Objects.nonNull(userProfile.getOwnSpecialId())) {
      return userProfile.getOwnSpecialId().getAccount() + "(" + userProfile.getAccount() + ")";
    }

    return userProfile.getAccount();
  }

  /**
   * 余额如果大于1250则发送货运代理徽章.
   *
   * @param userId  代理用户id.
   */
  public void sendFreightShipBadge(Long userId) {
    Long freightBadgeId = getFreightBadgeId();
    if (Objects.isNull(freightBadgeId)) {
      return;
    }

    badgeBackpackClient.giveBadgePermanent(freightBadgeId, userId);
  }

  /**
   * 余额如果小于1250则回收货运代理徽章.
   *
   * @param userId  代理用户id.
   * @param balance 代理余额.
   */
  public void removeFreightShipBadge(Long userId, BigDecimal balance) {

    // 余额如果小于1250则回收货运代理徽章
    Long freightBadgeId = getFreightBadgeId();
    if (Objects.isNull(freightBadgeId)) {
      return;
    }
    if (balance.compareTo(BigDecimal.valueOf(1250)) >= 0) {
      return;
    }
    badgeBackpackClient.deleteBadge(userId, freightBadgeId);
  }

  @Override
  public void updateFreightBalanceInfo(UpdateFreightBalanceInfoCmd cmd) {
    ResponseAssert.requiredSuccess(freightGoldClient.updateFreightBalanceInfo(cmd));
  }

}
