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

import com.alibaba.nacos.shaded.com.google.common.base.Throwables;
import com.red.circle.component.tencent.cls.service.ClsService;
import com.red.circle.framework.web.props.EnvProperties;
import com.red.circle.mq.business.model.event.wallet.WalletReceiptSyncEvent;
import com.red.circle.tool.core.date.DateUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.wallet.domain.gateway.WalletExceptionLogGateway;
import com.red.circle.wallet.domain.wallet.WalletBizType;
import com.red.circle.wallet.domain.wallet.WalletErrorLogReason;
import com.red.circle.wallet.domain.wallet.WalletExceptionLogRecord;
import com.red.circle.wallet.infra.convertor.WalletGoldAssetRecordInfraConvertor;
import com.red.circle.wallet.infra.database.cache.service.WalletGoldCacheService;
import com.red.circle.wallet.infra.database.mongo.entity.log.LogSyncCacheDbBalance;
import com.red.circle.wallet.infra.database.mongo.service.log.LogSyncCacheDbBalanceService;
import com.red.circle.wallet.infra.database.rds.entity.WalletGoldAssetRecord;
import com.red.circle.wallet.infra.database.rds.entity.WalletGoldBalance;
import com.red.circle.wallet.infra.database.rds.entity.WalletRemarks;
import com.red.circle.wallet.infra.database.rds.service.WalletGoldAssetRecordService;
import com.red.circle.wallet.infra.database.rds.service.WalletGoldBalanceService;
import com.red.circle.wallet.infra.database.rds.service.WalletRemarksService;
import com.red.circle.wallet.infra.enums.LogTopic;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;

