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

import com.red.circle.console.app.convertor.diamond.SalaryDiamondAppConvertor;
import com.red.circle.console.app.dto.clienobject.user.UserSalaryDiamondBalanceCO;
import com.red.circle.console.app.dto.clienobject.user.UserSalaryDiamondRunningWaterCO;
import com.red.circle.console.infra.database.rds.entity.admin.User;
import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.console.inner.error.ConsoleErrorCode;
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.framework.dto.ResultResponse;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.wallet.inner.endpoint.diamond.SalaryDiamondBalanceClient;
import com.red.circle.wallet.inner.endpoint.diamond.SalaryDiamondRunningWaterClient;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.UserSalaryDiamondBalanceQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserSalaryDiamondCmd;
import com.red.circle.wallet.inner.model.cmd.UserSalaryDiamondRunningWaterQryCmd;
import com.red.circle.wallet.inner.model.cmd.UserSalaryDiamondTransferCmd;
import com.red.circle.wallet.inner.model.dto.SalaryDiamondBalanceDTO;
import com.red.circle.wallet.inner.model.dto.UserSalaryDiamondRunningWaterDTO;
import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent;
import com.red.circle.wallet.inner.model.enums.UserSalaryDiamondWaterEvent;
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;

/**
 * 用户工资钻石
 *
 * @author zongpubin on 2024/3/13
 */
@Service
@RequiredArgsConstructor
public class UserSalaryDiamondServiceImpl implements UserSalaryDiamondService {

  private final UserService userService;
  private final UserProfileClient userProfileClient;
  private final SalaryDiamondAppConvertor salaryDiamondAppConvertor;
  private final SalaryDiamondBalanceClient salaryDiamondBalanceClient;
  private final SalaryDiamondRunningWaterClient salaryDiamondRunningWaterClient;

  @Override
  public PageResult<UserSalaryDiamondBalanceCO> listUserSalaryDiamondBalance(
      UserSalaryDiamondBalanceQryCmd query) {
    PageResult<SalaryDiamondBalanceDTO> pageResult = ResponseAssert.requiredSuccess(
        salaryDiamondBalanceClient.listUserSalaryDiamondBalance(query));

    if (CollectionUtils.isEmpty(pageResult.getRecords())) {
      return pageResult.convert(salaryDiamondAppConvertor::toSalaryDiamondBalanceCO);
    }

    Map<Long, UserProfileDTO> userProfileMap = ResponseAssert.requiredSuccess(
        userProfileClient.mapByUserIds(
            getAllUserIds(pageResult.getRecords())));

    return pageResult.convert(salaryDiamondBalanceDTO -> {
      UserSalaryDiamondBalanceCO userSalaryDiamond = salaryDiamondAppConvertor.toSalaryDiamondBalanceCO(
          salaryDiamondBalanceDTO);
      userSalaryDiamond.setUserProfile(userProfileMap.get(userSalaryDiamond.getUserId()));
      return userSalaryDiamond;
    });
  }

  @Override
  public void createSalaryDiamondBalance(UserSalaryDiamondCmd param) {
    if (Objects.isNull(param.getQuantity())) {
      param.setQuantity(BigDecimal.ZERO);
    }
    ResponseAssert.isTrue(WalletErrorCode.NUMBER_VIOLATION_ERROR,
        param.getQuantity().compareTo(BigDecimal.ZERO) >= 0);

    UserProfileDTO userProfile = ResponseAssert.requiredSuccess(
        userProfileClient.getByUserId(param.getUserId()));
    ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, userProfile);

    ResponseAssert.isFalse(UserErrorCode.USER_REGISTERED,
        ResponseAssert.requiredSuccess(salaryDiamondBalanceClient.exists(userProfile.getId())));
    param.setSysOrigin(userProfile.getOriginSys());
    ResponseAssert.requiredSuccess(salaryDiamondBalanceClient
        .incr(param));

