package com.red.circle.other.app.command.activity;

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.external.inner.endpoint.message.OfficialNoticeClient;
import com.red.circle.external.inner.model.cmd.message.notice.official.NoticeExtTemplateCustomizeCmd;
import com.red.circle.external.inner.model.enums.message.OfficialNoticeTypeEnum;
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.framework.dto.ResultResponse;
import com.red.circle.other.app.dto.clientobject.activity.LotteryWithdrawAmountCO;
import com.red.circle.other.app.dto.cmd.activity.LotteryTransferCmd;
import com.red.circle.other.app.service.activity.LotteryActivityRestService;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryUserProgress;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryWithdraw;
import com.red.circle.other.infra.database.rds.service.activity.LotteryUserProgressService;
import com.red.circle.other.infra.database.rds.service.activity.LotteryWithdrawService;
import com.red.circle.other.inner.endpoint.user.region.UserRegionClient;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.dto.user.SysExchangeGoldTipDTO;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.num.ArithmeticUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.freight.FreightBalanceClient;
import com.red.circle.wallet.inner.endpoint.freight.FreightBalanceRunningWaterClient;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.dto.FreightBalanceRunningWaterDTO;
import com.red.circle.wallet.inner.model.enums.FreightBalanceOrigin;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 可提现金额转账给指定用户执行器.
 *
 * @author system
 * @since 2025-10-24
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LotteryTransferExe {

  private final RedisService redisService;
  private final LotteryWithdrawService lotteryWithdrawService;
  private final UserRegionClient userRegionClient;
  private final WalletGoldClient walletGoldClient;
  private final FreightBalanceClient freightBalanceClient;
  private final FreightBalanceRunningWaterClient freightBalanceRunningWaterClient;
  private final LotteryWithdrawAmountQryExe lotteryWithdrawAmountQryExe;
  private final OfficialNoticeClient officialNoticeClient;
  private final UserProfileClient userProfileClient;

  public void execute(LotteryTransferCmd cmd) {
    ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        ArithmeticUtils.gtZero(cmd.getAmount()));
    ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        cmd.getAcceptUserId());

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

    try {
      Long userId = cmd.requiredReqUserId();

      // 6. 判断接收用户是否是货运代理
      boolean isFreight = freightBalanceClient.existsBalance(cmd.getAcceptUserId()).getBody();
      ResponseAssert.isTrue(WalletErrorCode.TARGET_USER_NOT_RECHARGE_AGENCY, isFreight);
      ResponseAssert.isTrue(WalletErrorCode.NOT_TRANSFER_YOURSELF, !Objects.equals(cmd.getAcceptUserId(), cmd.requiredReqUserId()));

      // 1. 获取可提现金额
      LotteryWithdrawAmountCO withdrawAmount = lotteryWithdrawAmountQryExe.execute(userId, cmd.getActivityId());;
      BigDecimal availableAmount = withdrawAmount.getAvailableAmount();

      // 2. 验证金额是否足够
      ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE,
          ArithmeticUtils.gte(availableAmount, cmd.getAmount()));

      // 3. 获取区域兑换金币比例
      Double goldRatio = getExchangeGoldRatio(cmd);

      // 4. 计算金币数量
      BigDecimal goldQuantity = BigDecimal.valueOf(goldRatio)
          .multiply(cmd.getAmount())
          .setScale(0, RoundingMode.DOWN);

      // 5. 记录转账（作为提现记录）
      LotteryWithdraw withdraw = new LotteryWithdraw();
      withdraw.setUserId(userId);
      withdraw.setWithdrawNo(generateTransferWithdrawNo());
      withdraw.setActivityId(cmd.getActivityId());
      withdraw.setAmount(cmd.getAmount());
      withdraw.setType("TRANSFER");
      withdraw.setStatus(3); // 已完成
      withdraw.setDescription("转账给用户" + cmd.getAcceptUserId() + ": " + goldQuantity + "金币");
      withdraw.setCreateTime(TimestampUtils.now());
      withdraw.setUpdateTime(TimestampUtils.now());
      lotteryWithdrawService.save(withdraw);

      Long withdrawId = withdraw.getId();

      // 7. 发送金币给接收用户
      sendGold(cmd, isFreight, goldQuantity, withdrawId, "抽奖活动转账金币");

      log.info("用户{}转账给用户{}成功，金额{}，金币{}",
          userId, cmd.getAcceptUserId(), cmd.getAmount(), goldQuantity);
    } finally {
      redisService.unlock(key);
    }
  }

  /**
   * 生成提现单号.
   */
  private String generateTransferWithdrawNo() {
    return "TS" + System.currentTimeMillis() +
        String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
  }

  /**
   * 发送金币给接收用户.
   */
  private void sendGold(LotteryTransferCmd cmd, boolean isFreight, BigDecimal goldQuantity,
                        Long withdrawId, String remark) {
    if (isFreight) {
      // 货运代理入账
      ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE,
          freightBalanceClient.incrCandyBalance(
              com.red.circle.common.business.core.enums.SysOriginPlatformEnum.valueOf(
                  cmd.getReqSysOrigin().getOrigin()),
              cmd.getAcceptUserId(), goldQuantity).getBody());

      // 插入流水
      BigDecimal freightBalance = freightBalanceClient.getAvailableBalance(cmd.getAcceptUserId()).getBody();
      freightBalanceRunningWaterClient.save(new FreightBalanceRunningWaterDTO()
          .setSysOrigin(cmd.getReqSysOrigin().getOrigin())
          .setUserId(cmd.requiredReqUserId())
          .setAcceptUserId(cmd.getAcceptUserId())
          .setType(ReceiptType.INCOME.getType())
          .setQuantity(goldQuantity)
          .setUsdQuantity(cmd.getAmount())
          .setBalance(freightBalance)
          .setOrigin(FreightBalanceOrigin.PURCHASE.name())
          .setRemark(remark)
          .setCreateUser(cmd.requiredReqUserId()));

      String noticeContent = getNoticeContent(cmd, goldQuantity);
      officialNoticeClient.send(
              NoticeExtTemplateCustomizeCmd.builder()
                      .toAccount(cmd.getAcceptUserId())
                      .noticeType(OfficialNoticeTypeEnum.COIN_SELLER_COINS_RECEIVED)
                      .content(noticeContent)
                      .build()
      );

      return;
    }

    // 6. 充值金币给用户自己
    GoldReceiptCmd build = GoldReceiptCmd.builder()
        .appIncome()
        .userId(cmd.requiredReqUserId())
        .amount(goldQuantity)
        .eventId(Objects.toString(withdrawId))
        .sysOrigin(cmd.getReqSysOrigin().getOrigin())
        .origin(GoldOrigin.LOTTERY_TRANSFER_GOLD)
        .remark(remark)
        .build();
    // 金币入账
    walletGoldClient.changeBalance(build);
  }

  private String getNoticeContent(LotteryTransferCmd cmd, BigDecimal goldQuantity) {
    // 获取发送者（代理）的 account
    String agencyAccount = resolveAccount(cmd.getReqUserId());

    // 获取接收者的区域判断是否为阿拉伯语
    ResultResponse<String> regionResponse = userRegionClient.getRegionCode(cmd.getAcceptUserId());
    boolean arRegion = regionResponse.checkSuccess() && "AR".equals(regionResponse.getBody());

    long goldCoins = goldQuantity.longValue();
    String noticeTemplate = arRegion
            ? "لقد استلمت %,d عملة. تم تحويل الأموال من المستخدم (ID: %s) إلى محفظة وكالة الشحن الخاصة بك. يرجى تأكيد الاستلام."
            : "You have received %,d Coins. The funds have been transferred from user (ID: %s) to your recharge agency wallet. Please confirm receipt.";
    return String.format(noticeTemplate, goldCoins, agencyAccount);
  }

  /**
   * 获取用户展示的 account，优先取专属号（ownSpecialId），否则取普通 account
   */
  private String resolveAccount(Long userId) {
    ResultResponse<UserProfileDTO> response = userProfileClient.getByUserId(userId);
    if (!response.checkSuccess() || response.getBody() == null) {
      return Objects.toString(userId);
    }
    UserProfileDTO profile = response.getBody();
    return profile.getOwnSpecialId() != null
            ? profile.getOwnSpecialId().getAccount()
            : profile.getAccount();
  }


  /**
   * 获得1$兑换多少金币比例，基于区域+用户输入的美元.
   *
   * @return 1美元可获得的金币数量.
   */
  private Double getExchangeGoldRatio(LotteryTransferCmd cmd) {
    Long acceptUserId = cmd.getAcceptUserId();
    boolean freight = freightBalanceClient.existsBalance(acceptUserId).getBody();

    List<SysExchangeGoldTipDTO> exchangeGolds = ResponseAssert.requiredSuccess(
        userRegionClient.listRegionExchangeGoldTip(cmd.requiredReqUserId(), freight));
    ResponseAssert.notEmpty(CommonErrorCode.CONFIGURATION_ERROR, exchangeGolds);

    exchangeGolds = exchangeGolds.stream().filter(gold ->
            cmd.getAmount().compareTo(BigDecimal.valueOf(gold.getStart())) >= 0 &&
                cmd.getAmount().compareTo(BigDecimal.valueOf(gold.getEnd())) <= 0)
        .collect(Collectors.toList());
    ResponseAssert.notEmpty(CommonErrorCode.CONFIGURATION_ERROR, exchangeGolds);

    SysExchangeGoldTipDTO exchangeGold = exchangeGolds.get(0);
    ResponseAssert.notNull(CommonErrorCode.CONFIGURATION_ERROR, exchangeGold);
    ResponseAssert.isFalse(CommonErrorCode.CONFIGURATION_ERROR,
        Objects.isNull(exchangeGold.getValue()) || exchangeGold.getValue() <= 0);

    return exchangeGold.getValue();
  }

}