/**
 * 钱包金币，资产同步.
 *
 * @author pengliang on 2023/8/30
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WalletAssetSyncTool {

  private final ClsService clsService;
  private final EnvProperties envProperties;
  private final UserProfileClient userProfileClient;
  private final WalletRemarksService walletRemarksService;
  private final WalletGoldCacheService walletGoldCacheService;
  private final WalletGoldBalanceService walletGoldBalanceService;
  private final WalletExceptionLogGateway walletExceptionLogGateway;
  private final LogSyncCacheDbBalanceService logSyncCacheDbBalanceService;
  private final WalletGoldAssetRecordService walletGoldAssetRecordService;
  private final WalletGoldAssetRecordInfraConvertor walletGoldAssetRecordInfraConvertor;

  /**
   * 检查数缓存->db同步误差（避免误操作, 只有金币余额10分钟没有变化时进行一次同步）.
   *
   * @param meltingFaultMaxNum 熔错最大数量(元).
   */
  public void syncCacheDbCheck(Long userId, long meltingFaultMaxNum) {
    long maxNum = meltingFaultMaxNum * 100;
    WalletGoldBalance wallet = walletGoldBalanceService.getById(userId);

    if (Objects.isNull(wallet)) {

      Long cacheBalance = walletGoldCacheService.getBalance(userId);

      if (Objects.isNull(cacheBalance) || cacheBalance <= 0) {
        return;
      }

      UserProfileDTO userProfile = userProfileClient.getByUserId(userId).getBody();

      if (Objects.nonNull(userProfile)) {
        walletGoldBalanceService.init(userProfile.getOriginSys(),
            userProfile.getId(),
            cacheBalance,
            0L);
      }
      return;
    }

    if (DateUtils.toDurationMinutes(wallet.getUpdateTime(), DateUtils.now()) < 10) {
      return;
    }

    long dbBalance = wallet.getIncome() - wallet.getExpenditure();

    if (dbBalance <= 0) {
      return;
    }

    Long cacheBalance = walletGoldCacheService.getBalance(userId);

    long syncBalance = dbBalance - cacheBalance;
    if (syncBalance < maxNum) {
      return;
    }

    if (walletGoldBalanceService.incrExpenditureCas(userId, syncBalance, wallet.getExpenditure())) {
      logSyncCacheDbBalanceService.add(
          new LogSyncCacheDbBalance()
              .setId(IdWorkerUtils.getId())
              .setSysOrigin(wallet.getSysOrigin())
              .setUserId(wallet.getId())
              .setDbIncome(wallet.getIncome())
              .setDbExpenditure(wallet.getExpenditure())
              .setDbBalance(dbBalance)
              .setCacheBalance(cacheBalance)
              .setMeltingFaultMaxNum(maxNum)
              .setSyncExpenditure(syncBalance)
              .setExpireTime(DateUtils.nowPlusDays(7))
              .setCreateTime(DateUtils.now())
      );
    }
  }

  /**
   * 保存金币流水资产流水.
   */
  public void saveWalletGoldAssetRecord(WalletReceiptSyncEvent receipt) {
    try {
      WalletGoldAssetRecord goldAssetRecord = toWalletGoldAssetRecord(receipt);
      walletGoldAssetRecordService.save(goldAssetRecord);

      clsService.upload(
          LogTopic.WALLET_GOLD_ASSET_PROD.getWalletTopic(envProperties.isProd()),
          walletGoldAssetRecordInfraConvertor.toWalletGoldAssetRecordLog(goldAssetRecord));

    } catch (DuplicateKeyException ignore) {
      // ignore
    } catch (Exception ex) {
      saveErrorLog(receipt, WalletErrorLogReason.SAVE_ASSET_RECORD);
      log.error("保存金币流水异常, 记录异常日志: {}", ex.getMessage());
    }

    // 保存备注信息
    if (StringUtils.isNotBlank(receipt.getRemark())) {
      try {
        walletRemarksService.save(toWalletRemarks(receipt));
      } catch (Exception ex) {
        saveErrorLog(receipt, WalletErrorLogReason.SAVE_REMARK);
        log.error("{}", Throwables.getStackTraceAsString(ex));
      }
    }

  }

  /**
   * 刷新金币同步.
   */
  public void syncGold(WalletReceiptSyncEvent receipt) {
    try {
      if (receipt.checkTypeIncome()) {
        walletGoldBalanceService.incrIncome(receipt.getUserId(),
            receipt.getAmount().getPennyAmount());
      }

      if (receipt.checkExpenditure()) {
        walletGoldBalanceService.incrExpenditure(receipt.getUserId(),
            receipt.getAmount().getPennyAmount());
      }
    } catch (Exception ex) {
      saveErrorLog(receipt, WalletErrorLogReason.REST_BALANCE);
      ex.printStackTrace();
    }
  }


  private WalletRemarks toWalletRemarks(WalletReceiptSyncEvent receipt) {
    WalletRemarks remarks = new WalletRemarks()
        .setId(IdWorkerUtils.getId())
        .setBizId(Objects.toString(receipt.getAssetRecordId()))
        .setBizType(receipt.getBizType())
        .setContent(receipt.getRemark());
    remarks.setCreateTime(receipt.getCreateTime());
    remarks.setUpdateTime(DateUtils.now());
    return remarks;
  }

  private void saveErrorLog(WalletReceiptSyncEvent receipt, WalletErrorLogReason reason) {
    walletExceptionLogGateway.add(toWalletExceptionLogRecord(receipt, reason));
  }

  private WalletGoldAssetRecord toWalletGoldAssetRecord(WalletReceiptSyncEvent receipt) {
    WalletGoldAssetRecord assetRecord = new WalletGoldAssetRecord()
        .setId(receipt.getAssetRecordId())
        .setSysOrigin(receipt.getSysOrigin())
        .setUserId(receipt.getUserId())
        .setPennyAmount(receipt.getAmount().getPennyAmount())
        .setPennyBalance(receipt.getBalance().getPennyAmount())
        .setType(receipt.receiptTypeVal())
        .setEventType(receipt.getEventType())
        .setEventDesc(receipt.getEventDesc())
        .setEventId(StringUtils.isBlankOrElse(receipt.getEventId(), StringUtils.EMPTY))
        .setOpUserType(receipt.opUserTypeVal());
    assetRecord.setCreateUser(receipt.getOpUserId());
    assetRecord.setUpdateUser(receipt.getOpUserId());
    assetRecord.setCreateTime(
        Objects.isNull(receipt.getCreateTime()) ? DateUtils.now() : receipt.getCreateTime());
    assetRecord.setUpdateTime(DateUtils.now());
    return assetRecord;
  }

  private WalletExceptionLogRecord toWalletExceptionLogRecord(WalletReceiptSyncEvent receipt,
      WalletErrorLogReason reason) {
    return new WalletExceptionLogRecord()
        .setAssetRecordId(receipt.getAssetRecordId())
        .setReceiptType(receipt.getReceiptType())
        .setUserId(receipt.getUserId())
        .setSysOrigin(receipt.getSysOrigin())
        .setEventType(receipt.getEventType())
        .setEventDesc(receipt.getEventDesc())
        .setEventId(receipt.getEventId())
        .setRemark(receipt.getRemark())
        .setRemark(receipt.getRemark())
        .setOpUserType(receipt.getOpUserType())
        .setPennyAmount(receipt.getAmount().getPennyAmount())
        .setPennyBalance(receipt.getBalance().getPennyAmount())
        .setBizType(WalletBizType.toEnum(receipt.getBizType()))
        .setReason(reason)
        .setStatus(0);
  }
}
