package com.red.circle.wallet.app.command.bank;

import com.red.circle.common.business.core.enums.OpUserType;
import com.red.circle.common.business.core.enums.ReceiptType;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.other.inner.endpoint.user.region.RegionAssistConfigClient;
import com.red.circle.other.inner.endpoint.user.region.RegionConfigClient;
import com.red.circle.other.inner.endpoint.user.region.UserRegionClient;
import com.red.circle.other.inner.enums.team.TeamPolicyTypeEnum;
import com.red.circle.other.inner.enums.user.RegionAssistTypeEnum;
import com.red.circle.other.inner.model.dto.user.SysExchangeGoldTipDTO;
import com.red.circle.other.inner.model.dto.user.reigon.RegionConfigDTO;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.regex.RegexConstant;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.app.dto.clientobject.UserBankWithdrawResultCO;
import com.red.circle.wallet.app.dto.cmd.UserBankWithdrawCmd;
import com.red.circle.wallet.infra.database.cache.service.WalletBankCacheService;
import com.red.circle.wallet.infra.database.mongo.dto.UserBankWithdrawMoneyApprovalProcess;
import com.red.circle.wallet.infra.database.mongo.dto.WithdrawMoneyApprovalProcessStatus;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankCard;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankRunningWater;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankWithdrawMoneyApply;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankBalanceService;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankCardService;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankRunningWaterService;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankWithdrawMoneyApplyService;
import com.red.circle.wallet.infra.database.rds.entity.diamond.SalaryDiamondBalance;
import com.red.circle.wallet.infra.database.rds.entity.diamond.SalaryDiamondRunningWater;
import com.red.circle.wallet.infra.database.rds.service.diamond.SalaryDiamondBalanceService;
import com.red.circle.wallet.infra.database.rds.service.diamond.SalaryDiamondRunningWaterService;
import com.red.circle.wallet.inner.error.BankErrorCode;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent;
import com.red.circle.wallet.inner.model.enums.WithdrawMethod;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;

/**
 * 用户银行卡钻石提现.
 *
 * @author xiaofei on 2024/5/15
 */
@Component
@RequiredArgsConstructor
public class UserBankDiamondWithdrawCmdExe {

  private static final Logger log = LoggerFactory.getLogger(UserBankDiamondWithdrawCmdExe.class);
  private final RedisService redisService;
  private final RegionConfigClient regionConfigClient;
  private final UserRegionClient userRegionClient;
  private final UserBankCardService userBankCardService;
  private final SalaryDiamondBalanceService salaryDiamondBalanceService;
  private final SalaryDiamondRunningWaterService salaryDiamondRunningWaterService;
  private final WalletBankCacheService walletBankCacheService;
  private final RegionAssistConfigClient regionAssistConfigClient;
  private final UserBankWithdrawMoneyApplyService userBankWithdrawMoneyApplyService;

  public UserBankWithdrawResultCO execute(UserBankWithdrawCmd cmd) {
    ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        ArithmeticUtils.gtZero(cmd.getAmount()));

    /*ResponseAssert.isFalse(CommonErrorCode.MSG_REPEAT_PROCESSING,
        walletBankCacheService.checkMonthWithdrawal(cmd.requiredReqUserId()));*/

    // 上锁
    String key = "UBWithdraw:" + cmd.requiredReqUserId();
    ResponseAssert.isTrue(CommonErrorCode.REQUEST_LIMITING, redisService.lock(key, 60));

