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.team.target.TeamMemberClient;
import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.model.dto.agency.agency.TeamMemberDTO;
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.tuple.PennyAmount;
import com.red.circle.wallet.app.dto.clientobject.UserBankWithdrawResultCO;
import com.red.circle.wallet.app.dto.cmd.UserBankWithdrawTransferCmd;
import com.red.circle.wallet.infra.database.cache.service.UserPayAuthCacheService;
import com.red.circle.wallet.infra.database.mongo.entity.bank.UserBankRunningWater;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankBalanceService;
import com.red.circle.wallet.infra.database.mongo.service.bank.UserBankRunningWaterService;
import com.red.circle.wallet.infra.database.rds.entity.auth.PayAuth;
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.auth.PayAuthService;
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.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.UserSalaryDiamondCmd;
import com.red.circle.wallet.inner.model.dto.BankBalanceDTO;
import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 用户银行卡提现 转账.
 *
 * @author xiaofei on 2024/5/16
 */
@Component
@Log4j2
@RequiredArgsConstructor
public class UserBankDiamondWithdrawTransferCmdExe {

  private final RedisService redisService;
  private final PayAuthService payAuthService;
  private final TeamMemberClient teamMemberClient;
  private final UserPayAuthCacheService userPayAuthCacheService;

  private final SalaryDiamondBalanceService salaryDiamondBalanceService;
  private final SalaryDiamondRunningWaterService salaryDiamondRunningWaterService;

  @Transactional(rollbackFor = Exception.class)
  public UserBankWithdrawResultCO execute(UserBankWithdrawTransferCmd cmd) {
    ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        ArithmeticUtils.gtZero(cmd.getAmount()));

    checkPayPassword(cmd);

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

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

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

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

    // 钻石流水
    SalaryDiamondRunningWater salaryDiamondRunningWater = new SalaryDiamondRunningWater()
            .setId(IdWorkerUtils.getId())
            .setUserId(cmd.requiredReqUserId())
            .setSysOrigin(cmd.requireReqSysOrigin())
            .setType(ReceiptType.EXPENDITURE.getType())
            .setAmount(cmd.getAmount())
            .setSalaryEvent(UserBankWaterEvent.TRANSFER.name())
            .setEventDesc(UserBankWaterEvent.TRANSFER.getDescribe())
            .setRemark("Withdrawal transfer")
            .setTrackId(Objects.toString(cmd.getAcceptUserId()))
            .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);

    // 转账
    UserSalaryDiamondCmd param = new UserSalaryDiamondCmd();
    param.setQuantity(cmd.getAmount());
    param.setUserId(cmd.getAcceptUserId());
    param.setSysOrigin(cmd.requireReqSysOrigin());
    param.setOperationUserId(cmd.getAcceptUserId());

    if (salaryDiamondBalanceService.incr(param)) {
      log.warn("添加转账钻石入账流水");
      BigDecimal acceptBalance = salaryDiamondBalanceService.getBalance(cmd.getAcceptUserId()).getBalance();
      // 钻石流水
      SalaryDiamondRunningWater acceptSalaryDiamondRunningWater = new SalaryDiamondRunningWater()
              .setId(IdWorkerUtils.getId())
              .setUserId(cmd.getAcceptUserId())
              .setSysOrigin(cmd.requireReqSysOrigin())
              .setType(ReceiptType.INCOME.getType())
              .setAmount(cmd.getAmount())
              .setSalaryEvent(UserBankWaterEvent.RECEIVE_TRANSFER.name())
              .setEventDesc(UserBankWaterEvent.RECEIVE_TRANSFER.getDescribe())
              .setRemark("Receive transfer")
              .setTrackId(Objects.toString(cmd.requiredReqUserId()))
              .setBalance(acceptBalance)
              .setCreateUserOrigin(OpUserType.APP.getType());
      acceptSalaryDiamondRunningWater.setCreateTime(TimestampUtils.now());
      acceptSalaryDiamondRunningWater.setUpdateTime(TimestampUtils.now());
      acceptSalaryDiamondRunningWater.setCreateUser(cmd.requiredReqUserId());
      acceptSalaryDiamondRunningWater.setCreateUserOrigin(OpUserType.APP.getType());
      salaryDiamondRunningWaterService.save(acceptSalaryDiamondRunningWater);
    }

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

  private void checkPayPassword(UserBankWithdrawTransferCmd cmd) {
    TeamMemberDTO teamMember = teamMemberClient.getByMemberId(cmd.requiredReqUserId())
        .getBody();
    if (Objects.nonNull(teamMember)
        && !TeamMemberRole.OWN.eq(teamMember.getRole())) {
      return;
    }
    if (cmd.originEqAswat() && cmd.checkAndroid()) {
      // 查询密码输入错误次数
      Integer times = userPayAuthCacheService.getPayAuthTimes(cmd.requiredReqUserId());
      ResponseAssert.isFalse(WalletErrorCode.PAY_PASSWORD_EXHAUSTED, times > 3);
      PayAuth payAuth = payAuthService.getByPassword(cmd.getPassword(),
          cmd.requiredReqUserId());

      if (Objects.isNull(payAuth)) {
        // 密码输错次数
        userPayAuthCacheService.recordPayAuth(cmd.requiredReqUserId());
        ResponseAssert.isFalse(WalletErrorCode.PAY_PASSWORD_ERROR, Boolean.TRUE);
      }
    }
  }
}
