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

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.exception.ResponseException;
import com.red.circle.framework.web.props.EnvProperties;
import com.red.circle.framework.web.util.ValidatorUtils;
import com.red.circle.mq.business.model.event.wallet.WalletReceiptSyncEvent;
import com.red.circle.mq.rocket.business.producer.WalletMqMessageService;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.http.RcHttpClient;
import com.red.circle.tool.core.json.JacksonUtils;
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.domain.gateway.WalletGoldGateway;
import com.red.circle.wallet.domain.wallet.RecoveryGoldReceipt;
import com.red.circle.wallet.domain.wallet.RefundBackReceipt;
import com.red.circle.wallet.domain.wallet.WalletBizStatus;
import com.red.circle.wallet.domain.wallet.WalletBizType;
import com.red.circle.wallet.domain.wallet.WalletReceipt;
import com.red.circle.wallet.domain.wallet.WalletReceiptRes;
import com.red.circle.wallet.infra.database.cache.service.WalletGoldCacheService;
import com.red.circle.wallet.infra.database.rds.entity.WalletGoldAssetRecord;
import com.red.circle.wallet.infra.database.rds.service.WalletAssetRecordStatusService;
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.enums.WalletScriptRes;
import com.red.circle.wallet.infra.tool.WalletAssetSyncTool;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