    // 添加流水记录
    saveRunningWater(param, userProfile, 0);


  }

  @Override
  public void sendSalaryDiamond(UserSalaryDiamondCmd param) {
    ResponseAssert.isTrue(WalletErrorCode.REQUIRED_GT_ZERO,
        ArithmeticUtils.gtZero(param.getQuantity()));

    UserProfileDTO userProfile = ResponseAssert.requiredSuccess(
        userProfileClient.getByUserId(param.getUserId()));
    ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, userProfile);

    // 累加
    param.setSysOrigin(userProfile.getOriginSys());
    ResponseAssert.requiredSuccess(salaryDiamondBalanceClient
        .incr(param));

    // 添加流水记录
    saveRunningWater(param, userProfile, 0);
  }

  @Override
  public void deductSalaryDiamond(UserSalaryDiamondCmd param) {
    ResponseAssert.isTrue(WalletErrorCode.REQUIRED_GT_ZERO,
        ArithmeticUtils.gtZero(param.getQuantity()));

    UserProfileDTO userProfile = ResponseAssert.requiredSuccess(
        userProfileClient.getByUserId(param.getUserId()));
    ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, userProfile);
    /*ResultResponse<SalaryDiamondBalanceDTO> balance = salaryDiamondBalanceClient.getBalance(userProfile.getId());
    ResponseAssert.notNull(WalletErrorCode.DEDUCTION_FAILED, balance);
    ResponseAssert.notNull(WalletErrorCode.DEDUCTION_FAILED, balance.getBody());

    if (param.getQuantity().compareTo(balance.getBody().getBalance()) > 0) {
      ResponseAssert.isFalse(WalletErrorCode.INSUFFICIENT_BALANCE,
              param.getQuantity().compareTo(balance.getBody().getBalance()) > 0);
    }*/
    // 累减
    ResponseAssert.requiredSuccess(salaryDiamondBalanceClient.decr(param));

    // 添加流水记录
    saveRunningWater(param, userProfile, 1);
  }

  @Override
  public void transferSalaryDiamond(UserSalaryDiamondTransferCmd param) {
    ResponseAssert.isTrue(WalletErrorCode.REQUIRED_GT_ZERO,
        ArithmeticUtils.gtZero(param.getQuantity()));

    ResponseAssert.isFalse(CommonErrorCode.OPERATION_CONFLICT,
        Objects.equals(param.getUserId(), param.getAcceptUserId()));

    UserProfileDTO userProfile = ResponseAssert.requiredSuccess(
        userProfileClient.getByUserId(param.getUserId()));
    ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, userProfile);
    UserProfileDTO acceptUserProfile = ResponseAssert.requiredSuccess(userProfileClient.getByUserId(
        param.getAcceptUserId()));
    ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, acceptUserProfile);

    BigDecimal balance = ResponseAssert.requiredSuccess(
        salaryDiamondBalanceClient.getBalance(userProfile.getId())).getBalance();
    ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE,
        balance.compareTo(param.getQuantity()) >= 0);
    SalaryDiamondBalanceDTO acceptUserBalance = ResponseAssert.requiredSuccess(
        salaryDiamondBalanceClient.getBalance(
            acceptUserProfile.getId()));

    // 扣除
    ResponseAssert.requiredSuccess(
        salaryDiamondBalanceClient.decr(
            new UserSalaryDiamondCmd()
                .setUserId(userProfile.getId())
                .setQuantity(param.getQuantity())
                .setOperationUserId(param.getOperationUserId())
                .setSysOrigin(userProfile.getOriginSys()))
    );

    // 增加
    ResponseAssert.requiredSuccess(salaryDiamondBalanceClient.incr(
            new UserSalaryDiamondCmd()
                .setUserId(acceptUserProfile.getId())
                .setQuantity(param.getQuantity())
                .setOperationUserId(param.getOperationUserId())
                .setSysOrigin(userProfile.getOriginSys())
        )
    );

    // 添加流水记录
    ResponseAssert.requiredSuccess(salaryDiamondRunningWaterClient.addBatch(List.of(
            new UserSalaryDiamondRunningWaterDTO()
                .setId(IdWorkerUtils.getId())
                .setUserId(userProfile.getId())
                .setSysOrigin(userProfile.getOriginSys())
                .setType(1)
                .setAmount(param.getQuantity())
                .setSalaryEvent(UserBankWaterEvent.TRANSFER.name())
                .setEventDesc(UserBankWaterEvent.TRANSFER.getDescribe())
                .setTrackId(Objects.toString(acceptUserProfile.getId()))
                .setRemark(param.getRemark())
                .setBalance(balance.subtract(param.getQuantity())
                    .setScale(2, RoundingMode.DOWN))
                .setCreateTime(TimestampUtils.now())
                .setCreateUser(param.getOperationUserId())
                .setCreateUserOrigin(1),

            new UserSalaryDiamondRunningWaterDTO()
                .setId(IdWorkerUtils.getId())
                .setUserId(acceptUserProfile.getId())
                .setSysOrigin(acceptUserProfile.getOriginSys())
                .setType(0)
                .setAmount(param.getQuantity())
                .setSalaryEvent(UserBankWaterEvent.RECEIVE_TRANSFER.name())
                .setEventDesc(UserBankWaterEvent.RECEIVE_TRANSFER.getDescribe())
                .setTrackId(Objects.toString(userProfile.getId()))
                .setRemark(param.getRemark())
                .setBalance(Objects.nonNull(acceptUserBalance) ? acceptUserBalance.getBalance()
                    .add(param.getQuantity())
                    .setScale(2, RoundingMode.DOWN) : param.getQuantity()
                    .setScale(2, RoundingMode.DOWN))
                .setCreateTime(TimestampUtils.now())
                .setCreateUser(param.getOperationUserId())
                .setCreateUserOrigin(1)
        )
    ));
  }

  @Override
  public PageResult<UserSalaryDiamondRunningWaterCO> runningWaterPage(
      UserSalaryDiamondRunningWaterQryCmd query) {
    PageResult<UserSalaryDiamondRunningWaterDTO> pageResult = ResponseAssert.requiredSuccess(
        salaryDiamondRunningWaterClient.runningWaterPage(
            query));
    if (CollectionUtils.isEmpty(pageResult.getRecords())) {
      return pageResult.convert(salaryDiamondAppConvertor::toUserSalaryDiamondRunningWaterCO);
    }
    Map<Long, UserProfileDTO> userProfileMap = getUserProfileMap(
        getSalaryDiamondRunningWaterUserIds(pageResult.getRecords().stream().toList()));

    Map<Long, User> sysUserMap = getSysUserMap(pageResult.getRecords().stream().toList());

    return pageResult.convert(log -> {

      UserProfileDTO userProfile = userProfileMap.get(log.getUserId());
      if (Objects.isNull(userProfile)) {
        return null;
      }

      String name = Optional.ofNullable(sysUserMap.get(log.getCreateUser())).map(User::getNickname)
          .orElse("");

      UserSalaryDiamondRunningWaterCO waterVO = new UserSalaryDiamondRunningWaterCO()
          .setId(log.getId())
          .setUserId(log.getUserId())
          .setUserProfile(userProfile)
          .setSysOrigin(log.getSysOrigin())
          .setType(log.getType())
          .setTypeName(log.getType() == 0 ? "收入" : "支出")
          .setAmount(log.getAmount())
          .setSalaryEvent(log.getSalaryEvent())
          .setEventDesc(log.getEventDesc())
          .setRemark(log.getRemark())
          .setTrackId(log.getTrackId())
          .setBalance(log.getBalance())
          .setTempBalance(log.getTempBalance())
          .setCreateTime(log.getCreateTime())
          .setUpdateTime(log.getUpdateTime())
          .setCreateUser(log.getCreateUser())
          .setCreateUserOrigin(log.getCreateUserOrigin())
          .setCreateUserOriginName(log.getCreateUserOrigin() == 0 ? "APP" : "后台:" + name);
      if (StringUtils.isBlank(log.getTrackId())) {
        return waterVO;
      }
      if (!Objects.equals(log.getSalaryEvent(), UserSalaryDiamondWaterEvent.TRANSFER.name()) &&
          !Objects.equals(log.getSalaryEvent(),
              UserSalaryDiamondWaterEvent.RECEIVE_TRANSFER.name())) {
        return waterVO;
      }
      UserProfileDTO tmpUserProfile = userProfileMap.get(Long.valueOf(log.getTrackId()));
      if (Objects.isNull(tmpUserProfile)) {
        return waterVO;
      }
      waterVO.setTmpUserProfile(tmpUserProfile);
      return waterVO;
    });
  }

  private Set<Long> getAllUserIds(Collection<SalaryDiamondBalanceDTO> records) {
    return records.stream().map(SalaryDiamondBalanceDTO::getUserId).collect(Collectors.toSet());
  }

  private void saveRunningWater(UserSalaryDiamondCmd param, UserProfileDTO userProfile,
      Integer type) {

    ResponseAssert.requiredSuccess(
        salaryDiamondRunningWaterClient.add(new UserSalaryDiamondRunningWaterDTO()
            .setId(IdWorkerUtils.getId())
            .setUserId(userProfile.getId())
            .setSysOrigin(userProfile.getOriginSys())
            .setType(type)
            .setTrackId(userProfile.getId().toString())
            .setAmount(param.getQuantity())
            .setSalaryEvent(param.getEvent().name())
            .setEventDesc(param.getEvent().getDescribe())
            .setRemark(param.getRemark())
            .setBalance(
                ResponseAssert.requiredSuccess(
                    salaryDiamondBalanceClient.getBalance(userProfile.getId())).getBalance())
            .setCreateTime(TimestampUtils.now())
            .setCreateUser(param.getOperationUserId())
            .setCreateUserOrigin(1)
        )
    );
  }

  private Set<Long> getSalaryDiamondRunningWaterUserIds(
      List<UserSalaryDiamondRunningWaterDTO> runningWaters) {

    Set<Long> tmpUserIds = runningWaters.stream().map(runningWater -> {
      if (!Objects.equals(runningWater.getSalaryEvent(),
          UserSalaryDiamondWaterEvent.TRANSFER.name()) && !Objects
          .equals(runningWater.getSalaryEvent(),
              UserSalaryDiamondWaterEvent.RECEIVE_TRANSFER.name())) {
        return null;
      }
      return StringUtils.isNotBlank(runningWater.getTrackId()) ? Long
          .valueOf(runningWater.getTrackId())
          : null;
    }).filter(Objects::nonNull).collect(Collectors.toSet());

    return Stream.of(tmpUserIds,
            runningWaters.stream().map(UserSalaryDiamondRunningWaterDTO::getUserId)
                .collect(Collectors.toSet()))
        .flatMap(Collection::stream)
        .collect(Collectors.toSet());
  }

  private Map<Long, UserProfileDTO> getUserProfileMap(Set<Long> userIds) {
    return ResponseAssert.requiredSuccess(userProfileClient.mapByUserIds(userIds));
  }

  private Map<Long, User> getSysUserMap(List<UserSalaryDiamondRunningWaterDTO> bankRunningWaters) {
    return userService.mapBackUser(
        bankRunningWaters.stream().filter(user -> user.getCreateUserOrigin() == 1)
            .map(UserSalaryDiamondRunningWaterDTO::getCreateUser).collect(
                Collectors.toSet()));
  }
}
