package com.haha.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haha.common.constant.CommonConstants;
import com.haha.common.enums.ActivityStatusEnum;
import com.haha.common.enums.ApplyScopeEnum;
import com.haha.common.enums.InviteRewardStatusEnum;
import com.haha.common.enums.InviteStatusEnum;
import com.haha.common.exception.BusinessException;
import com.haha.common.vo.StatusLabel;
import com.haha.entity.CouponTemplate;
import com.haha.entity.InviteActivity;
import com.haha.entity.InviteRecord;
import com.haha.entity.InviteReward;
import com.haha.entity.UserCoupon;
import com.haha.entity.dto.CouponDistributeDTO;
import com.haha.entity.dto.InviteActivityCreateDTO;
import com.haha.entity.dto.InviteActivityUpdateDTO;
import com.haha.entity.dto.InviteRecordQueryDTO;
import com.haha.mapper.InviteActivityMapper;
import com.haha.mapper.InviteRecordMapper;
import com.haha.mapper.InviteRewardMapper;
import com.haha.service.CouponTemplateService;
import com.haha.service.InviteActivityService;
import com.haha.service.InviteNotificationService;
import com.haha.service.RedisService;
import com.haha.service.UserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 邀请活动服务实现类
 * 实现邀请活动的完整业务逻辑，包括活动管理、邀请码生成、邀请关系绑定、
 * 邀请激活、奖励发放、防刷机制等功能
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class InviteActivityServiceImpl extends ServiceImpl<InviteActivityMapper, InviteActivity> implements InviteActivityService {

    private final InviteActivityMapper inviteActivityMapper;
    private final InviteRecordMapper inviteRecordMapper;
    private final InviteRewardMapper inviteRewardMapper;
    private final CouponTemplateService couponTemplateService;
    private final UserCouponService userCouponService;
    private final RedisService redisService;
    private final StringRedisTemplate stringRedisTemplate;
    private final InviteNotificationService notificationService;

    /**
     * Base62编码字符集，用于生成短邀请码
     */
    private static final String BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    /**
     * 随机数生成器，用于生成安全的随机数
     */
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    /**
     * Redis防刷Key前缀：IP频率限制
     */
    private static final String REDIS_KEY_INVITE_IP_PREFIX = "invite:anti_fraud:ip:";

    /**
     * Redis防刷Key前缀：设备频率限制
     */
    private static final String REDIS_KEY_INVITE_DEVICE_PREFIX = "invite:anti_fraud:device:";

    // ==================== CRUD 操作 ====================

    @Override
    public IPage<InviteActivity> getPage(InviteRecordQueryDTO queryDTO) {
        queryDTO.validate();
        Page<InviteActivity> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());

        LambdaQueryWrapper<InviteActivity> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.hasText(queryDTO.getActivityName()), InviteActivity::getActivityName, queryDTO.getActivityName());
        wrapper.eq(queryDTO.getStatus() != null, InviteActivity::getStatus, queryDTO.getStatus());
        wrapper.ge(queryDTO.getStartDate() != null, InviteActivity::getStartTime, queryDTO.getStartDate());
        wrapper.le(queryDTO.getEndDate() != null, InviteActivity::getEndTime, queryDTO.getEndDate());
        wrapper.eq(InviteActivity::getDeleted, CommonConstants.NOT_DELETED);
        wrapper.orderByDesc(InviteActivity::getCreateTime);

        IPage<InviteActivity> result = this.page(page, wrapper);
        result.getRecords().forEach(this::fillLabels);

        return result;
    }

    @Override
    public InviteActivity getDetail(Long id) {
        InviteActivity activity = this.getById(id);
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }
        fillLabels(activity);
        return activity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public InviteActivity create(InviteActivityCreateDTO dto, Long creatorId, String creatorName) {
        // 参数校验：结束时间必须大于开始时间
        if (dto.getEndTime().isBefore(dto.getStartTime()) || dto.getEndTime().isEqual(dto.getStartTime())) {
            throw new BusinessException(400, "结束时间必须大于开始时间");
        }

        // 获取双向奖励配置
        Long inviterCouponTemplateId = dto.getInviterCouponTemplateId();
        Long inviteeCouponTemplateId = dto.getInviteeCouponTemplateId();

        // 【核心校验】至少需要配置一组奖励（邀请人奖励或被邀请人奖励）
        if (inviterCouponTemplateId == null && inviteeCouponTemplateId == null) {
            throw new BusinessException(400, "至少需要配置一组奖励（邀请人奖励或被邀请人奖励）");
        }

        // 校验邀请人优惠券模板是否存在且可用（如果配置了）
        if (inviterCouponTemplateId != null) {
            CouponTemplate inviterTemplate = couponTemplateService.getById(inviterCouponTemplateId);
            if (inviterTemplate == null) {
                throw new BusinessException(400, "邀请人优惠券模板不存在");
            }
        }

        // 校验被邀请人优惠券模板是否存在且可用（如果配置了）
        if (inviteeCouponTemplateId != null) {
            CouponTemplate inviteeTemplate = couponTemplateService.getById(inviteeCouponTemplateId);
            if (inviteeTemplate == null) {
                throw new BusinessException(400, "被邀请人优惠券模板不存在");
            }
        }

        InviteActivity activity = new InviteActivity();
        activity.setActivityName(dto.getActivityName());
        activity.setActivityDesc(dto.getActivityDesc());
        activity.setStartTime(dto.getStartTime());
        activity.setEndTime(dto.getEndTime());
        activity.setStatus(ActivityStatusEnum.DRAFT.getCode());
        activity.setCouponTemplateId(inviterCouponTemplateId); // 邀请人优惠券模板ID
        activity.setInviteeCouponTemplateId(inviteeCouponTemplateId); // 被邀请人优惠券模板ID
        activity.setDailyLimit(dto.getDailyLimit() != null ? dto.getDailyLimit() : 10);
        activity.setTotalLimit(dto.getTotalLimit() != null ? dto.getTotalLimit() : 100);
        activity.setRequireFirstOrder(dto.getRequireFirstOrder() != null ? dto.getRequireFirstOrder() : 1);
        activity.setMinOrderAmount(dto.getMinOrderAmount() != null ? dto.getMinOrderAmount() : BigDecimal.ZERO);
        activity.setInviteCodePrefix(StringUtils.hasText(dto.getInviteCodePrefix()) ? dto.getInviteCodePrefix() : "INV");
        activity.setApplyScope(dto.getApplyScope());
        activity.setProductScope(dto.getProductScope());
        activity.setTotalInviteCount(0);
        activity.setValidInviteCount(0);
        activity.setRewardCount(0);
        activity.setCreatorId(creatorId);
        activity.setCreatorName(creatorName);
        activity.setDeleted(CommonConstants.NOT_DELETED);

        this.save(activity);

        log.info("创建邀请活动成功: id={}, name={}, creator={}, inviterCouponTemplateId={}, inviteeCouponTemplateId={}",
                activity.getId(), activity.getActivityName(), creatorName, inviterCouponTemplateId, inviteeCouponTemplateId);
        return activity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public InviteActivity update(InviteActivityUpdateDTO dto) {
        InviteActivity activity = this.getById(dto.getId());
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        // 只有草稿状态允许编辑
        if (!ActivityStatusEnum.canEdit(activity.getStatus())) {
            throw new BusinessException(400, "当前状态不允许编辑");
        }

        // 参数校验：如果传了时间，需要校验
        if (dto.getStartTime() != null && dto.getEndTime() != null) {
            if (dto.getEndTime().isBefore(dto.getStartTime()) || dto.getEndTime().isEqual(dto.getStartTime())) {
                throw new BusinessException(400, "结束时间必须大于开始时间");
            }
        }

        if (dto.getActivityName() != null) {
            activity.setActivityName(dto.getActivityName());
        }
        if (dto.getActivityDesc() != null) {
            activity.setActivityDesc(dto.getActivityDesc());
        }
        if (dto.getStartTime() != null) {
            activity.setStartTime(dto.getStartTime());
        }
        if (dto.getEndTime() != null) {
            activity.setEndTime(dto.getEndTime());
        }

        // 【双向奖励】处理邀请人优惠券模板ID更新（优先使用 inviterCouponTemplateId，兼容旧的 couponTemplateId）
        Long newInviterCouponTemplateId = dto.getInviterCouponTemplateId();
        if (newInviterCouponTemplateId == null) {
            newInviterCouponTemplateId = dto.getCouponTemplateId(); // 兼容旧字段
        }
        if (newInviterCouponTemplateId != null) {
            // 校验邀请人优惠券模板是否存在
            CouponTemplate inviterTemplate = couponTemplateService.getById(newInviterCouponTemplateId);
            if (inviterTemplate == null) {
                throw new BusinessException(400, "邀请人优惠券模板不存在");
            }
            activity.setCouponTemplateId(newInviterCouponTemplateId);
        }

        // 【双向奖励】处理被邀请人优惠券模板ID更新
        Long newInviteeCouponTemplateId = dto.getInviteeCouponTemplateId();
        if (newInviteeCouponTemplateId != null) {
            // 校验被邀请人优惠券模板是否存在
            CouponTemplate inviteeTemplate = couponTemplateService.getById(newInviteeCouponTemplateId);
            if (inviteeTemplate == null) {
                throw new BusinessException(400, "被邀请人优惠券模板不存在");
            }
            activity.setInviteeCouponTemplateId(newInviteeCouponTemplateId);
        }

        // 【核心校验】更新后至少需要配置一组奖励
        Long finalInviterCouponTemplateId = activity.getCouponTemplateId();
        Long finalInviteeCouponTemplateId = activity.getInviteeCouponTemplateId();
        if (finalInviterCouponTemplateId == null && finalInviteeCouponTemplateId == null) {
            throw new BusinessException(400, "至少需要配置一组奖励（邀请人奖励或被邀请人奖励）");
        }

        if (dto.getDailyLimit() != null) {
            activity.setDailyLimit(dto.getDailyLimit());
        }
        if (dto.getTotalLimit() != null) {
            activity.setTotalLimit(dto.getTotalLimit());
        }
        if (dto.getRequireFirstOrder() != null) {
            activity.setRequireFirstOrder(dto.getRequireFirstOrder());
        }
        if (dto.getMinOrderAmount() != null) {
            activity.setMinOrderAmount(dto.getMinOrderAmount());
        }
        if (dto.getInviteCodePrefix() != null) {
            activity.setInviteCodePrefix(dto.getInviteCodePrefix());
        }
        if (dto.getApplyScope() != null) {
            activity.setApplyScope(dto.getApplyScope());
        }
        if (dto.getProductScope() != null) {
            activity.setProductScope(dto.getProductScope());
        }

        this.updateById(activity);

        log.info("更新邀请活动成功: id={}, name={}, inviterCouponTemplateId={}, inviteeCouponTemplateId={}",
                activity.getId(), activity.getActivityName(), finalInviterCouponTemplateId, finalInviteeCouponTemplateId);
        return activity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean publish(Long id) {
        InviteActivity activity = this.getById(id);
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        if (!ActivityStatusEnum.canPublish(activity.getStatus())) {
            throw new BusinessException(400, "当前状态不允许发布");
        }

        // 【发布前校验】检查是否已配置至少一组奖励（邀请人奖励或被邀请人奖励）
        Long inviterCouponTemplateId = activity.getCouponTemplateId();
        Long inviteeCouponTemplateId = activity.getInviteeCouponTemplateId();
        if (inviterCouponTemplateId == null && inviteeCouponTemplateId == null) {
            throw new BusinessException(400, "发布失败：活动未配置任何奖励，请至少配置邀请人奖励或被邀请人奖励");
        }

        LocalDateTime now = LocalDateTime.now();
        int newStatus = (now.isAfter(activity.getStartTime()) && now.isBefore(activity.getEndTime()))
                ? ActivityStatusEnum.ONGOING.getCode()
                : ActivityStatusEnum.PUBLISHED.getCode();

        boolean result = inviteActivityMapper.updateStatus(id, newStatus) > 0;

        if (result) {
            log.info("发布邀请活动成功: id={}, status={}, inviterCouponTemplateId={}, inviteeCouponTemplateId={}",
                    id, newStatus, inviterCouponTemplateId, inviteeCouponTemplateId);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean pause(Long id) {
        InviteActivity activity = this.getById(id);
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        if (!ActivityStatusEnum.canPause(activity.getStatus())) {
            throw new BusinessException(400, "当前状态不允许暂停");
        }

        boolean result = inviteActivityMapper.updateStatus(id, ActivityStatusEnum.PAUSED.getCode()) > 0;

        if (result) {
            log.info("暂停邀请活动成功: id={}", id);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean resume(Long id) {
        InviteActivity activity = this.getById(id);
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        if (!ActivityStatusEnum.canResume(activity.getStatus())) {
            throw new BusinessException(400, "当前状态不允许恢复");
        }

        boolean result = inviteActivityMapper.updateStatus(id, ActivityStatusEnum.ONGOING.getCode()) > 0;

        if (result) {
            log.info("恢复邀请活动成功: id={}", id);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteActivity(Long id) {
        InviteActivity activity = this.getById(id);
        if (activity == null) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        if (!ActivityStatusEnum.canDelete(activity.getStatus())) {
            throw new BusinessException(400, "当前状态不允许删除");
        }

        activity.setDeleted(CommonConstants.DELETED);
        boolean result = this.updateById(activity);

        if (result) {
            log.info("删除邀请活动成功: id={}", id);
        }
        return result;
    }

    // ==================== 邀请码相关 ====================

    @Override
    public String generateInviteCode(Long userId, Long activityId) {
        // 获取活动信息以获取前缀配置
        InviteActivity activity = inviteActivityMapper.selectById(activityId);
        if (activity == null) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        String prefix = activity.getInviteCodePrefix();
        LocalDateTime now = LocalDateTime.now();

        // 构建邀请码组成部分
        String timestamp = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String userIdEncoded = encodeToBase62(userId);
        String activityIdEncoded = encodeToBase62(activityId);
        String randomPart = generateRandomString(4);

        // 组装邀请码：前缀 + 日期 + 用户ID编码 + 活动ID编码 + 随机数
        String inviteCode = String.format("%s%s%s%s%s",
                prefix,
                timestamp,
                userIdEncoded,
                activityIdEncoded,
                randomPart);

        log.info("生成邀请码成功: code={}, userId={}, activityId={}", inviteCode, userId, activityId);
        return inviteCode;
    }

    @Override
    public InviteActivity getOngoingActivity() {
        LocalDateTime now = LocalDateTime.now();
        List<InviteActivity> ongoingList = inviteActivityMapper.selectOngoing(now);
        if (ongoingList == null || ongoingList.isEmpty()) {
            return null;
        }
        InviteActivity activity = ongoingList.get(0);
        fillLabels(activity);
        return activity;
    }

    @Override
    public List<InviteActivity> getOngoingActivities() {
        LocalDateTime now = LocalDateTime.now();
        List<InviteActivity> ongoingList = inviteActivityMapper.selectOngoing(now);
        if (ongoingList == null) {
            return java.util.Collections.emptyList();
        }
        return ongoingList;
    }

    // ==================== 邀请关系绑定 ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bindInviteRelation(Long activityId, Long inviterUserId, Long inviteeUserId,
                                      String inviteCode, String sourceChannel, String deviceInfo, String ipAddress) {
        // 1. 参数校验
        if (inviterUserId.equals(inviteeUserId)) {
            throw new BusinessException(400, "不能邀请自己");
        }

        // 2. 检查被邀请人是否已被邀请过
        InviteRecord existingRecord = inviteRecordMapper.selectByInviteeUserId(inviteeUserId);
        if (existingRecord != null) {
            log.warn("该用户已被邀请过: inviteeUserId={}, existingRecordId={}", inviteeUserId, existingRecord.getId());
            throw new BusinessException(400, "该用户已参与过邀请活动");
        }

        // 3. 检查活动是否在进行中
        InviteActivity activity = this.getById(activityId);
        if (activity == null || activity.getDeleted() == CommonConstants.DELETED) {
            throw new BusinessException(404, "邀请活动不存在");
        }
        if (activity.getStatus() != ActivityStatusEnum.ONGOING.getCode()) {
            throw new BusinessException(400, "邀请活动未在进行中");
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(activity.getStartTime()) || now.isAfter(activity.getEndTime())) {
            throw new BusinessException(400, "邀请活动不在有效期内");
        }

        // 4. 执行防刷检查
        boolean antiFraudPass = checkAntiFraud(inviterUserId, activityId, ipAddress, deviceInfo);
        if (!antiFraudPass) {
            throw new BusinessException(400, "邀请频率过高，请稍后再试");
        }

        // 5. 创建邀请记录
        InviteRecord record = new InviteRecord();
        record.setActivityId(activityId);
        record.setInviterUserId(inviterUserId);
        record.setInviteeUserId(inviteeUserId);
        record.setInviteCode(inviteCode);
        record.setStatus(InviteStatusEnum.PENDING.getCode()); // 待激活
        record.setInviteTime(LocalDateTime.now());
        record.setSourceChannel(sourceChannel);
        record.setDeviceInfo(deviceInfo);
        record.setIpAddress(ipAddress);
        record.setRemark("");

        inviteRecordMapper.insert(record);

        // 6. 更新活动的总邀请数+1
        inviteActivityMapper.incrementTotalInviteCount(activityId);

        // 7. 发送新好友注册通知给邀请人（异步，不影响主流程）
        try {
            notificationService.sendNewInviteeNotification(inviterUserId, inviteeUserId.toString());
        } catch (Exception notifyEx) {
            log.error("发送新好友注册通知失败（不影响业务）: inviterUserId={}, error={}",
                    inviterUserId, notifyEx.getMessage(), notifyEx);
        }

        // 8. 向被邀请人即时发放新人优惠券（异步，发券失败不影响绑定关系）
        try {
            boolean couponResult = distributeInviteeCoupon(record.getId());
            if (couponResult) {
                log.info("被邀请人优惠券发放成功: recordId={}, inviteeUserId={}", record.getId(), inviteeUserId);
            } else {
                log.warn("被邀请人优惠券发放失败（已记录，可后续手动补发）: recordId={}, inviteeUserId={}",
                        record.getId(), inviteeUserId);
            }
        } catch (Exception couponEx) {
            log.error("向被邀请人发放优惠券异常（不影响绑定关系）: recordId={}, inviteeUserId={}, error={}",
                    record.getId(), inviteeUserId, couponEx.getMessage(), couponEx);
        }

        log.info("绑定邀请关系成功: recordId={}, inviterUserId={}, inviteeUserId={}, inviteCode={}",
                record.getId(), inviterUserId, inviteeUserId, inviteCode);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bindInviteRelation(Long inviteeUserId, String inviteCode, String sourceChannel, String deviceInfo) {
        InviteRecord record = getInviteRecordByCode(inviteCode);
        if (record == null) {
            log.warn("[邀请] 邀请码无效或已过期 - userId: {}, inviteCode: {}", inviteeUserId, inviteCode);
            return false;
        }
        return bindInviteRelation(
                record.getActivityId(),
                record.getInviterUserId(),
                inviteeUserId,
                inviteCode,
                sourceChannel,
                deviceInfo,
                ""
        );
    }

    // ==================== 邀请激活 ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean activateInviteRecord(Long inviteeUserId, Long orderId) {
        // 1. 根据被邀请人ID查询待激活的邀请记录
        LambdaQueryWrapper<InviteRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(InviteRecord::getInviteeUserId, inviteeUserId);
        wrapper.eq(InviteRecord::getStatus, InviteStatusEnum.PENDING.getCode()); // 待激活状态
        wrapper.orderByDesc(InviteRecord::getCreateTime);
        wrapper.last("LIMIT 1");

        InviteRecord record = inviteRecordMapper.selectOne(wrapper);
        if (record == null) {
            log.warn("未找到待激活的邀请记录: inviteeUserId={}", inviteeUserId);
            return false;
        }

        // 2. 获取活动配置，检查是否要求首单
        InviteActivity activity = this.getById(record.getActivityId());
        if (activity == null) {
            log.error("邀请记录关联的活动不存在: activityId={}", record.getActivityId());
            return false;
        }

        // TODO: 这里可以添加订单校验逻辑，验证是否为首单以及金额是否达标
        // 目前简化处理，直接激活

        // 3. 更新邀请记录状态为已激活
        int updateResult = inviteRecordMapper.updateToActivated(record.getId());
        if (updateResult <= 0) {
            log.error("更新邀请记录为已激活失败: recordId={}", record.getId());
            return false;
        }

        // 4. 更新活动的有效邀请数+1
        inviteActivityMapper.incrementValidInviteCount(record.getActivityId());

        log.info("激活邀请记录成功: recordId={}, inviteeUserId={}, orderId={}",
                record.getId(), inviteeUserId, orderId);

        // 5. 异步发放奖励（或同步发放）
        try {
            distributeReward(record.getId());
        } catch (Exception e) {
            log.error("发放奖励失败（不影响激活结果）: recordId={}, error={}",
                    record.getId(), e.getMessage(), e);
            // 奖励发放失败不影响激活结果，可以后续手动补发
        }

        return true;
    }

    // ==================== 奖励发放 ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean distributeReward(Long recordId) {
        log.info("[邀请人奖励] 开始发放邀请人奖励: recordId={}", recordId);

        // 1. 根据记录ID查询邀请记录
        InviteRecord record = inviteRecordMapper.selectById(recordId);
        if (record == null) {
            log.error("[邀请人奖励] 邀请记录不存在: recordId={}", recordId);
            return false;
        }

        // 2. 检查记录状态是否为已激活
        if (record.getStatus() != InviteStatusEnum.ACTIVATED.getCode()) {
            log.warn("[邀请人奖励] 邀请记录状态不正确，无法发放奖励: recordId={}, status={}", recordId, record.getStatus());
            return false;
        }

        // 3. 获取活动配置
        InviteActivity activity = this.getById(record.getActivityId());
        if (activity == null) {
            log.error("[邀请人奖励] 活动不存在: activityId={}", record.getActivityId());
            return false;
        }

        // 4. 【新增】配置检查：仅在配置了邀请人优惠券模板时才发放（与被邀请人即时发券逻辑解耦）
        Long inviterCouponTemplateId = activity.getCouponTemplateId(); // couponTemplateId 即为邀请人优惠券模板ID
        if (inviterCouponTemplateId == null) {
            log.info("[邀请人奖励] 活动未配置邀请人优惠券模板，跳过发放: activityId={}, recordId={}, inviterUserId={}",
                    activity.getId(), recordId, record.getInviterUserId());
            return true; // 返回true表示成功（虽然没发券，但这是预期行为，兼容单向奖励模式）
        }

        // 5. 【优化】防重复发放检查：查询是否已存在该邀请记录的邀请人奖励记录（reward_type=0）
        InviteReward existingInviterReward = inviteRewardMapper.selectByRecordIdAndRewardType(recordId, 0); // rewardType=0 表示邀请人
        if (existingInviterReward != null) {
            if (existingInviterReward.getStatus() == InviteRewardStatusEnum.DISTRIBUTED.getCode()) {
                log.warn("[邀请人奖励] 邀请人奖励已发放，不能重复发放: recordId={}, rewardId={}, inviterUserId={}",
                        recordId, existingInviterReward.getId(), record.getInviterUserId());
                return false;
            } else if (existingInviterReward.getStatus() == InviteRewardStatusEnum.PENDING.getCode()
                    || existingInviterReward.getStatus() == InviteRewardStatusEnum.FAILED.getCode()) {
                log.warn("[邀请人奖励] 邀请人奖励记录已存在（状态={}），不能重复创建: recordId={}, rewardId={}, status={}",
                        existingInviterReward.getStatus() == InviteRewardStatusEnum.PENDING.getCode() ? "待发放" : "失败",
                        recordId, existingInviterReward.getId(), existingInviterReward.getStatus());
                return false;
            }
        }

        // 6. 创建待发放的奖励记录（先创建记录，再发放优惠券）
        InviteReward reward = new InviteReward();
        reward.setActivityId(record.getActivityId());
        reward.setRecordId(recordId);
        reward.setInviterUserId(record.getInviterUserId());
        reward.setCouponTemplateId(inviterCouponTemplateId); // 使用经过配置检查的模板ID
        reward.setRewardType(0); // 邀请人奖励（确保与被邀请人奖励rewardType=1区分）
        reward.setTriggerType(0); // 首单完成触发（确保与被邀请人即时发放triggerType=1区分）
        reward.setStatus(InviteRewardStatusEnum.PENDING.getCode()); // 待发放
        reward.setUserCouponId(null);
        reward.setCouponCode(null);
        reward.setFailReason(null);
        reward.setDistributeTime(null);
        reward.setRetryCount(0);
        reward.setOperatorId(null);
        reward.setOperatorName(null);

        inviteRewardMapper.insert(reward);

        log.info("[邀请人奖励] 创建邀请人奖励记录成功: recordId={}, rewardId={}, inviterUserId={}, couponTemplateId={}",
                recordId, reward.getId(), record.getInviterUserId(), inviterCouponTemplateId);

        // 7. 调用UserCouponService发放优惠券给邀请人
        try {
            CouponDistributeDTO distributeDTO = new CouponDistributeDTO();
            distributeDTO.setUserId(record.getInviterUserId());
            distributeDTO.setTemplateId(inviterCouponTemplateId); // 使用经过配置检查的模板ID
            distributeDTO.setSource("邀请活动-邀请人奖励");
            distributeDTO.setSourceId(String.valueOf(recordId));

            userCouponService.distributeCoupon(distributeDTO, null, "系统自动发放");

            // TODO: 需要从distributeCoupon返回值中获取userCouponId和couponCode
            // 这里暂时设置为占位值，实际应根据返回值更新
            Long userCouponId = -1L; // 应从实际发放结果获取
            String couponCode = "INVITER_" + System.currentTimeMillis(); // 应从实际发放结果获取

            // 8. 更新奖励记录为已发放
            inviteRewardMapper.updateToDistributed(reward.getId(), userCouponId, couponCode);

            // 9. 更新邀请记录状态为已奖励
            inviteRecordMapper.updateToRewarded(recordId);

            // 10. 更新活动的奖励发放数+1
            inviteActivityMapper.incrementRewardCount(record.getActivityId());

            // 11. 发送消息通知（仅邀请人奖励通知）
            try {
                // 获取优惠券模板信息用于通知
                CouponTemplate couponTemplate = couponTemplateService.getById(inviterCouponTemplateId);
                if (couponTemplate != null) {
                    notificationService.sendRewardSuccessNotification(
                        record.getInviterUserId(),
                        couponTemplate.getCouponName(),
                        couponTemplate.getDiscountValue()
                    );
                }
            } catch (Exception notifyEx) {
                log.error("[邀请人奖励] 发送奖励通知失败（不影响业务）: recordId={}, error={}",
                        recordId, notifyEx.getMessage(), notifyEx);
            }

            log.info("[邀请人奖励] 发放邀请人奖励成功: recordId={}, rewardId={}, inviterUserId={}, userCouponId={}",
                    recordId, reward.getId(), record.getInviterUserId(), userCouponId);
            return true;

        } catch (Exception e) {
            // 发放失败，更新奖励状态为失败
            log.error("[邀请人奖励] 发放优惠券失败: recordId={}, inviterUserId={}, couponTemplateId={}, error={}",
                    recordId, record.getInviterUserId(), inviterCouponTemplateId, e.getMessage(), e);
            inviteRewardMapper.updateToFailed(reward.getId(), "邀请人优惠券发放失败: " + e.getMessage());
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean distributeInviteeCoupon(Long recordId) {
        log.info("开始向被邀请人发放新人优惠券: recordId={}", recordId);

        // 1. 查询邀请记录
        InviteRecord record = inviteRecordMapper.selectById(recordId);
        if (record == null) {
            log.error("邀请记录不存在，无法发放被邀请人优惠券: recordId={}", recordId);
            return false;
        }

        // 2. 检查是否已发放过被邀请人奖励（防止重复发放）
        InviteReward existingInviteeReward = inviteRewardMapper.selectByRecordIdAndRewardType(recordId, 1); // rewardType=1 表示被邀请人
        if (existingInviteeReward != null && existingInviteeReward.getStatus() == InviteRewardStatusEnum.DISTRIBUTED.getCode()) {
            log.warn("被邀请人奖励已发放，不能重复发放: recordId={}, rewardId={}", recordId, existingInviteeReward.getId());
            return false;
        }

        // 3. 查询活动配置，获取 inviteeCouponTemplateId
        InviteActivity activity = this.getById(record.getActivityId());
        if (activity == null) {
            log.error("活动不存在，无法发放被邀请人优惠券: activityId={}", record.getActivityId());
            return false;
        }

        // 4. 如果 inviteeCouponTemplateId 为空，跳过不发券（兼容单向奖励模式）
        if (activity.getInviteeCouponTemplateId() == null) {
            log.info("活动未配置被邀请人优惠券模板，跳过发券: activityId={}, recordId={}", activity.getId(), recordId);
            return true; // 返回true表示成功（虽然没发券，但这是预期行为）
        }

        // 5. 创建待发放的被邀请人奖励记录
        InviteReward inviteeReward = new InviteReward();
        inviteeReward.setActivityId(record.getActivityId());
        inviteeReward.setRecordId(recordId);
        inviteeReward.setInviterUserId(record.getInviteeUserId()); // 注意：这里是被邀请人的用户ID
        inviteeReward.setCouponTemplateId(activity.getInviteeCouponTemplateId());
        inviteeReward.setRewardType(1); // 被邀请人奖励
        inviteeReward.setTriggerType(1); // 邀请绑定触发（即时发放）
        inviteeReward.setStatus(InviteRewardStatusEnum.PENDING.getCode()); // 待发放
        inviteeReward.setUserCouponId(null);
        inviteeReward.setCouponCode(null);
        inviteeReward.setFailReason(null);
        inviteeReward.setDistributeTime(null);
        inviteeReward.setRetryCount(0);
        inviteeReward.setOperatorId(null);
        inviteeReward.setOperatorName(null);

        inviteRewardMapper.insert(inviteeReward);

        // 6. 调用优惠券服务发放优惠券给被邀请人
        try {
            CouponDistributeDTO distributeDTO = new CouponDistributeDTO();
            distributeDTO.setUserId(record.getInviteeUserId()); // 发放给被邀请人
            distributeDTO.setTemplateId(activity.getInviteeCouponTemplateId()); // 使用被邀请人优惠券模板
            distributeDTO.setSource("邀请活动-新人注册奖励");
            distributeDTO.setSourceId(String.valueOf(recordId));

            userCouponService.distributeCoupon(distributeDTO, null, "系统自动发放");

            // TODO: 需要从distributeCoupon返回值中获取userCouponId和couponCode
            Long userCouponId = -1L; // 应从实际发放结果获取
            String couponCode = "INVITEE_" + System.currentTimeMillis(); // 应从实际发放结果获取

            // 7. 更新奖励记录为已发放
            inviteRewardMapper.updateToDistributed(inviteeReward.getId(), userCouponId, couponCode);

            // 8. 记录日志
            log.info("向被邀请人发放新人优惠券成功: recordId={}, rewardId={}, inviteeUserId={}, couponTemplateId={}",
                    recordId, inviteeReward.getId(), record.getInviteeUserId(), activity.getInviteeCouponTemplateId());

            // 9. 发送通知给被邀请人（可选）
            try {
                CouponTemplate couponTemplate = couponTemplateService.getById(activity.getInviteeCouponTemplateId());
                if (couponTemplate != null) {
                    notificationService.sendRewardSuccessNotification(
                        record.getInviteeUserId(),
                        couponTemplate.getCouponName(),
                        couponTemplate.getDiscountValue()
                    );
                }
            } catch (Exception notifyEx) {
                log.error("发送被邀请人奖励通知失败（不影响业务）: recordId={}, error={}",
                        recordId, notifyEx.getMessage(), notifyEx);
            }

            return true;

        } catch (Exception e) {
            // 10. 发券失败时记录到 InviteReward 表（status=失败, 失败原因），不抛出异常
            log.error("向被邀请人发放优惠券失败: recordId={}, inviteeUserId={}, error={}",
                    recordId, record.getInviteeUserId(), e.getMessage(), e);
            inviteRewardMapper.updateToFailed(inviteeReward.getId(), "被邀请人优惠券发放失败: " + e.getMessage());
            return false;
        }
    }

    // ==================== 防刷检查 ====================

    @Override
    public boolean checkAntiFraud(Long inviterUserId, Long activityId, String ipAddress, String deviceInfo) {
        // 1. 获取活动配置
        InviteActivity activity = this.getById(activityId);
        if (activity == null) {
            return false;
        }

        LocalDate today = LocalDate.now();

        // 2. 检查今日邀请数量是否超过每日限制
        int todayInviteCount = inviteRecordMapper.countTodayInvites(inviterUserId, activityId, today);
        if (todayInviteCount >= activity.getDailyLimit()) {
            log.warn("今日邀请数量已达上限: userId={}, activityId={}, todayCount={}, dailyLimit={}",
                    inviterUserId, activityId, todayInviteCount, activity.getDailyLimit());
            return false;
        }

        // 3. 检查总邀请数量是否超过总限制
        int totalInviteCount = inviteRecordMapper.countTotalInvitesByUser(inviterUserId, activityId);
        if (totalInviteCount >= activity.getTotalLimit()) {
            log.warn("总邀请数量已达上限: userId={}, activityId{}, totalCount={}, totalLimit={}",
                    inviterUserId, activityId, totalInviteCount, activity.getTotalLimit());
            return false;
        }

        // 4. 使用Redis检查IP频率限制（同一IP在1小时内最多邀请50次）
        if (StringUtils.hasText(ipAddress)) {
            String ipKey = REDIS_KEY_INVITE_IP_PREFIX + ipAddress;
            Long ipCount = stringRedisTemplate.opsForValue().increment(ipKey);
            if (ipCount != null && ipCount == 1) {
                // 第一次设置，过期时间1小时
                stringRedisTemplate.expire(ipKey, 1, TimeUnit.HOURS);
            }
            if (ipCount != null && ipCount > 50) {
                log.warn("IP邀请频率过高: ipAddress={}, count={}", ipAddress, ipCount);
                return false;
            }
        }

        // 5. 使用Redis检查设备频率限制（同一设备在1小时内最多邀请30次）
        if (StringUtils.hasText(deviceInfo)) {
            String deviceKey = REDIS_KEY_INVITE_DEVICE_PREFIX + deviceInfo.hashCode();
            Long deviceCount = stringRedisTemplate.opsForValue().increment(deviceKey);
            if (deviceCount != null && deviceCount == 1) {
                // 第一次设置，过期时间1小时
                stringRedisTemplate.expire(deviceKey, 1, TimeUnit.HOURS);
            }
            if (deviceCount != null && deviceCount > 30) {
                log.warn("设备邀请频率过高: deviceInfo={}, count={}", deviceInfo, deviceCount);
                return false;
            }
        }

        // 所有检查通过
        return true;
    }

    // ==================== 统计查询 ====================

    @Override
    public Map<String, Object> getActivityStatistics(Long activityId) {
        Map<String, Object> stats = new HashMap<>();

        // 1. 获取活动基本信息
        InviteActivity activity = this.getById(activityId);
        if (activity == null) {
            throw new BusinessException(404, "邀请活动不存在");
        }

        // 2. 从数据库统计各项指标
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayStart = now.toLocalDate().atStartOfDay();
        LocalDateTime todayEnd = todayStart.plusDays(1).minusNanos(1);

        Map<String, Object> dbStats = inviteActivityMapper.selectStatisticsByActivityId(
                activityId, todayStart, todayEnd);

        // 3. 组装统计数据
        stats.put("activityId", activityId);
        stats.put("activityName", activity.getActivityName());
        stats.put("status", activity.getStatus());
        stats.put("statusLabel", InviteStatusEnum.getLabelByCode(activity.getStatus()).getLabel());
        stats.put("startTime", activity.getStartTime());
        stats.put("endTime", activity.getEndTime());
        stats.put("totalInviteCount", activity.getTotalInviteCount()); // 总邀请数（从活动表）
        stats.put("validInviteCount", activity.getValidInviteCount()); // 有效邀请数（从活动表）
        stats.put("rewardCount", activity.getRewardCount()); // 奖励发放数（从活动表）

        // 从查询结果补充详细统计
        if (dbStats != null) {
            stats.put("todayInvite", dbStats.get("todayInvite")); // 今日新增邀请数
            stats.put("validRate", calculateRate(activity.getTotalInviteCount(), activity.getValidInviteCount())); // 有效率
            stats.put("rewardRate", calculateRate(activity.getValidInviteCount(), activity.getRewardCount())); // 奖励发放率
        }

        // 4. 获取Top邀请达人
        List<Map<String, Object>> topInviters = inviteRecordMapper.selectTopInviters(activityId, 10);
        stats.put("topInviters", topInviters);

        // 5. 获取奖励发放统计
        Map<String, Object> rewardStats = inviteRewardMapper.selectRewardStatistics(activityId);
        if (rewardStats != null) {
            stats.put("rewardDistributedCount", rewardStats.get("distributedCount"));
            stats.put("rewardFailedCount", rewardStats.get("failedCount"));
        }

        return stats;
    }

    @Override
    public Map<String, Object> getMyInviteStatistics(Long userId, Long activityId) {
        Map<String, Object> stats = new HashMap<>();

        // 1. 统计当前用户的邀请数据
        int totalInvites = inviteRecordMapper.countTotalInvitesByUser(userId, activityId);
        LocalDate today = LocalDate.now();
        int todayInvites = inviteRecordMapper.countTodayInvites(userId, activityId, today);

        // 2. 统计各状态的邀请数量
        LambdaQueryWrapper<InviteRecord> pendingWrapper = new LambdaQueryWrapper<>();
        pendingWrapper.eq(InviteRecord::getInviterUserId, userId);
        pendingWrapper.eq(InviteRecord::getActivityId, activityId);
        pendingWrapper.eq(InviteRecord::getStatus, InviteStatusEnum.PENDING.getCode());
        long pendingCount = inviteRecordMapper.selectCount(pendingWrapper);

        LambdaQueryWrapper<InviteRecord> activatedWrapper = new LambdaQueryWrapper<>();
        activatedWrapper.eq(InviteRecord::getInviterUserId, userId);
        activatedWrapper.eq(InviteRecord::getActivityId, activityId);
        activatedWrapper.eq(InviteRecord::getStatus, InviteStatusEnum.ACTIVATED.getCode());
        long activatedCount = inviteRecordMapper.selectCount(activatedWrapper);

        LambdaQueryWrapper<InviteRecord> rewardedWrapper = new LambdaQueryWrapper<>();
        rewardedWrapper.eq(InviteRecord::getInviterUserId, userId);
        rewardedWrapper.eq(InviteRecord::getActivityId, activityId);
        rewardedWrapper.eq(InviteRecord::getStatus, InviteStatusEnum.REWARDED.getCode());
        long rewardedCount = inviteRecordMapper.selectCount(rewardedWrapper);

        // 3. 组装统计数据
        stats.put("userId", userId);
        stats.put("activityId", activityId);
        stats.put("totalInvites", totalInvites); // 总邀请数
        stats.put("todayInvites", todayInvites); // 今日邀请数
        stats.put("pendingCount", pendingCount); // 待激活数量
        stats.put("activatedCount", activatedCount); // 已激活数量
        stats.put("rewardedCount", rewardedCount); // 已获奖励数量

        return stats;
    }

    @Override
    public Map<String, Object> getMyInviteStatistics(Long userId) {
        InviteActivity ongoingActivity = getOngoingActivity();
        if (ongoingActivity == null) {
            return new HashMap<>();
        }
        return getMyInviteStatistics(userId, ongoingActivity.getId());
    }

    @Override
    public Map<String, Object> getInviteCodeInfo(Long userId) {
        InviteActivity ongoingActivity = getOngoingActivity();
        if (ongoingActivity == null) {
            return null;
        }
        String inviteCode = generateInviteCode(userId, ongoingActivity.getId());
        Map<String, Object> result = new HashMap<>();
        result.put("inviteCode", inviteCode);
        result.put("inviteLink", "pages/index/index?inviteCode=" + inviteCode);
        result.put("activityInfo", ongoingActivity);
        return result;
    }

    @Override
    public Map<String, Object> generatePosterInfo(Long userId) {
        InviteActivity ongoingActivity = getOngoingActivity();
        if (ongoingActivity == null) {
            return null;
        }
        String inviteCode = generateInviteCode(userId, ongoingActivity.getId());
        Map<String, Object> result = new HashMap<>();
        result.put("inviteCode", inviteCode);
        result.put("posterUrl", "");
        return result;
    }

    @Override
    public IPage<InviteRecord> getMyInviteRecords(Long userId, Long activityId, Integer page, Integer pageSize) {
        // 参数校验
        if (page == null || page < 1) {
            page = 1;
        }
        if (pageSize == null || pageSize < 1) {
            pageSize = 10;
        }
        if (pageSize > 100) {
            pageSize = 100;
        }

        int offset = (page - 1) * pageSize;

        // 查询用户的邀请记录
        List<InviteRecord> records = inviteRecordMapper.selectByInviterUserId(userId, activityId, offset, pageSize);

        // 查询总数
        LambdaQueryWrapper<InviteRecord> countWrapper = new LambdaQueryWrapper<>();
        countWrapper.eq(InviteRecord::getInviterUserId, userId);
        countWrapper.eq(InviteRecord::getActivityId, activityId);
        Long total = inviteRecordMapper.selectCount(countWrapper);

        // 组装分页结果
        Page<InviteRecord> result = new Page<>(page, pageSize, total);
        result.setRecords(records);

        // 填充标签
        records.forEach(record -> {
            StatusLabel statusLabel = InviteStatusEnum.getLabelByCode(record.getStatus());
            record.setStatusLabel(statusLabel.getLabel());
        });
        return result;
    }

    @Override
    public IPage<InviteRecord> getMyInviteRecords(Long userId, Integer page, Integer pageSize) {
        InviteActivity ongoingActivity = getOngoingActivity();
        if (ongoingActivity == null) {
            return new Page<>(page, pageSize, 0);
        }
        return getMyInviteRecords(userId, ongoingActivity.getId(), page, pageSize);
    }

    @Override
    public IPage<InviteRecord> getInviteRecords(InviteRecordQueryDTO queryDTO) {
        queryDTO.validate();
        Page<InviteRecord> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());

        LambdaQueryWrapper<InviteRecord> wrapper = new LambdaQueryWrapper<>();
        if (queryDTO.getActivityId() != null) {
            wrapper.eq(InviteRecord::getActivityId, queryDTO.getActivityId());
        }
        if (queryDTO.getInviterUserId() != null) {
            wrapper.eq(InviteRecord::getInviterUserId, queryDTO.getInviterUserId());
        }
        if (queryDTO.getStatus() != null) {
            wrapper.eq(InviteRecord::getStatus, queryDTO.getStatus());
        }
        if (queryDTO.getStartDate() != null) {
            wrapper.ge(InviteRecord::getInviteTime, queryDTO.getStartDate());
        }
        if (queryDTO.getEndDate() != null) {
            wrapper.le(InviteRecord::getInviteTime, queryDTO.getEndDate());
        }
        wrapper.orderByDesc(InviteRecord::getCreateTime);

        IPage<InviteRecord> result = inviteRecordMapper.selectPage(page, wrapper);
        result.getRecords().forEach(record -> {
            StatusLabel statusLabel = InviteStatusEnum.getLabelByCode(record.getStatus());
            record.setStatusLabel(statusLabel.getLabel());
        });

        return result;
    }

    // ==================== 手动补发奖励 ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean manualReissueReward(Long rewardId, Long operatorId, String operatorName) {
        // 1. 查询失败的奖励记录
        InviteReward reward = inviteRewardMapper.selectById(rewardId);
        if (reward == null) {
            throw new BusinessException(404, "奖励记录不存在");
        }

        // 2. 检查状态是否为发放失败
        if (reward.getStatus() != InviteRewardStatusEnum.FAILED.getCode()) {
            throw new BusinessException(400, "只有发放失败的奖励才能补发");
        }

        // 3. 重新调用优惠券发放逻辑
        try {
            CouponDistributeDTO distributeDTO = new CouponDistributeDTO();
            distributeDTO.setUserId(reward.getInviterUserId());
            distributeDTO.setTemplateId(reward.getCouponTemplateId());
            distributeDTO.setSource("邀请活动奖励-手动补发");
            distributeDTO.setSourceId(String.valueOf(reward.getRecordId()));

            userCouponService.distributeCoupon(distributeDTO, operatorId, operatorName);

            // TODO: 应从实际发放结果获取userCouponId和couponCode
            Long newUserCouponId = -2L; // 占位值
            String newCouponCode = "MANUAL_" + System.currentTimeMillis(); // 占位值

            // 4. 更新奖励状态为已补发
            inviteRewardMapper.updateToRetried(rewardId, newUserCouponId, newCouponCode,
                    operatorId, operatorName);

            log.info("手动补发奖励成功: rewardId={}, operatorId={}, operatorName={}",
                    rewardId, operatorId, operatorName);
            return true;

        } catch (Exception e) {
            log.error("手动补发奖励失败: rewardId={}, error={}", rewardId, e.getMessage(), e);
            // 再次标记为失败，增加重试次数
            inviteRewardMapper.updateToFailed(rewardId, "手动补发失败: " + e.getMessage());
            throw new BusinessException(500, "补发奖励失败: " + e.getMessage());
        }
    }

    @Override
    public InviteRecord getInviteRecordByCode(String inviteCode) {
        return inviteRecordMapper.selectByInviteCode(inviteCode);
    }

    // ==================== 定时任务方法 ====================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int[] autoUpdateActivityStatus() {
        LocalDateTime now = LocalDateTime.now();
        int startedCount = 0;
        int endedCount = 0;

        // 1. 将应该开始的活动状态更新为"进行中"（草稿/已发布 + 开始时间到了 + 未结束）
        List<InviteActivity> shouldStart = inviteActivityMapper.selectList(
            new LambdaQueryWrapper<InviteActivity>()
                .in(InviteActivity::getStatus, ActivityStatusEnum.DRAFT.getCode(), ActivityStatusEnum.PUBLISHED.getCode())
                .le(InviteActivity::getStartTime, now)
                .ge(InviteActivity::getEndTime, now)
                .eq(InviteActivity::getDeleted, CommonConstants.NOT_DELETED)
        );

        for (InviteActivity activity : shouldStart) {
            inviteActivityMapper.updateStatus(activity.getId(), ActivityStatusEnum.ONGOING.getCode());
            startedCount++;
            log.info("[邀请定时任务] 活动{}已自动开始", activity.getActivityName());
        }

        // 2. 将应该结束的活动状态更新为"已结束"（进行中 + 结束时间过了）
        List<InviteActivity> shouldEnd = inviteActivityMapper.selectList(
            new LambdaQueryWrapper<InviteActivity>()
                .eq(InviteActivity::getStatus, ActivityStatusEnum.ONGOING.getCode())
                .lt(InviteActivity::getEndTime, now)
                .eq(InviteActivity::getDeleted, CommonConstants.NOT_DELETED)
        );

        for (InviteActivity activity : shouldEnd) {
            inviteActivityMapper.updateStatus(activity.getId(), ActivityStatusEnum.ENDED.getCode());
            endedCount++;
            log.info("[邀请定时任务] 活动{}已自动结束", activity.getActivityName());
        }

        log.info("[邀请定时任务] 活动状态更新完成，开始{}个，结束{}个", startedCount, endedCount);
        return new int[]{startedCount, endedCount};
    }

    @Override
    public int[] retryFailedRewards(int maxRetryCount, int batchSize) {
        List<InviteReward> failedRewards = inviteRewardMapper.selectFailedRewardsForRetry(maxRetryCount, batchSize);

        int successCount = 0;
        int failCount = 0;

        for (InviteReward reward : failedRewards) {
            try {
                boolean success = distributeReward(reward.getRecordId());
                if (success) {
                    successCount++;
                    log.info("[邀请定时任务] 奖励重试成功 - rewardId: {}", reward.getId());
                } else {
                    failCount++;
                    log.warn("[邀请定时任务] 奖励重试仍失败 - rewardId: {}", reward.getId());
                }
            } catch (Exception e) {
                failCount++;
                log.error("[邀请定时任务] 奖励重试异常 - rewardId: {}", reward.getId(), e);
            }
        }

        log.info("[邀请定时任务] 失败奖励重试完成，成功{}个，失败{}个", successCount, failCount);
        return new int[]{successCount, failCount};
    }

    @Override
    public List<InviteActivity> getEndingSoonActivities(int daysAhead) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime deadline = now.plusDays(daysAhead);

        return inviteActivityMapper.selectList(
            new LambdaQueryWrapper<InviteActivity>()
                .eq(InviteActivity::getStatus, ActivityStatusEnum.ONGOING.getCode())
                .between(InviteActivity::getEndTime, now, deadline)
                .eq(InviteActivity::getDeleted, CommonConstants.NOT_DELETED)
        );
    }

    // ==================== 私有工具方法 ====================

    /**
     * 将数字编码为Base62字符串
     * 用于压缩用户ID和活动ID，缩短邀请码长度
     *
     * @param num 要编码的数字
     * @return Base62编码后的字符串
     */
    private String encodeToBase62(long num) {
        if (num == 0) {
            return "0";
        }

        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.append(BASE62_CHARS.charAt((int) (num % 62)));
            num /= 62;
        }
        return sb.reverse().toString();
    }

    /**
     * 生成指定长度的随机字符串（由Base62字符组成）
     *
     * @param length 字符串长度
     * @return 随机字符串
     */
    private String generateRandomString(int length) {
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append(BASE62_CHARS.charAt(SECURE_RANDOM.nextInt(BASE62_CHARS.length())));
        }
        return sb.toString();
    }

    /**
     * 计算比率（百分比）
     *
     * @param total 总数
     * @param part  部分
     * @return 百分比字符串（如 "85.5%"）
     */
    private String calculateRate(Integer total, Integer part) {
        if (total == null || total == 0) {
            return "0%";
        }
        double rate = (part.doubleValue() / total.doubleValue()) * 100;
        return String.format("%.1f%%", rate);
    }

    /**
     * 填充活动对象的标签字段（用于前端展示）
     *
     * @param activity 活动对象
     */
    private void fillLabels(InviteActivity activity) {
        // 状态标签
        StatusLabel statusLabel = ActivityStatusEnum.getLabelByCode(activity.getStatus());
        activity.setStatusLabel(statusLabel.getLabel());

        // 适用范围标签（如果有枚举的话，这里简单处理）
        if (activity.getApplyScope() != null) {
            activity.setApplyScopeLabel(ApplyScopeEnum.getLabelByCode(activity.getApplyScope()).getLabel());
        }

        // 商品范围标签
        if (activity.getProductScope() != null) {
            activity.setProductScopeLabel(activity.getProductScope() == 1 ? "全部商品" : "指定商品");
        }
    }
}
