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.other.inner.endpoint.user.region.RegionAssistConfigClient;
import com.red.circle.other.inner.endpoint.user.region.RegionConfigClient;
import com.red.circle.other.inner.enums.user.RegionAssistTypeEnum;
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.domain.gateway.SalaryAccountGateway;
import com.red.circle.wallet.domain.salary.SalaryEvent;
import com.red.circle.wallet.domain.salary.SalaryReceipt;
import com.red.circle.wallet.domain.salary.SalaryReceiptRes;
import com.red.circle.wallet.domain.salary.SalaryType;
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.UserBankWithdrawMoneyApply;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankCardService;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankWithdrawMoneyApplyService;
import com.red.circle.wallet.inner.error.BankErrorCode;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.enums.WithdrawMethod;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 用户银行卡提现.
 *
 * @author pengliang on 2023/2/20
 */
@Component
@RequiredArgsConstructor
public class UserBankWithdrawCmdExe {

  private final RedisService redisService;
  private final RegionConfigClient regionConfigClient;
  private final UserBankCardService userBankCardService;
  private final WalletBankCacheService walletBankCacheService;
  private final RegionAssistConfigClient regionAssistConfigClient;
  private final UserBankWithdrawMoneyApplyService userBankWithdrawMoneyApplyService;
  private final SalaryAccountGateway salaryAccountGateway;

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

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

    SalaryType salaryType = SalaryType.BD_SALARY;

    PennyAmount availableBalance = salaryAccountGateway.getAvailableBalance(cmd.getReqUserId(), salaryType);
    BigDecimal balance = availableBalance.getDollarAmount();

      // 3. 校验提现金额
    ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, ArithmeticUtils.gte(balance, cmd.getAmount()));

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

    Long applyId = IdWorkerUtils.getId();

    // 扣钱
      SalaryReceipt receipt = new SalaryReceipt()
              .setReceiptType(ReceiptType.EXPENDITURE)
              .setUserId(cmd.getReqUserId())
              .setAcceptUserId(cmd.getReqUserId())
              .setSysOrigin(cmd.getReqSysOrigin().getOrigin())
              .setSalaryType(salaryType)
              .setSalaryEvent(SalaryEvent.WITHDRAW)
              .setAmount(PennyAmount.ofDollar(cmd.getAmount()))
              .setTrackId(String.valueOf(applyId))
              .setEventDesc("提现")
              .setOpUserType(OpUserType.APP)
              .setOpUserId(cmd.getReqUserId());
    SalaryReceiptRes salaryReceiptRes = salaryAccountGateway.changeBalance(receipt);
    ResponseAssert.notNull(WalletErrorCode.INSUFFICIENT_BALANCE, salaryReceiptRes);

    // 手续费
    Long serviceCharge = getAssistDiamondConfig(cmd.requiredReqUserId());
    BigDecimal actualAmount = getActualAmount(cmd.getAmount(), serviceCharge);

    // 申请详情
    userBankWithdrawMoneyApplyService.add(
        new UserBankWithdrawMoneyApply()
            .setId(applyId)
            .setSysOrigin(cmd.requireReqSysOrigin())
            .setAcceptMethod(WithdrawMethod.ONESELF.name())
            .setSubmitUserId(cmd.requiredReqUserId())
            .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(salaryReceiptRes.getAvailableBalance().getDollarAmount())
        .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(0, RoundingMode.CEILING);
    return amount.subtract(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;
  }
}
