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

import com.alibaba.nacos.common.utils.StringUtils;
import com.red.circle.common.business.enums.SendPropsOrigin;
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.other.app.dto.cmd.task.CheckInRewardCmd;
import com.red.circle.other.app.util.EnhancedDeviceUtil;
import com.red.circle.other.infra.common.activity.PropsActivitySendCommon;
import com.red.circle.other.infra.common.props.PropsSendCommon;
import com.red.circle.other.infra.database.cache.key.CheckInKeys;
import com.red.circle.other.infra.database.cache.service.other.CheckInCacheService;
import com.red.circle.other.infra.database.rds.entity.activity.LotteryDeviceRecord;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
import com.red.circle.other.infra.database.rds.entity.user.user.LatestMobileDevice;
import com.red.circle.other.infra.database.rds.service.activity.LotteryDeviceRecordService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.inner.asserts.GameErrorCode;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.model.cmd.material.PrizeDescribe;
import com.red.circle.other.inner.model.cmd.material.PrizeDescribeRewardCmd;
import com.red.circle.other.infra.utils.ZonedDateTimeUtils;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.date.ZonedId;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 每日打卡奖励-信息.
 *
 * @author zongpubin on 2023/10/19
 */
@Service
@AllArgsConstructor
public class CheckInExe {

  private final RedisService redisService;
  private final CheckInCacheService checkInCacheService;
  private final PropsActivitySendCommon propsActivitySendCommon;
  private final PropsActivityRuleConfigService propsActivityRuleConfigService;
  private final PropsActivityRewardConfigService propsActivityRewardConfigService;
  private final PropsSendCommon propsSendCommon;
  private final LatestMobileDeviceService latestMobileDeviceService;
  private final LotteryDeviceRecordService lotteryDeviceRecordService;
  private final static long AZIZI_FIXED_ACTIVITY_ID = 10010L;

  public Integer execute(CheckInRewardCmd cmd) {
    // 判断是否为Azizi签到（根据dailyKey是否为空）
    boolean isAziziCheckIn = StringUtils.isNotBlank(cmd.getDailyKey());
    
    // 设置每日签到key
    if (isAziziCheckIn) {
      cmd.setDailyKey(CheckInKeys.AZIZI_CHECK_IN.getKey("AZIZI_CHECK_IN" + cmd.getReqUserId() + ZonedDateTimeAsiaRiyadhUtils.nowDateToInt()));

      // 2. 校验设备唯一性（防止小号薅羊毛）
      checkDeviceUnique(AZIZI_FIXED_ACTIVITY_ID, cmd.getReqUserId());
    } else {
      cmd.setDailyKey(CheckInKeys.DAILY_CHECK_IN.getKey("DAILY_CHECK_IN_" + cmd.getReqUserId() + ZonedDateTimeAsiaRiyadhUtils.nowDateToInt()));
    }


    PropsActivityRuleConfig ruleConfig = propsActivityRuleConfigService.getById(cmd.getId());
    // 1034 没有找到映射信息
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, ruleConfig);

    String lockKey = "CHECK_IN_DAILY_REWARD:" + cmd.getReqUserId();