/**
 * 金币钱包.
 *
 * @author pengliang on 2023/5/26
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WalletGoldGatewayImpl implements WalletGoldGateway {

  private final RcHttpClient rcHttpClient = RcHttpClient.newRcHttpClient();
  private final RedisService redisService;
  private final EnvProperties envProperties;
  private final WalletAssetSyncTool walletAssetSyncTool;
  private final WalletMqMessageService walletMqMessageService;
  private final WalletGoldCacheService walletGoldCacheService;
  private final WalletGoldBalanceService walletGoldBalanceService;
  private final WalletGoldAssetRecordService walletGoldAssetRecordService;
  private final WalletAssetRecordStatusService walletAssetRecordStatusService;

  @Override
  public PennyAmount getBalance(Long userId) {
    return PennyAmount.ofPenny(walletGoldCacheService.getBalance(userId));
  }

  @Override
  public WalletReceiptRes changeBalance(WalletReceipt receipt) {

    if (!ValidatorUtils.validateFastPass(receipt)) {
      throw new IllegalArgumentException("changeBalance required param error.");
    }

    // 兼容历史, 没有传递ReqTraceId表示不需要验证重复
//    if (StringUtils.isNotBlank(receipt.getReqTraceId())) {
//      ResponseAssert.isTrue(WalletErrorCode.REPEAT_SHIPMENT,
//          redisService.setIfAbsent(getReqTraceKey(receipt), 5, TimeUnit.MINUTES));
//    }

    Long pennyBalance = walletGoldCacheService.changeBalance(receipt.getReceiptType(),
        receipt.getUserId(), receipt.getAmount().getPennyAmount());

    ResponseAssert.isFalse(WalletErrorCode.INSUFFICIENT_BALANCE,
        WalletScriptRes.INSUFFICIENT_BALANCE.eq(pennyBalance));

    if (WalletScriptRes.PARAM_ERROR.eq(pennyBalance)) {
      throw new IllegalArgumentException(
          "changeBalance param error: " + JacksonUtils.toJson(receipt));
    }

    if (WalletScriptRes.NOT_EXISTS.eq(pennyBalance)) {
      // 5005 初始化钱包锁
      ResponseAssert.isTrue(WalletErrorCode.INIT_WALLET_LOCK,
          redisService.lock("INIT_WG:" + receipt.getUserId(), 60));

      walletGoldBalanceService.init(receipt.getSysOrigin(),
          receipt.getUserId(), 0L, 0L);

      walletGoldCacheService.setBalance(receipt.getUserId(),
          walletGoldBalanceService.getBalance(receipt.getUserId()));

      return changeBalance(receipt);
    }

    PennyAmount balancePennyAmount = PennyAmount.ofPenny(pennyBalance);

    WalletReceiptSyncEvent event = toWalletReceiptSyncEvent(receipt, balancePennyAmount);

    // TODO 标记是否已处理， 用于队列等实时性要求情况下判断是否已处理（临时过渡，需要重构容易被遗忘的逻辑）
    if (existsEvent(event.getEventType())) {
      walletGoldCacheService.pushEvent(event.getAssetRecordId(), event.getEventId());
    }

    // 如果关闭延迟同步流水，及时插入
    if (Objects.equals(event.getCloseDelayAsset(), true)) {
      walletAssetSyncTool.saveWalletGoldAssetRecord(event);
    }

    walletMqMessageService.sync(event);
    return WalletReceiptRes.of(event.getAssetRecordId(), balancePennyAmount, receipt.getAmount());
  }

  @NotNull
  private static String getReqTraceKey(WalletReceipt receipt) {
    return "WG_RQ:" + receipt.getReqTraceId();
  }

  private static boolean existsEvent(String eventType) {
    if (StringUtils.isBlank(eventType)) {
      return false;
    }
    List<String> markLabels = List.of("COMPENSATE_ORDER", "GIFT", "GAME_OPEN");
    for (String labels : markLabels) {
      if (eventType.contains(labels)) {
        return true;
      }
    }
    return false;
  }


  @Override
  public void refundBack(RefundBackReceipt receipt) {

    PennyAmount refundBackAmount = receipt.getAmount();

    if (refundBackAmount.getPennyAmount() <= 0) {
      log.warn("[refundBack] 参数错误: data={}", JacksonUtils.toJson(receipt));
      return;
    }

    if (!changeStatusReimburse(receipt.getEventId())) {
      log.warn("[refundBack] 已处理重复处理: bizId={}", receipt.getEventId());
      return;
    }

    PennyAmount balanceAmount = getBalance(receipt.getUserId());
    if (balanceAmount.getPennyAmount() <= 0) {
      log.warn("[refundBack] 没有可回收金额：userId={}", receipt.getUserId());
      return;
    }

    recovery(new RecoveryGoldReceipt()
        .setUserId(receipt.getUserId())
        .setSysOrigin(receipt.getSysOrigin())
        .setEventType(Objects.toString(GoldOrigin.USER_REFUND))
        .setEventDesc(GoldOrigin.USER_REFUND.getDesc())
        .setEventId(Objects.toString(receipt.getEventId()))
        .setAmount(refundBackAmount)
    );
  }

  @Override
  public void recovery(RecoveryGoldReceipt receipt) {
    PennyAmount balanceAmount = getBalance(receipt.getUserId());

    if (balanceAmount.getPennyAmount() <= 0) {
      log.warn("[recovery] 没有可回收金币：userId={}", receipt.getUserId());
      return;
    }

    PennyAmount recoveryAmount = receipt.getAmount();
    // 余额充足
    if (balanceAmount.getPennyAmount() >= recoveryAmount.getPennyAmount()) {
      try {
        changeBalance(new WalletReceipt()
            .setReqTraceId(receipt.getReqTraceId())
            .setReceiptType(ReceiptType.EXPENDITURE)
            .setUserId(receipt.getUserId())
            .setSysOrigin(receipt.getSysOrigin())
            .setEventType(receipt.getEventType())
            .setEventDesc(receipt.getEventDesc())
            .setEventId(receipt.getEventId())
            .setAmount(recoveryAmount)
            .setOpUserType(OpUserType.APP)
            .setCreateTime(TimestampUtils.now())
        );
      } catch (ResponseException ex) {
        // 余额不足清理
        if (Objects.equals(ex.getErrorCodeName(),
            WalletErrorCode.INSUFFICIENT_BALANCE.getErrorCodeName())) {
          clearBalance(receipt);
        }

        ex.printStackTrace();
      }
      return;
    }

    clearBalance(receipt);
  }

  /**
   * 清空用户余额.
   */
  private void clearBalance(RecoveryGoldReceipt receipt) {
    if (walletGoldBalanceService.clear(receipt.getUserId())) {
      walletGoldCacheService.removeBalance(receipt.getUserId());
    }

    PennyAmount balanceAmount = getBalance(receipt.getUserId());
    walletGoldAssetRecordService.save(new WalletGoldAssetRecord()
        .setId(IdWorkerUtils.getId())
        .setSysOrigin(Objects.toString(receipt.getSysOrigin()))
        .setUserId(receipt.getUserId())
        .setPennyAmount(balanceAmount.getPennyAmount())
        .setPennyBalance(0L)
        .setType(ReceiptType.EXPENDITURE.getType())
        .setEventType(Objects.toString(GoldOrigin.USER_REFUND))
        .setEventDesc(GoldOrigin.USER_REFUND.getDesc())
        .setEventId(receipt.getEventId())
        .setOpUserType(OpUserType.APP.getType())
    );
  }

  /**
   * 标记退款.
   *
   * @return true: 不存在已执行保存、false 已存在
   */
  private boolean changeStatusReimburse(String receiptId) {
    return walletAssetRecordStatusService.saveIfAbsent(receiptId,
        WalletBizType.GOLD, WalletBizStatus.REIMBURSE);
  }

  private WalletReceiptSyncEvent toWalletReceiptSyncEvent(WalletReceipt receipt,
      PennyAmount balancePennyAmount) {
    WalletReceiptSyncEvent event = new WalletReceiptSyncEvent();
    event.setAssetRecordId(IdWorkerUtils.getId());
    event.setReceiptType(receipt.getReceiptType());
    event.setUserId(receipt.getUserId());
    event.setSysOrigin(receipt.getSysOrigin());
    event.setEventType(receipt.getEventType());
    event.setEventDesc(
        GoldOrigin.matchSpecialDescByEventType(receipt.getEventType(), receipt.getEventDesc()));
    event.setEventId(receipt.getEventId());
    event.setRemark(receipt.getRemark());
    event.setOpUserType(receipt.getOpUserType());
    event.setOpUserId(receipt.getOpUserId());
    event.setBizType(WalletBizType.GOLD.getType());
    event.setAmount(receipt.getAmount());
    event.setBalance(balancePennyAmount);
    event.setCloseDelayAsset(receipt.getCloseDelayAsset());
    event.setCreateTime(receipt.getCreateTime());
    return event;
  }

}