    BigDecimal withdrawalAmount = BigDecimal.ZERO;
    ResultResponse<List<SysExchangeGoldTipDTO>> response = userRegionClient.listRegionWithdrawTip(cmd.requiredReqUserId());
    // 计算
    if (Objects.nonNull(response) && Objects.nonNull(response.getBody())) {
      SysExchangeGoldTipDTO tipDTO = response.getBody().stream()
              .filter(item -> cmd.getAmount().compareTo(new BigDecimal(item.getStart())) >=0
                      && cmd.getAmount().compareTo(new BigDecimal(item.getEnd())) <= 0).findFirst().orElse(null);
      log.warn(tipDTO+"");
      // 错误类型需要新加一个
      ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, tipDTO);
        assert tipDTO != null;
        withdrawalAmount = getWithdrawalAmount(cmd.getAmount(), tipDTO.getValue());
    }

    SalaryDiamondBalance balance = salaryDiamondBalanceService.getBalance(cmd.requiredReqUserId());

    ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE,
        ArithmeticUtils.gte(balance.getBalance(), cmd.getAmount()));

    UserBankCard bankCard = userBankCardService.getById(cmd.getAcceptBankCardId());
    ResponseAssert.notNull(BankErrorCode.BANK_CARD_NOT_FOUND, bankCard);

    // 扣钱
    SalaryDiamondBalance decrUserBankBalanceDTO = salaryDiamondBalanceService.decr(cmd.requiredReqUserId(),
        PennyAmount.ofDollar(cmd.getAmount()));
    ResponseAssert.notNull(WalletErrorCode.INSUFFICIENT_BALANCE, decrUserBankBalanceDTO);

    // 手续费
    Long serviceCharge = getAssistDiamondConfig(cmd.requiredReqUserId());
    // 计算完手续费后的金额
    BigDecimal actualAmount = getActualAmount(withdrawalAmount, serviceCharge);

    Long bankRunWaterId = IdWorkerUtils.getId();
    Long applyId = IdWorkerUtils.getId();

    // 钻石流水
    SalaryDiamondRunningWater salaryDiamondRunningWater = new SalaryDiamondRunningWater()
            .setId(bankRunWaterId)
            .setUserId(cmd.requiredReqUserId())
            .setSysOrigin(cmd.requireReqSysOrigin())
            .setType(ReceiptType.EXPENDITURE.getType())
            .setAmount(cmd.getAmount())
            .setServiceCharge(serviceCharge)
            .setActualAmount(actualAmount)
            .setSalaryEvent(UserBankWaterEvent.WITHDRAW.name())
            .setEventDesc(UserBankWaterEvent.WITHDRAW.getDescribe())
            .setRemark("Cash withdrawal")
            .setTrackId(applyId.toString())
            .setBalance(decrUserBankBalanceDTO.getBalance())
            .setCreateUserOrigin(OpUserType.APP.getType());
    salaryDiamondRunningWater.setCreateTime(TimestampUtils.now());
    salaryDiamondRunningWater.setUpdateTime(TimestampUtils.now());
    salaryDiamondRunningWater.setCreateUser(cmd.requiredReqUserId());
    salaryDiamondRunningWater.setCreateUserOrigin(OpUserType.APP.getType());
    salaryDiamondRunningWaterService.save(salaryDiamondRunningWater);

    // 申请详情
    userBankWithdrawMoneyApplyService.add(
        new UserBankWithdrawMoneyApply()
            .setId(applyId)
            .setSysOrigin(cmd.requireReqSysOrigin())
            .setAcceptMethod(WithdrawMethod.ONESELF.name())
            .setSubmitUserId(cmd.requiredReqUserId())
            .setAmountType(TeamPolicyTypeEnum.SALARY_DIAMOND.name())
            .setBankCardId(bankCard.getId())
            .setBankCardType(bankCard.getCardType())
            .setAmount(cmd.getAmount())
            .setApprovalProcesses(List.of(
                new UserBankWithdrawMoneyApprovalProcess()
                    .setStatus(WithdrawMoneyApprovalProcessStatus.SUBMIT.name())
                    .setRemark("Application submitted")
                    .setCreateTime(TimestampUtils.now())
            ))
            .setLatestApprovalStatus(WithdrawMoneyApprovalProcessStatus.SUBMIT.name())
            .setServiceCharge(serviceCharge)
            .setActualAmount(actualAmount)
            .setCreateTime(TimestampUtils.now())
            .setUpdateTime(TimestampUtils.now())
            .setCreateUser(cmd.requiredReqUserId())
            .setUpdateUser(cmd.requiredReqUserId())
            .setCreateUserOrigin(OpUserType.APP.getType())
            .setUpdateUserOrigin(OpUserType.APP.getType())
    );

    walletBankCacheService.setMonthWithdrawal(cmd.requiredReqUserId());
    redisService.unlock(key);
    return new UserBankWithdrawResultCO()
        .setBankBalance(decrUserBankBalanceDTO.getBalance())
        .setWithdrawAmount(Objects.toString(cmd.getAmount()));
  }

  private BigDecimal getActualAmount(BigDecimal amount, Long serviceCharge) {
    BigDecimal charge = new BigDecimal(serviceCharge).divide(new BigDecimal(100));
    BigDecimal serviceChargeAmount = amount.multiply(charge).setScale(2, RoundingMode.DOWN);
    log.warn("手续费金额{}", serviceChargeAmount);
    return amount.subtract(serviceChargeAmount).setScale(0, RoundingMode.DOWN);
  }

  private BigDecimal getWithdrawalAmount(BigDecimal amount, Double value) {
    BigDecimal charge = new BigDecimal(value);
    BigDecimal serviceChargeAmount = amount.divide(charge, 2, BigDecimal.ROUND_HALF_UP).setScale(2, RoundingMode.DOWN);
    return serviceChargeAmount;
  }

  private Long getAssistDiamondConfig(Long userId) {

    RegionConfigDTO regionConfig = ResponseAssert.requiredSuccess(
        regionConfigClient.getRegionByUserId(userId)
    );
    if (Objects.isNull(regionConfig)) {
      return 0L;
    }

    String assistConfigData = ResponseAssert.requiredSuccess(
        regionAssistConfigClient.getAssistConfigData(
            RegionAssistTypeEnum.WITHDRAWAL_COMMISSION_RATIO, regionConfig.getId())
    );

    if (StringUtils.isBlank(assistConfigData)) {
      return 0L;
    }

    if (!assistConfigData.matches(RegexConstant.NUMBER)) {
      return 0L;
    }

    Long data = DataTypeUtils.toLong(assistConfigData);
    if (Objects.isNull(data) || data <= 0) {
      return 0L;
    }
    return data;
  }
}