    try {

      ResponseAssert.isTrue(CommonErrorCode.NOT_REPEATABLE,
          redisService.lock(lockKey, 30));

      ResponseAssert.isFalse(GameErrorCode.CHECKED_IN,
          checkInCacheService.checkDailyCheckIn(cmd.getReqUserId(), cmd.getDailyKey()));

      ResponseAssert.isFalse(GameErrorCode.CHECKED_IN,
          Objects.equals(checkInCacheService.getCheckInDays(buildSevenCheckInKey(isAziziCheckIn, cmd.getReqUserId())), 7));

      // 累计打卡天数
      String sevenCheckInKey = buildSevenCheckInKey(isAziziCheckIn, cmd.getReqUserId());
      Long inDays = checkInCacheService.incrCheckInDays(sevenCheckInKey, getExpiredTime(isAziziCheckIn, cmd.getReqUserId()));
      
      // Azizi签到需要检查最大天数限制
      if (isAziziCheckIn) {
        ResponseAssert.isTrue(GameErrorCode.MAX_CHECK_IN, inDays <= 7);
      }

      checkInCacheService.markDailyCheckIn(cmd.getReqUserId(), cmd.getDailyKey());

/*    不再根据资源组发，根据资源组顺序一个一个发
      propsActivitySendCommon.sendActivityGroup(SendRewardGroup.builder()
          .trackId(cmd.getId())
          .sysOrigin(cmd.requireReqSysOriginEnum())
          .acceptUserId(cmd.getReqUserId())
          .resourceGroupId(cmd.getResourceGroupId())
          .origin(SendPropsOrigin.DAILY_REGISTER)
          .build());*/

      List<PropsActivityRewardConfig> configs = propsActivityRewardConfigService.listByGroupId(cmd.getResourceGroupId());
      ResponseAssert.notEmpty(CommonErrorCode.NOT_FOUND_MAPPING_INFO, configs);


      // 根据星期几获取对应的奖品配置（星期几就是第几个配置，索引需要减1）
      int configIndex = Math.min(inDays.intValue() - 1, configs.size() - 1);
      ResponseAssert.isTrue(CommonErrorCode.NOT_FOUND_MAPPING_INFO, configIndex >= 0 && configIndex < configs.size());
      
      PropsActivityRewardConfig config = configs.get(configIndex);

      propsSendCommon.send(new PrizeDescribeRewardCmd()
          .setTrackId(cmd.getId())
          .setAcceptUserId(cmd.getReqUserId())
          .setOrigin(SendPropsOrigin.DAILY_REGISTER)
          .setSysOrigin(cmd.requireReqSysOriginEnum())
          .setPrizes(Collections.singletonList(new PrizeDescribe()
              .setTrackId(config.getGroupId())
              .setType(config.getType())
              .setDetailType(config.getDetailType())
              .setContent(config.getContent())
              .setQuantity(config.getQuantity())))
      );

      return checkInCacheService.getCheckInDays(sevenCheckInKey);

    } finally {
      redisService.unlock(lockKey);
    }
  }

  /**
   * 校验设备唯一性（防止小号薅羊毛）
   */
  private void checkDeviceUnique(Long activityId, Long userId) {
    LatestMobileDevice device = latestMobileDeviceService.getByUserId(userId);

    if (device == null) {
      return;
    }

    // 1. 先用新指纹查询（不含IMEI）
    String fingerprintV2 = EnhancedDeviceUtil.buildEnhancedDeviceFingerprintV2(device);
    LotteryDeviceRecord existingRecord = lotteryDeviceRecordService.getByActivityAndFingerprint(activityId, fingerprintV2);

    if (existingRecord != null && !existingRecord.getUserId().equals(userId)) {
      ResponseAssert.isTrue(OtherErrorCode.DEVICE_ALREADY_USED, false);
    }

    // 2. 兜底：再用旧指纹查询（含IMEI）- 兼容历史数据
    String fingerprintV1 = EnhancedDeviceUtil.buildEnhancedDeviceFingerprint(device);
    if (!fingerprintV1.equals(fingerprintV2)) {  // 只有当两个指纹不同时才需要再查一次
      LotteryDeviceRecord existingRecordV1 = lotteryDeviceRecordService
              .getByActivityAndFingerprint(activityId, fingerprintV1);

      if (existingRecordV1 != null && !existingRecordV1.getUserId().equals(userId)) {
        ResponseAssert.isTrue(OtherErrorCode.DEVICE_ALREADY_USED, false);
      }
    }

    lotteryDeviceRecordService.recordDevice(activityId, userId, fingerprintV2);

  }

  /**
   * 构建7日签到统计key
   * @param isAziziCheckIn 是否为Azizi签到
   * @param userId 用户ID
   * @return 签到统计key
   */
  private String buildSevenCheckInKey(boolean isAziziCheckIn, Long userId) {
    return isAziziCheckIn 
        ? CheckInKeys.AZIZI_SEVEN_CHECK_IN.getKey(userId)
        : CheckInKeys.SEVEN_CHECK_IN.getKey(userId);
  }

  /**
   * 获取过期时间
   * @param isAziziCheckIn 是否为Azizi签到
   * @param userId 用户ID
   * @return 过期时间（毫秒）
   */
  private long getExpiredTime(boolean isAziziCheckIn, Long userId) {
    if (isAziziCheckIn) {
      return 60 * 60 * 24 * 365 * 1000L;
    }

    Duration duration = getDuration();
    long expiredTime = duration.plusDays(1).toMillis();
    
    String sevenCheckInKey = buildSevenCheckInKey(isAziziCheckIn, userId);
    Integer checkInDays = checkInCacheService.getCheckInDays(sevenCheckInKey);
    // 第6天及以上（包括脏数据）都在今天结束时过期，准备开始新周期
    if (checkInDays != null && checkInDays >= 6) {
      expiredTime = duration.toMillis();
    }
    return expiredTime;
  }

  private Duration getDuration() {
    return ZonedDateTimeUtils.todayInterval(ZonedId.ASIA_RIYADH.getZonedId(), 0);
  }


}
