package com.red.circle.wallet.infra.gateway;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.red.circle.common.business.core.enums.ReceiptType;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.web.util.ValidatorUtils;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.domain.gateway.SalaryAccountGateway;
import com.red.circle.wallet.domain.salary.SalaryReceipt;
import com.red.circle.wallet.domain.salary.SalaryReceiptRes;
import com.red.circle.wallet.inner.model.enums.SalaryType;
import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountBalance;
import com.red.circle.wallet.infra.database.rds.entity.salary.SalaryAccountRunningWater;
import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountBalanceService;
import com.red.circle.wallet.infra.database.rds.service.salary.SalaryAccountRunningWaterService;
import com.red.circle.wallet.infra.util.TimeConvertUtils;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import java.math.BigDecimal;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 工资账户 Gateway 实现.
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SalaryAccountGatewayImpl implements SalaryAccountGateway {

  private final SalaryAccountBalanceService salaryAccountBalanceService;
  private final SalaryAccountRunningWaterService salaryAccountRunningWaterService;

  @Override
  public PennyAmount getBalance(Long userId, SalaryType salaryType) {
    return PennyAmount.ofPenny(salaryAccountBalanceService.getBalance(userId, salaryType));
  }

  @Override
  public PennyAmount getAvailableBalance(Long userId, SalaryType salaryType) {
    return PennyAmount.ofPenny(salaryAccountBalanceService.getAvailableBalance(userId, salaryType));
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public SalaryReceiptRes changeBalance(SalaryReceipt receipt) {
    if (!ValidatorUtils.validateFastPass(receipt)) {
      throw new IllegalArgumentException("changeBalance required param error.");
    }

    // 检查账户是否存在，不存在则初始化
    SalaryAccountBalance balance = salaryAccountBalanceService.getByUserIdAndType(
        receipt.getUserId(), receipt.getSalaryType());
    
    if (Objects.isNull(balance)) {
      salaryAccountBalanceService.init(receipt.getSysOrigin(), receipt.getUserId(), 
          receipt.getSalaryType());
      balance = salaryAccountBalanceService.getByUserIdAndType(
          receipt.getUserId(), receipt.getSalaryType());
    }

    // 更新余额
    boolean updateSuccess;
    if (receipt.getReceiptType() == ReceiptType.INCOME) {
      updateSuccess = salaryAccountBalanceService.incrIncome(
          receipt.getUserId(), 
          receipt.getSalaryType(),
          receipt.getAmount().getPennyAmount()
      );
    } else {
      updateSuccess = salaryAccountBalanceService.incrExpenditure(
          receipt.getUserId(),
          receipt.getSalaryType(),
          receipt.getAmount().getPennyAmount()
      );
    }

    ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, updateSuccess);

    // 查询更新后的余额
    Long newBalance = salaryAccountBalanceService.getBalance(receipt.getUserId(), 
        receipt.getSalaryType());
    Long newAvailableBalance = salaryAccountBalanceService.getAvailableBalance(receipt.getUserId(),
        receipt.getSalaryType());

    // 插入流水记录
    Long recordId = IdWorkerUtils.getId();
    try {
      SalaryAccountRunningWater runningWater = buildRunningWater(receipt, recordId, 
          newBalance, newAvailableBalance);
      salaryAccountRunningWaterService.save(runningWater);
    } catch (DuplicateKeyException e) {
      // 幂等性：trackId重复，查询已有记录
        LambdaQueryWrapper<SalaryAccountRunningWater> eq = Wrappers.lambdaQuery(SalaryAccountRunningWater.class)
                .eq(SalaryAccountRunningWater::getTrackId, receipt.getTrackId());
      SalaryAccountRunningWater existingWater = salaryAccountRunningWaterService.getOne(eq);
      if (Objects.nonNull(existingWater)) {
        return SalaryReceiptRes.of(
            existingWater.getId(),
            PennyAmount.ofPenny(existingWater.getBalance()
                .multiply(new BigDecimal("100")).longValue()),
            PennyAmount.ofPenny(existingWater.getAvailableBalance()
                .multiply(new BigDecimal("100")).longValue()),
            receipt.getAmount()
        );
      }
      throw e;
    }

    return SalaryReceiptRes.of(
        recordId,
        PennyAmount.ofPenny(newBalance),
        PennyAmount.ofPenny(newAvailableBalance),
        receipt.getAmount()
    );
  }

  private SalaryAccountRunningWater buildRunningWater(SalaryReceipt receipt, Long recordId,
      Long balance, Long availableBalance) {
    SalaryAccountRunningWater water = new SalaryAccountRunningWater()
        .setId(recordId)
        .setUserId(receipt.getUserId())
         .setAcceptUserId(receipt.getAcceptUserId())
        .setSysOrigin(receipt.getSysOrigin())
        .setType(receipt.getReceiptType().getType())
        .setSalaryType(receipt.getSalaryType().getCode())
        .setAmount(receipt.getAmount().getDollarAmount())
        .setBalance(PennyAmount.ofPenny(balance).getDollarAmount())
        .setAvailableBalance(PennyAmount.ofPenny(availableBalance).getDollarAmount())
        .setTrackId(receipt.getTrackId())
        .setSalaryEvent(receipt.getSalaryEvent().getCode())
        .setEventDesc(receipt.getEventDesc())
        .setRemark(receipt.getRemark())
        .setCreateUserOrigin(receipt.getOpUserType().getType())
        .setBillBelong(receipt.getBillBelong())
        .setTeamId(receipt.getTeamId())
        .setBdId(receipt.getBdId())
        .setAgentId(receipt.getAgentId());

    if (Objects.nonNull(receipt.getServiceCharge())) {
      water.setServiceCharge(receipt.getServiceCharge());
    }
    if (Objects.nonNull(receipt.getActualAmount())) {
      water.setActualAmount(receipt.getActualAmount().getDollarAmount());
    }

    water.setCreateTime(Objects.isNull(receipt.getCreateTime()) 
        ? TimestampUtils.now() : TimeConvertUtils.toTimestamp(receipt.getCreateTime()));
    water.setUpdateTime(TimestampUtils.now());
    water.setCreateUser(receipt.getOpUserId());
    water.setUpdateUser(receipt.getOpUserId());

    return water;
  }
}
