package com.ssyf.task.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ssyf.module.robot.domain.entity.*;
import com.ssyf.module.robot.service.*;
import com.ssyf.module.robot.domain.strategy.*;
import com.ssyf.task.helper.TaskHelper;
import com.ssyf.task.models.RobotTaskContext;
import com.ssyf.task.websocket.LiveRoomWsManager;
import com.ssyf.task.websocket.WsMessageStorageService;
import com.ssyf.task.websocket.WsStatisticsService;
import com.ssyf.task.websocket.RRobotWsMessage;
import com.ssyf.third.bmiss.model.*;
import com.ssyf.third.bmiss.services.BusinessService;
import com.ssyf.third.bmiss.services.GiftService;
import com.ssyf.third.bmiss.services.LiveService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 直播间动作与生命周期服务
 * 负责机器人进入直播间后的全流程管理：
 * 1. 房间选择与校验
 * 2. 进入与状态维护
 * 3. 互动行为（点赞、送礼等）
 * 4. 停留控制与退出
 *
 * @author system
 * @since 2.0 Optimized
 */
@Slf4j
@Service
public class LiveRoomActionService {

    // ================== 依赖注入 ==================

    @Autowired
    @Lazy
    private BusinessService businessService;
    @Autowired
    @Lazy
    private LiveService liveService;
    @Autowired
    @Lazy
    private GiftService giftService;
    @Autowired
    @Lazy
    private RobotRecordService robotRecordService;
    @Autowired
    @Lazy
    private RRobotFeeService rRobotFeeService;
    @Autowired
    @Lazy
    private LiveRoomRelationService liveRoomRelationService;
    @Autowired
    @Lazy
    private RGiftConfigService rGiftConfigService;
    @Autowired
    @Lazy
    private LiveRoomStrategyService liveRoomStrategyService;
    @Autowired
    @Lazy
    private LiveRoomMonitorService monitorService; 
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    @Lazy
    private RRobotConsumptionRecordService consumptionRecordService;

    @Autowired
    private LiveRoomWsManager wsManager;
    @Autowired
    @Lazy
    private MessageTemplateService messageTemplateService;

    @Autowired
    @Lazy
    private ChatConfigService chatConfigService;

    @Autowired
    @Lazy
    private ChatStrategyFactory chatStrategyFactory;

    @Autowired
    @Lazy
    private RRobotRunningInfoService runningInfoService;
    @Lazy
    @Autowired
    private RobotManualControlService manualControlService; // 新增手动控制服务
    @Lazy
    @Autowired
    private GiftDecisionManager giftDecisionManager;

    @Autowired
    @Lazy
    private AsrSpeechManagerService asrSpeechManagerService; // 新增ASR语音解析管理服务

    @Autowired
    private WsStatisticsService wsStatisticsService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ChatInfoService chatInfoService;

    @Autowired
    private WsMessageStorageService wsMessageStorageService;

    // ================== Redis Key 常量 ==================

    private static final String REDIS_PREFIX_ENTERED_ROOMS = "robot:entered:rooms:";
    private static final String REDIS_PREFIX_CURRENT_ROOM = "robot:current:room:";
    private static final String REDIS_PREFIX_ROOM_DETAIL = "robot:room:detail:";
    private static final String REDIS_PREFIX_ENTER_HISTORY = "robot:enter:history:";
    private static final String REDIS_PREFIX_GIFTED_ANCHORS = "robot:gifted:anchors:";
    // 引用 StrategyService 定义的 Key 前缀
    private static final String REDIS_TASK_START_PREFIX = "robot:task:start:";
    // 新增缓存Key前缀：按天记录机器人的送礼计数
    // robot:gift:count:{date}:{robotUuid}:{anchorId} -> Integer (次数)
    private static final String REDIS_PREFIX_GIFT_COUNT_DAILY = "robot:gift:count:";
    // robot:gift:anchors:{date}:{robotUuid} -> Set<String> (已送过礼的主播ID集合)
    private static final String REDIS_PREFIX_GIFT_ANCHORS_DAILY = "robot:gift:anchors:";
    // 新增缓存Key前缀：聊天上下文存储
    // robot:chat:context:{robotUuid}:{roomId} -> ChatContext (聊天上下文)
    private static final String REDIS_PREFIX_CHAT_CONTEXT = "robot:chat:context:";


    // 缓存过期时间
    private static final long CACHE_EXPIRE_HOURS = 24;

    /**
     * 计算当前时间到当天结束时间的秒数
     *
     * @return 剩余秒数
     */
    private long getSecondsUntilEndOfDay() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime endOfDay = LocalDateTimeUtil.endOfDay(now);
        return java.time.Duration.between(now, endOfDay).getSeconds();
    }

    /**
     * 进入直播间主流程 (由 RobotAutomationService 调用)
     * 尝试选择并进入直播间，成功后将机器人托管给 MonitorService 进行驻留管理。
     *
     * @return true if successfully entered and handed over, or if action completed gracefully.
     */
    public boolean enterAndHandover(RRobotAccount robotAccount, RConfigRelation configRelation,
                                    RRunningConfig runningConfig, List<LiveRoom> allLiveRooms) {

        String robotUuid = robotAccount.getUuid();
        UserInfo userInfo = RobotTaskContext.getUserInfo();
        String token = RobotTaskContext.getToken();

        try {
            // 0. 前置校验
            if (CollUtil.isEmpty(allLiveRooms)) {
                log.debug("当前无在线直播间，跳过处理: {}", robotAccount.getEmail());
                return false;
            }

            // 1. 确定目标直播间
            LiveRoom targetLiveRoom = determineTargetRoom(robotAccount, runningConfig, allLiveRooms);
            if (targetLiveRoom == null) {
                log.debug("无法确定目标直播间，清理残留状态: {}", robotAccount.getEmail());
                handleLiveRoomUnavailable(robotAccount);
                return false;
            }

            Integer roomId = targetLiveRoom.getUser_id();
            String currentCachedRoomId = getCurrentRoomIdCache(robotUuid);

            // 2. 检查当前是否已在房间 (防止重复进)
            // 如果缓存里有且ID对得上，说明服务重启前可能就在这房间里
            if (currentCachedRoomId != null && currentCachedRoomId.equals(String.valueOf(roomId))) {
                //获取数据库数据判断是否在直播间
                RRobotRunningInfo old = runningInfoService.getOneByRobotUuid(robotAccount.getUuid());
                if (old != null && old.getCurrentBehavior() == 2) {
                    log.debug("检测到机器人已在房间中 (缓存)，重新注册到监控服务: {}", robotAccount.getEmail());
                    registerToMonitor(robotAccount, targetLiveRoom, configRelation, runningConfig, userInfo);
                    return true;
                }
            }

            // 3. 校验进入规则
            if (!checkEnterRules(targetLiveRoom, configRelation, runningConfig, token)) {
                log.debug("不满足进入条件，跳过: RoomID={}, Account={}", roomId, robotAccount.getEmail());
                return false;
            }

            wsManager.ensureConnection(targetLiveRoom, token, (clientId) -> {
                // 4. 执行进入动作
                boolean enterSuccess = enterLiveRoomApi(targetLiveRoom, robotAccount, configRelation, runningConfig, userInfo, token, clientId);
                if (enterSuccess) {
                    // 5. 【关键】成功后注册到监控服务 (移交控制权)
                    registerToMonitor(robotAccount, targetLiveRoom, configRelation, runningConfig, userInfo);
                } else {
                    // 进入失败，清理状态
                    clearCurrentRoomCache(robotUuid);
                }
            });

            return true;
        } catch (Exception e) {
            log.error("进入直播间流程异常: Account={}", robotAccount.getEmail(), e);
            // 记录异常日志到数据库
            robotRecordService.recordRobotRunRecord(null, robotAccount, configRelation, runningConfig, userInfo,
                    "进入直播间", "进入直播间异常", e.getMessage(), 2);
            return false;
        }
    }

    /**
     * 在房间内的互动逻辑 (由 LiveRoomMonitorService 周期性调用)
     * 该方法只负责执行互动，不负责进出房逻辑。
     */
    public void executeInRoomInteraction(LiveRoom room, RRobotAccount account, RConfigRelation relation,
                                         RRunningConfig config, UserInfo user, String token) {
        // 新增：分布式锁Key，防止同一机器人在同一直播间并发执行互动
        String lockKey = "robot:interaction:lock:" + account.getUuid() + ":" + room.getUser_id();
        RLock lock = redissonClient.getLock(lockKey);

        boolean isLocked = false;
        try {
            // 尝试获取分布式锁，等待时间1秒，锁有效期3秒
            isLocked = lock.tryLock(1, 3, TimeUnit.SECONDS);
            if (!isLocked) {
                log.debug("获取互动锁失败，跳过本次互动: Robot={}, Room={}", account.getEmail(), room.getUser_id());
                return;
            }

            // 【新增核心逻辑】检查是否被手动控制
            // 如果机器人被运营手动接管，跳过所有自动化互动
            if (manualControlService.isManuallyControlled(account.getUuid())) {
                log.debug("机器人被手动接管，跳过自动化互动: Robot={}, Room={}",
                        account.getEmail(), room.getUser_id());
                return;
            }

            // MonitorService 每5秒调用一次，我们需要降低互动频率

            // 1. 聊天 (Chat) - 降低概率以适应5秒Tick
            if (Integer.valueOf(1).equals(config.getChatSwitch())) {
                executeChat(room, account, relation, config, user, token);
            }

            // 2. 送礼 (Gift) - 极低概率
            if (Integer.valueOf(1).equals(config.getGiftSwitch())) {
                executeGift(room, account, relation, config, user, token);
            }

            // 3. 点赞/关注/分享 (通常只做一次，内部有去重逻辑)
            // 这些动作通常在进入房间初期完成，所以保持较高概率尝试，利用Redis去重防止重复
            if (shouldExecute(config.getLikeProbability(), room, account, config)) {
                executeLike(room, account, relation, config, user, token);
            }
            if (shouldExecute(config.getFollowProbability(), room, account, config)) {
                executeFollow(room, account, relation, config, user, token);
            }
            if (shouldExecute(config.getShareProbability(), room, account, config)) {
                executeShare(room, account, relation, config, user, token);
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("互动执行线程被中断: Account={}", account.getEmail(), e);
        } catch (Exception e) {
            log.error("互动执行异常: Account={}", account.getEmail(), e);
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user,
                    "互动异常", "互动执行异常", e.getMessage(), 2);
        } finally {
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 退出直播间 (Public, 供 MonitorService 调用)
     */
    public boolean quitLiveRoom(LiveRoom room, RRobotAccount account, RConfigRelation relation,
                                RRunningConfig config, UserInfo user, String token) {
        Integer roomId = room.getUser_id();
        Integer userId = Integer.parseInt(user.getUser_id());

        log.debug("机器人请求退出直播间: Account={}, RoomID={}", account.getEmail(), roomId);

        // 1. 调用退出 API
        ApiResponse resp = businessService.quitLiveRoom(roomId, userId, token);

        if (!resp.isSuccess()) {
            log.warn("退出直播间失败: {}, Msg: {}", roomId, resp.getMessage());
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "退出直播间", "退出直播间失败", resp.getMessage(), 2);
        }

        // 无论 API 成功与否，都要清理本地状态，防止死锁在房间内

        // 2. 减少负载计数
        liveRoomStrategyService.decrementRoomLoad(roomId);

        // 3. 清理当前房间缓存
        clearCurrentRoomCache(account.getUuid());

        // 4. 清理房间详情与状态
        clearRoomDetailCache(account.getUuid(), roomId);

        // 5. 清理聊天上下文 (新增：退出直播间时清理聊天频率控制数据)
        clearChatContext(account.getUuid(), roomId);

        // 6. 记录日志与状态更新
        robotRecordService.updateRobotRunningInfoAfterQuit(account, roomId, null);
        if (resp.isSuccess()) {
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "退出直播间", "退出直播间", "OK", 4);
        }

        // --- 新增：结束ASR语音解析 ---
        try {
            boolean asrEnded = asrSpeechManagerService.endAsr(roomId, user, room);
            if (asrEnded) {
                log.debug("ASR语音解析结束成功，直播间ID: {}, 用户ID: {}", roomId, user.getUser_id());
            } else {
                log.warn("ASR语音解析结束失败，直播间ID: {}, 用户ID: {}", roomId, user.getUser_id());
            }
        } catch (Exception e) {
            log.error("结束ASR语音解析异常，直播间ID: {}, 用户ID: {}", roomId, user.getUser_id(), e);
            // ASR解析失败不影响正常退出直播间流程
        }

        return true;
    }

    // =================================================================================================
    //                                      内部核心逻辑实现
    // =================================================================================================

    /**
     * 注册到监控服务，计算停留时间
     */
    private void registerToMonitor(RRobotAccount account, LiveRoom room, RConfigRelation relation,
                                   RRunningConfig config, UserInfo userInfo) {
        int staySeconds = calculateStayDuration(config);
        long planExitTime = System.currentTimeMillis() + (staySeconds * 1000L);
        log.debug("机器人移交监控: Account={}, RoomID={}, 计划停留 {} 秒", account.getEmail(), room.getUser_id(), staySeconds);
        monitorService.register(account, room, relation, config, userInfo, planExitTime);
    }

    //计算停留时间
    private int calculateStayDuration(RRunningConfig config) {
        // 计算随机停留时间 [min, max]
        int min = config.getMinStayDurationInSeconds() != null ? config.getMinStayDurationInSeconds() : 60;
        int max = config.getMaxStayDurationInSeconds() != null ? config.getMaxStayDurationInSeconds() : min;

        // 确保 min <= max
        if (min > max) {
            int temp = min;
            min = max;
            max = temp;
        }

        int staySeconds = min + ThreadLocalRandom.current().nextInt(Math.max(1, max - min + 1));
        return staySeconds;
    }

    /**
     * 确定目标直播间
     */
    private LiveRoom determineTargetRoom(RRobotAccount robotAccount, RRunningConfig runningConfig, List<LiveRoom> allLiveRooms) {
        String robotUuid = robotAccount.getUuid();
        String cachedRoomIdStr = getCurrentRoomIdCache(robotUuid);

        // A. 检查缓存中是否有正在进行的任务
        if (StrUtil.isNotBlank(cachedRoomIdStr)) {
            Optional<LiveRoom> existingRoom = allLiveRooms.stream()
                    .filter(r -> String.valueOf(r.getUser_id()).equals(cachedRoomIdStr))
                    .findFirst();

            if (existingRoom.isPresent()) {
                return existingRoom.get();
            } else {
                // 房间已下播，清理旧状态
                log.debug("目标直播间已下播或不可用: RoomID={}", cachedRoomIdStr);
                handleLiveRoomUnavailable(robotAccount);
            }
        }

        // --- 新增逻辑开始：处理排除直播间 (黑名单) ---
        List<LiveRoom> availableRooms = new ArrayList<>(allLiveRooms); // 创建副本，避免影响原始列表

        List<LiveRoomRelation> excludedRelations = liveRoomRelationService.getListByRelationCode(runningConfig.getUuid(), "runningConfigOut");
        if (CollUtil.isNotEmpty(excludedRelations)) {
            availableRooms = filterExcludedRooms(availableRooms, excludedRelations);
            if (CollUtil.isEmpty(availableRooms)) {
                log.debug("直播间列表经排除策略筛选后为空: Account={}", robotAccount.getEmail());
                return null;
            }
        }
        // --- 新增逻辑结束 ---

        // B. 选择新房间 (使用过滤后的 availableRooms)
        List<LiveRoomRelation> limitedRelations = liveRoomRelationService.getListByRelationCode(runningConfig.getUuid(), "runningConfig");
        List<LiveRoom> candidateRooms;

        if (CollUtil.isNotEmpty(limitedRelations)) {
            // 如果配置了白名单，从 availableRooms 中筛选
            candidateRooms = filterLimitedRooms(availableRooms, limitedRelations);
            if (CollUtil.isEmpty(candidateRooms)) {
                log.debug("指定直播间列表筛选后无可用房间: Account={}", robotAccount.getEmail());
            }
        } else {
            // 如果没配置白名单，基于 availableRooms 进行历史记录去重
            Set<Object> enteredRoomIds = getEnteredHistoryCache(robotUuid);
            candidateRooms = availableRooms.stream()
                    .filter(r -> enteredRoomIds == null || !enteredRoomIds.contains(r.getUser_id()))
                    .collect(Collectors.toList());

            if (CollUtil.isEmpty(candidateRooms)) {
                log.debug("候选直播间列表为空(可能已全部访问过)，尝试从过滤后的列表重选: Account={}", robotAccount.getEmail());
                // 如果历史记录排重后没房了，就从所有可用房间(已排除黑名单)里重选
                candidateRooms = new ArrayList<>(availableRooms);
            }
        }


        // 传入 runningConfig 以支持爬坡策略
        LiveRoom selected = liveRoomStrategyService.selectOptimalRoom(candidateRooms, runningConfig);
        if (selected == null) {
            log.debug("策略服务未能选出最优直播间: Account={}", robotAccount.getEmail());
        }
        return selected;
    }

    private boolean enterLiveRoomApi(LiveRoom room, RRobotAccount robotAccount, RConfigRelation relation,
                                     RRunningConfig config, UserInfo userInfo, String token, String clientId) {
        Integer roomId = room.getUser_id();
        Integer userId = Integer.parseInt(userInfo.getUser_id());

        log.debug("机器人 {} 正式请求进入直播间 {}", robotAccount.getEmail(), roomId);

        // 1. 调用业务接口
        ApiResponse joinResponse = businessService.joinLiveRoom(roomId, userId, clientId, token);
        if (!joinResponse.isSuccess()) {
            log.warn("API拒绝进入直播间: {}, Msg: {}", roomId, joinResponse.getMessage());
            // 记录进入失败日志
            robotRecordService.recordRobotRunRecord(room, robotAccount, relation, config, userInfo,
                    "进入直播间", "进入直播间失败", joinResponse.getMessage(), 2);
            return false;
        }

        // 2. 二次确认状态
        ApiResponse stateResponse = businessService.getLiveRoomCurrentState(roomId, clientId, token);
        if (!stateResponse.isSuccess()) {
            log.warn("获取直播间状态失败: {}, Msg: {}", roomId, stateResponse.getMessage());
            robotRecordService.recordRobotRunRecord(room, robotAccount, relation, config, userInfo,
                    "进入直播间", "获取状态失败", stateResponse.getMessage(), 2);
            return false;
        }

        // 3. 更新状态
        liveRoomStrategyService.incrementRoomLoad(roomId);
        setCurrentRoomCache(robotAccount.getUuid(), roomId);
        addEnteredHistoryCache(robotAccount.getUuid(), roomId);
        addFrequencyTimestamp(robotAccount.getUuid(), roomId);
        initRoomDetailCache(robotAccount.getUuid(), roomId, room);

        // --- 新增：初始化任务开始时间锚点 ---
        // 只有当这是第一个进入该房间的机器人时，setIfAbsent 才会生效
        // 这样可以确保所有后续机器人共享同一个“第0分钟”
        String startKey = REDIS_TASK_START_PREFIX + config.getUuid() + ":" + roomId;
        // 过期时间设置为到当天结束时间，避免无限堆积
        redisTemplate.opsForValue().setIfAbsent(startKey, String.valueOf(System.currentTimeMillis()), getSecondsUntilEndOfDay(), TimeUnit.SECONDS);

        // --- 新增：开启ASR语音解析 ---
        try {
            boolean asrStarted = asrSpeechManagerService.startAsr(roomId, userInfo, room);
            if (asrStarted) {
                log.debug("ASR语音解析开启成功，直播间ID: {}, 用户ID: {}", roomId, userInfo.getUser_id());
            } else {
                log.warn("ASR语音解析开启失败，直播间ID: {}, 用户ID: {}", roomId, userInfo.getUser_id());
            }
        } catch (Exception e) {
            log.error("开启ASR语音解析异常，直播间ID: {}, 用户ID: {}", roomId, userInfo.getUser_id(), e);
            // ASR解析失败不影响正常进入直播间流程
        }

        // 4. 记录日志
        robotRecordService.recordRobotRunRecord(room, robotAccount, relation, config, userInfo,
                "进入直播间", "进入直播间成功", "OK", 4);
        robotRecordService.recordRobotRunningInfo(room, robotAccount, relation, config, userInfo);

        return true;
    }

    /**
     * 根据排除规则过滤直播间
     *
     * @param sourceRooms 原始直播间列表
     * @param exclusions  排除规则关系列表
     * @return 过滤后的直播间列表
     */
    private List<LiveRoom> filterExcludedRooms(List<LiveRoom> sourceRooms, List<LiveRoomRelation> exclusions) {
        if (CollUtil.isEmpty(sourceRooms) || CollUtil.isEmpty(exclusions)) {
            return sourceRooms;
        }

        // 提取黑名单的特征值，放入 Set 提高查找效率
        Set<String> excludedRoomIds = exclusions.stream()
                .map(LiveRoomRelation::getLiveRoomId)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());

        Set<String> excludedAnchorIds = exclusions.stream()
                .map(LiveRoomRelation::getAnchorId) // 对应主播ID/UserNumber
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());

        Set<String> excludedAnchorCodes = exclusions.stream()
                .map(LiveRoomRelation::getAnchorCode)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());

        // 过滤掉匹配任意黑名单规则的房间
        return sourceRooms.stream().filter(room -> {
            boolean isIdExcluded = excludedRoomIds.contains(String.valueOf(room.getUser_id()));
            boolean isAnchorExcluded = excludedAnchorIds.contains(room.getUser_number());
            // 假设 room.getUser_number() 也可能对应 anchorCode，视具体业务字段定义而定，通常是同一字段的校验
            boolean isCodeExcluded = excludedAnchorCodes.contains(room.getUser_number());

            // 只要命中任意一个排除条件，就剔除 (返回 false)
            return !isIdExcluded && !isAnchorExcluded && !isCodeExcluded;
        }).collect(Collectors.toList());
    }

    // --- 互动动作的具体实现 ---

    private void executeLike(LiveRoom room, RRobotAccount account, RConfigRelation relation, RRunningConfig config, UserInfo user, String token) {
        if (hasPerformedActionInSession(account.getUuid(), room.getUser_id(), "hasLiked")) {
            return;
        }

        ApiResponse resp = businessService.giveLike(room.getUser_id(), Integer.parseInt(user.getUser_id()), token);
        if (resp.isSuccess()) {
            markActionPerformedInSession(account.getUuid(), room.getUser_id(), "hasLiked");
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "点赞", "点赞成功", "OK", 4);
        } else {
            log.debug("点赞失败: Account={}, Msg={}", account.getEmail(), resp.getMessage());
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "点赞", "点赞失败", resp.getMessage(), 2);
        }
    }

    private void executeFollow(LiveRoom room, RRobotAccount account, RConfigRelation relation, RRunningConfig config, UserInfo user, String token) {
        if (hasPerformedActionInSession(account.getUuid(), room.getUser_id(), "hasFollowed")) {
            return;
        }

        ApiResponse resp = businessService.followUser(room.getUser_id(), token, 1, 0);
        if (resp.isSuccess()) {
            markActionPerformedInSession(account.getUuid(), room.getUser_id(), "hasFollowed");
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "关注", "关注主播成功", "OK", 4);
        } else {
            log.debug("关注失败: Account={}, Msg={}", account.getEmail(), resp.getMessage());
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "关注", "关注主播失败", resp.getMessage(), 2);
        }
    }

    private void executeShare(LiveRoom room, RRobotAccount account, RConfigRelation relation, RRunningConfig config, UserInfo user, String token) {
        if (hasPerformedActionInSession(account.getUuid(), room.getUser_id(), "hasShared")) {
            return;
        }

        ApiResponse resp = businessService.getRoomOtherConfig(room.getUser_id(), token);
        if (resp.isSuccess()) {
            markActionPerformedInSession(account.getUuid(), room.getUser_id(), "hasShared");
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "分享", "分享直播间成功", "OK", 4);
        } else {
            log.debug("分享失败: Account={}, Msg={}", account.getEmail(), resp.getMessage());
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "分享", "分享直播间失败", resp.getMessage(), 2);
        }
    }

    private void executeChat(LiveRoom room, RRobotAccount account, RConfigRelation relation, RRunningConfig config, UserInfo user, String token) {
        try {
            // 1. 获取可用的聊天配置
            List<ChatConfig> availableConfigs = chatConfigService.getAvailableConfigsByRobotUuid(account.getUuid());
            if (CollUtil.isEmpty(availableConfigs)) {
                log.debug("没有找到可用的聊天配置: Account={}", account.getEmail());
                return;
            }

            // 2. 选择第一个可用的配置（按优先级排序）
            ChatConfig chatConfig = availableConfigs.get(0);

            // 3. 加载或创建聊天上下文（支持聊天频率控制的持久化）
            ChatContext chatContext = loadChatContext(account.getUuid(), room.getUser_id());
            chatContext.setLiveRoomId(String.valueOf(room.getUser_id()));
            chatContext.setLiveRoomName(room.getTitle());
            chatContext.setAnchorId(String.valueOf(room.getUser_id()));
            chatContext.setAnchorName(room.getNick_name());
            chatContext.setOnlineUsers(Optional.ofNullable(room.getNumber_of_people()).orElse(0));
            chatContext.setTriggerType("auto");
            chatContext.setMessage("");
            chatContext.setRobotUuid(account.getUuid());

            //机器人昵称设置，该功能必须要求运营把机器人的nickname进行设置
            chatContext.setRobotName(StrUtil.isNotBlank(account.getNickname()) ? account.getNickname() : account.getEmail());

            // 4. 通过策略工厂获取聊天策略
            ChatStrategy chatStrategy = chatStrategyFactory.getChatStrategy(chatConfig, chatContext);
            if (chatStrategy == null) {
                log.debug("没有找到合适的聊天策略: Account={}, Config={}", account.getEmail(), chatConfig.getUuid());
                return;
            }

            // 5. 检查是否可以聊天
            if (!chatStrategy.canChat(chatConfig, chatContext)) {
                log.debug("聊天策略检查不通过: Account={}, Strategy={}", account.getEmail(), chatStrategy.getStrategyType());
                return;
            }

            // 6. 获取直播间前50条消息作为上下文
            String interactionInfo = buildInteractionInfo(room.getUser_id() ,room,account ,chatContext);
            if (StrUtil.isNotBlank(interactionInfo)) {
                chatContext.setMessage(interactionInfo);
            }else{
                log.debug("没有获取到历史聊天互动信息，不发言直接跳过: Account={}", account.getEmail());
                return;
            }

            // 7. 异步获取聊天内容（使用回调方式）
            log.debug("开始异步获取聊天内容: Account={}, Strategy={}", account.getEmail(), chatStrategy.getStrategyType());
            
            chatStrategy.getChatContentAsync(chatConfig, chatContext, new ChatCallback() {
                @Override
                public void onChatContent(String message) {
                    try {
                        log.debug("收到聊天内容回调: Account={}, Message={}", account.getEmail(), message);
                        
                        if (StrUtil.isBlank(message)) {
                            log.debug("聊天策略返回空消息，跳过发送: Account={}", account.getEmail());
                            return;
                        }

                        // 8. 发送聊天消息
                        ApiResponse resp = businessService.liveRoomChat(room.getUser_id(), token, message);
                        if (resp.isSuccess()) {
                            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "聊天", "弹幕发送成功", message, 4);
                            log.debug("聊天成功: Account={}, Strategy={}, Message={}", account.getEmail(), chatStrategy.getStrategyType(), message);

                            // 9. 记录聊天数据到chat_info表
                            saveChatInfoToDatabase(room, account, chatConfig, message, chatStrategy.getStrategyType());

                            // 10. 更新聊天上下文：记录本次聊天时间和增加聊天次数
                            chatContext.setLastChatTime(System.currentTimeMillis());
                            chatContext.setChatCount(chatContext.getChatCount() + 1);
                            log.debug("更新聊天上下文: Account={}, LastChatTime={}, ChatCount={}",
                                    account.getEmail(), chatContext.getLastChatTime(), chatContext.getChatCount());

                            // 11. 保存更新后的聊天上下文到Redis
                            saveChatContext(account.getUuid(), room.getUser_id(), chatContext);
                        } else {
                            log.debug("聊天失败: Account={}, Msg={}", account.getEmail(), resp.getMessage());
                            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "聊天", "弹幕发送失败", resp.getMessage(), 2);
                        }
                    } catch (Exception e) {
                        log.error("聊天回调处理异常: Account={}", account.getEmail(), e);
                        robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "聊天", "聊天回调处理异常", e.getMessage(), 2);
                    }
                }
            });
            
            log.debug("异步获取聊天内容已启动，等待回调: Account={}", account.getEmail());
            
        } catch (Exception e) {
            log.error("聊天执行异常: Account={}", account.getEmail(), e);
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "聊天", "聊天执行异常", e.getMessage(), 2);
        }
    }
    
    /**
     * 保存聊天信息到数据库
     * 
     * @param room 直播间信息
     * @param account 机器人账号
     * @param chatConfig 聊天配置
     * @param message 聊天内容
     * @param chatType 聊天类型
     */
    private void saveChatInfoToDatabase(LiveRoom room, RRobotAccount account, ChatConfig chatConfig, String message, ChatStrategyType chatType) {
        try {
            // 创建聊天信息实体
            ChatInfo chatInfo = new ChatInfo();
            chatInfo.setUuid(cn.hutool.core.util.IdUtil.fastSimpleUUID());
            chatInfo.setChatContent(message);
            chatInfo.setChatTime(LocalDateTime.now());
            chatInfo.setChatConfigUuid(chatConfig.getUuid());
            
            // 设置聊天类型：1-模板信息，2-ai聊天
            if (ChatStrategyType.TEMPLATE.compareTo(chatType) == 0) {
                chatInfo.setChatType(1);
            } else {
                chatInfo.setChatType(2);
            }
            
            // 设置机器人信息
            chatInfo.setRobotGroupUuid(account.getGroupId());
            chatInfo.setRobotGroupName(account.getGroupName());
            chatInfo.setRobotUuid(account.getUuid());
            chatInfo.setRobotAccount(account.getEmail());
            chatInfo.setRobotNickName(account.getNickname());
            
            // 设置直播间信息
            chatInfo.setAnchorId(String.valueOf(room.getUser_id()));
            chatInfo.setAnchorName(room.getNick_name());
            chatInfo.setRoomId(String.valueOf(room.getUser_id()));
            chatInfo.setRoomName(room.getTitle());
            
            // 保存到数据库
            chatInfoService.save(chatInfo);
            
            log.debug("聊天数据保存成功: Account={}, RoomID={}, Message={}", 
                    account.getEmail(), room.getUser_id(), message);
            
        } catch (Exception e) {
            log.error("保存聊天数据到数据库失败: Account={}, RoomID={}", 
                    account.getEmail(), room.getUser_id(), e);
            // 记录失败日志，但不影响主流程
        }
    }

    /**
     * 构建直播间互动信息
     * 获取当前时间点的前50条数据并组装为直播间互动信息
     * @param roomId 直播间ID
     * @return 组装好的互动信息
     */
    private String buildInteractionInfo(Integer roomId ,LiveRoom room,RRobotAccount account ,ChatContext chatContext) {
        try {
            // 获取当前时间点的前50条消息
            List<RRobotWsMessage> recentMessages = wsMessageStorageService.getRecentMessages(roomId, 50);
            if (CollUtil.isEmpty(recentMessages)) {
                log.debug("直播间 {} 没有找到最近消息", roomId);
                return "";
            }
            
            StringBuilder interactionInfo = new StringBuilder();
            
            // 组装互动信息，格式为：用户(昵称)：发言内容
            for (RRobotWsMessage message : recentMessages) {
                if (StrUtil.isNotBlank(message.getContent())) {
                    String role = message.getSourceRole();
                    String content = message.getContent();
                    String roleName = "用户";

                    // 格式化消息：用户(昵称)：发言内容
                    if ("anchor".equals(role) || room.getUser_id().equals(message.getSourceUserId())) {
                        roleName = "主播";
                    }
                    String userName = message.getAnchorName();
                    //判断该消息是不是该机器人的
                    if(Objects.equals(account.getUserNumber() ,message.getSourceUserId()) || Objects.equals(account.getUserId() ,message.getSourceUserId())){
                        userName = chatContext.getRobotName();
                    }
                    String str = roleName + "(" + userName + ")：" + content;
                    interactionInfo.append(str).append("\\n");
                }
            }
            
            String result = interactionInfo.toString().trim();
            log.debug("构建直播间互动信息完成: RoomId={}, MessageCount={}, Length={}", roomId, recentMessages.size(), result.length());
            
            return result;
            
        } catch (Exception e) {
            log.error("构建直播间互动信息异常: RoomId={}", roomId, e);
            return "";
        }
    }

    /**
     * 执行送礼逻辑
     * 修正了送礼判断逻辑：不使用随机数，而是根据停留时长均分时间段进行判断
     */
    private void executeGift(LiveRoom room, RRobotAccount account, RConfigRelation relation, RRunningConfig config, UserInfo user, String token) {
        // 新增：分布式全局直播间锁，防止多机器人并发送礼导致费用超额
        String lockKey = "robot:gift:global:lock:" + room.getUser_id();
        RLock lock = redissonClient.getLock(lockKey);

        boolean isLocked = false;
        try {
            // 尝试获取分布式锁，等待时间2秒，锁有效期5秒
            isLocked = lock.tryLock(2, 5, TimeUnit.SECONDS);
            if (!isLocked) {
                log.debug("【送礼】获取直播间全局送礼锁失败，跳过本次送礼: Room={}, Robot={}", room.getUser_id(), account.getEmail());
                return;
            }

            if (StrUtil.isBlank(config.getGiftConfigUuid())) {
                log.debug("【送礼】送礼配置UUID为空，跳过送礼: Account={}", account.getEmail());
                return;
            }
            RGiftConfig giftConfig = rGiftConfigService.getOneByUid(config.getGiftConfigUuid());
            RRobotFee robotFee = rRobotFeeService.getOneAvailable(account);

            if (giftConfig == null || robotFee == null) {
                log.info("【送礼】送礼配置或费用记录不存在，跳过送礼: Account={}", account.getEmail());
                return;
            }

            // 新增：严格费用检查 - 确保已使用费用小于总费用
            if (robotFee.getUsedAmount() != null && robotFee.getTotalAmount() != null
                    && robotFee.getUsedAmount() >= robotFee.getTotalAmount()) {
                log.info("【送礼】机器人费用已用完: Account={}, Used={}, Total={}",
                        account.getEmail(), robotFee.getUsedAmount(), robotFee.getTotalAmount());
                return;
            }

            // --- 1. 参数校验 ---
            int maxBatchPerAnchor = giftConfig.getGiftBatchNum() != null ? giftConfig.getGiftBatchNum() : 0;
            if (maxBatchPerAnchor <= 0) {
                log.info("【送礼】送礼配置无效(BatchNum<=0): Account={}", account.getEmail());
                return;
            }
            int maxDailyAnchors = giftConfig.getDailyAnchonum() != null ? giftConfig.getDailyAnchonum() : 1;


            // --- 2. 状态检查 (Redis + DB) ---
            String today = DateUtil.today(); // "yyyy-MM-dd"
            String anchorId = String.valueOf(room.getUser_id());

            // 2.1 检查单主播批次限制 (giftBatchNum)
            Integer currentBatchCount = getGiftCountForAnchorToday(account.getUuid(), anchorId, today);
            if (currentBatchCount >= maxBatchPerAnchor) {
                log.info("【送礼】今日对该主播送礼批次已达上限: Account={}, Anchor={}, Curr={}, Max={}",
                        account.getEmail(), anchorId, currentBatchCount, maxBatchPerAnchor);
                return;
            }

            // 2.2 检查每日最大主播数限制 (dailyAnchonum)
            // 只有当这是一个新的主播时，才需要检查总主播数限制
            boolean isAnchorGiftedBeforeToday = isAnchorGiftedToday(account.getUuid(), anchorId, today);
            if (!isAnchorGiftedBeforeToday) {
                Integer totalGiftedAnchors = getDistinctGiftedAnchorCountToday(account.getUuid(), today);
                if (totalGiftedAnchors >= maxDailyAnchors) {
                    log.info("【送礼】今日送礼主播总数已达上限: Account={}, Curr={}, Max={}", account.getEmail(), totalGiftedAnchors, maxDailyAnchors);
                    return;
                }
            }

            // 获取当前停留时长
            long currentStayDuration = getStayDuration(account.getUuid(), room.getUser_id());
            // 获取计划总停留时长
            Integer staySeconds = giftConfig.getMaxWatchDuration() != null ? giftConfig.getMaxWatchDuration() : giftConfig.getMinWatchDuration();
            if (staySeconds == null || staySeconds <= 0) {
                staySeconds = 100;
            }

            // 计算时间片间隔：总时长 / 总批次
            long interval = staySeconds / giftConfig.getGiftBatchNum();
            if (interval <= 0) {
                interval = 30;
            }

            //不能小于最小时间，不能大于最大时间的二分之一
            if (giftConfig.getMinWatchDuration() != null && giftConfig.getMinWatchDuration() > interval) {
                interval = giftConfig.getMinWatchDuration();
            }
            if (giftConfig.getMaxWatchDuration() != null && giftConfig.getMaxWatchDuration() / 2 < interval) {
                interval = giftConfig.getMaxWatchDuration() / 2;
            }

            // 核心逻辑：当前时长是否已经进入了下一次送礼的时间段
            // 例如：Batches=3, Total=600s, Interval=200s.
            // 第1次(Count=0): 需要 Current > 0 (且最好稍微延迟一点，避免一进房就送，这里依赖心跳间隔)
            // 第2次(Count=1): 需要 Current > 200
            // 第3次(Count=2): 需要 Current > 400
            long requiredDuration = (currentBatchCount + 1) * interval;
            //当前已停留时间的对比值，若该值大于总停留时间，则需要减去.否则会一直送礼。
            if (currentStayDuration < requiredDuration) {
                // 时间未到，不送
                log.info("【送礼】送礼时间未到: Account={}, Current={}, Required={}", account.getEmail(), currentStayDuration, requiredDuration);
                return;
            }
            // log.debug("送礼时间已到: Account={}, Current={}, Required={} ,currentBatchCount={},staySeconds={},interval={}", account.getEmail(), currentStayDuration, requiredDuration ,currentBatchCount ,staySeconds , interval);
            List<Integer> giftedAnchors = getGiftedAnchorsCache(account.getUuid());
            // 3. 检查资金与其他配置条件 (TaskHelper.calculateAvailableAmount 内部再次校验了BatchNum，作为双重保障)
            int onlineUsers = Optional.ofNullable(room.getNumber_of_people()).orElse(0);
            double availableBudget = TaskHelper.calculateAvailableAmount(giftConfig, giftedAnchors, room.getUser_id(), onlineUsers, (int) currentStayDuration);

            if (availableBudget <= 0) {
                log.info("【送礼】可用送礼预算不足或不满足条件: Account={}, Budget={}", account.getEmail(), availableBudget);
                return;
            }

            // 4. 计算实际可用额度 (Fee Limit)
            double hardLimit = (robotFee.getTotalAmount() != null ? robotFee.getTotalAmount() : 0) -
                    (robotFee.getUsedAmount() != null ? robotFee.getUsedAmount() : 0) + (robotFee.getApplyAmount() != null ? robotFee.getApplyAmount() : 0);
            if (hardLimit <= 0) {
                log.info("【送礼】机器人费用余额(hardLimit)不足: Account={}", account.getEmail());
                return;
            }
            //之前申请的费用
            double applyAmount = robotFee.getApplyAmount() != null ? robotFee.getApplyAmount() : 0.0;

            // 5. 筛选礼物
            List<GiftDetail> giftList = giftService.getGiftList();
            if (CollUtil.isEmpty(giftList)) {
                log.info("【送礼】获取礼物列表为空: RoomID={}", room.getUser_id());
                return;
            }

            // 5. 【核心修改】调用决策管理器选择礼物
            // 费用判断、黑白名单过滤、价格筛选都在这里完成
            GiftDetail selectedGift = giftDecisionManager.decideGift(giftList, giftConfig, robotFee, availableBudget);

            if (selectedGift == null) {
                // 决策器返回空，说明没有合适的礼物（买不起、被排除、不在白名单等）
                log.info("【送礼】没有合适的礼物: Account={}", account.getEmail());
                return;
            }

            double giftPrice = selectedGift.getGift_price().doubleValue();

            // 6. 申请余额 (若不足)
            double needApply = 0.0;
            if (applyAmount < giftPrice) {
                needApply = giftPrice - applyAmount;
                ApiResponse applyResp = businessService.applyForBalance(Integer.parseInt(user.getUser_id()), "ROBOT_GIFT_AUTO", needApply, token);
                if (!applyResp.isSuccess()) {
                    log.warn("【送礼】申请费用失败: Account={}, Amount={}, Msg={}", account.getEmail(), needApply, applyResp.getMessage());
                    robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "申请费用", "申请费用失败", applyResp.getMessage(), 2);
                    return;
                }
                robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "申请费用", "申请费用成功", String.valueOf(needApply), 4);
            }

            // 7. 发送礼物
            Integer giftId = selectedGift.getGift_id();
            List<Map<String, Object>> giftIdArr = new ArrayList<>();
            Map<String, Object> giftItem = new HashMap<>();
            giftItem.put("gift_id", giftId);
            giftItem.put("skin_id", 0);
            giftItem.put("num", 1);
            giftIdArr.add(giftItem);

            Map<String, Object> ext = new HashMap<>();
            ext.put("num", 0);
            ext.put("callback_information", "robot_gift");

            List<Map<String, Object>> recipientArr = new ArrayList<>();
            Map<String, Object> recipient = new HashMap<>();
            recipient.put("user_id", room.getUser_id());
            recipientArr.add(recipient);

            ApiResponse<Object> sendResp = giftService.sendGift(room.getUser_id(), giftIdArr, 0, new HashMap<>(), 0, recipientArr, 0, token);

            if (sendResp.isSuccess()) {
                //打印日志，展示账号、直播间、消费费用
                log.info("【送礼】送礼成功: Account={}, Room={}, Gift={}, Amount={}", account.getEmail(), room.getUser_id(), selectedGift.getGift_name(), giftPrice);
                double netApplyChange = needApply - giftPrice;
                log.debug("【送礼】申请费用变化: Account={}, Gift={}, Amount={}, NetChange={}", account.getEmail(), selectedGift.getGift_name(), needApply, netApplyChange);
                rRobotFeeService.updateFeeAfterGift(robotFee.getUuid(), account.getUuid(), netApplyChange, giftPrice);

                // --- 关键：更新 Redis 计数 ---
                incrementGiftCountForAnchorToday(account.getUuid(), anchorId, today);
                markAnchorGiftedToday(account.getUuid(), anchorId, today);

                // 记录送礼行为 (记录一次ID)
                addGiftedAnchorCache(account.getUuid(), room.getUser_id());
                robotRecordService.recordRobotConsumption(room, account, relation, config, user, selectedGift, 1);
                robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "送礼", "送礼成功", selectedGift.getGift_name(), 4);
            } else {
                log.warn("【送礼】送礼API调用失败: Account={}, Msg={}", account.getEmail(), sendResp.getMessage());
                robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "送礼", "送礼失败", sendResp.getMessage(), 2);
                if (needApply > 0) {
                    // 回滚申请的余额
                    rRobotFeeService.updateFeeAfterGift(robotFee.getUuid(), account.getUuid(), needApply, 0.0);
                }
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("【送礼】送礼执行线程被中断: Account={}, Room={}", account.getEmail(), room.getUser_id(), e);
        } catch (Exception e) {
            log.error("【送礼】送礼执行异常: Account={}, Room={}", account.getEmail(), room.getUser_id(), e);
            robotRecordService.recordRobotRunRecord(room, account, relation, config, user, "送礼", "送礼执行异常", e.getMessage(), 2);
        } finally {
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("【送礼】释放直播间全局送礼锁: Room={}, Robot={}", room.getUser_id(), account.getEmail());
            }
        }
    }

    /**
     * 增加今日对某主播送礼计数
     */
    private void incrementGiftCountForAnchorToday(String robotUuid, String anchorId, String dateStr) {
        String key = REDIS_PREFIX_GIFT_COUNT_DAILY + dateStr + ":" + robotUuid + ":" + anchorId;
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
    }

    /**
     * 获取今日已送礼的唯一主播数
     */
    private Integer getDistinctGiftedAnchorCountToday(String robotUuid, String dateStr) {
        String key = REDIS_PREFIX_GIFT_ANCHORS_DAILY + dateStr + ":" + robotUuid;
        // 如果Key存在，直接返回集合大小
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            Long size = redisTemplate.opsForSet().size(key);
            return size != null ? size.intValue() : 0;
        }
        // 缓存不存在，查DB总数。
        // 注意：这里无法简单地将DB的所有anchorId加载到Set中(量可能大)，所以仅返回count用于当前判断。
        // 并在逻辑中依赖 "isAnchorGiftedToday" 的双重检查。
        LocalDateTime todayStart = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
        LocalDateTime todayEnd = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
        return consumptionRecordService.countDistinctAnchors(robotUuid, todayStart, todayEnd);
    }

    /**
     * 获取今日对某主播已送礼次数 (优先Redis, 缓存未命中则查DB)
     */
    private Integer getGiftCountForAnchorToday(String robotUuid, String anchorId, String dateStr) {
        String key = REDIS_PREFIX_GIFT_COUNT_DAILY + dateStr + ":" + robotUuid + ":" + anchorId;
        Object val = redisTemplate.opsForValue().get(key);
        if (val != null) {
            return Integer.parseInt(val.toString());
        }
        // Cache miss: load from DB
        LocalDateTime todayStart = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
        LocalDateTime todayEnd = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
        Integer count = consumptionRecordService.countGiftsToAnchor(robotUuid, anchorId, todayStart, todayEnd);

        redisTemplate.opsForValue().set(key, count, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
        return count;
    }

    /**
     * 检查今日是否已经给该主播送过礼
     */
    private boolean isAnchorGiftedToday(String robotUuid, String anchorId, String dateStr) {
        String key = REDIS_PREFIX_GIFT_ANCHORS_DAILY + dateStr + ":" + robotUuid;
        // 1. 检查缓存
        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, anchorId));
        }
        // 2. 缓存不存在，查DB
        LocalDateTime todayStart = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
        LocalDateTime todayEnd = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
        Integer count = consumptionRecordService.countGiftsToAnchor(robotUuid, anchorId, todayStart, todayEnd);
        return count > 0;
    }

    /**
     * 标记该主播今日已送礼
     */
    private void markAnchorGiftedToday(String robotUuid, String anchorId, String dateStr) {
        String key = REDIS_PREFIX_GIFT_ANCHORS_DAILY + dateStr + ":" + robotUuid;
        redisTemplate.opsForSet().add(key, anchorId);
        redisTemplate.expire(key, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
    }


    // =================================================================================================
    //                                      Redis 与 辅助方法
    // =================================================================================================

    private List<LiveRoom> filterLimitedRooms(List<LiveRoom> allRooms, List<LiveRoomRelation> limits) {
        Set<String> allowedIds = limits.stream().map(LiveRoomRelation::getLiveRoomId).filter(StrUtil::isNotBlank).collect(Collectors.toSet());
        Set<String> allowedAnchors = limits.stream().map(LiveRoomRelation::getAnchorId).filter(StrUtil::isNotBlank).collect(Collectors.toSet());
        Set<String> allowedCodes = limits.stream().map(LiveRoomRelation::getAnchorCode).filter(StrUtil::isNotBlank).collect(Collectors.toSet());

        return allRooms.stream().filter(room ->
                allowedIds.contains(String.valueOf(room.getUser_id())) ||
                        allowedAnchors.contains(room.getUser_number()) ||
                        allowedCodes.contains(room.getUser_number())
        ).collect(Collectors.toList());
    }

    private boolean checkEnterRules(LiveRoom room, RConfigRelation relation, RRunningConfig config, String token) {
        if (config.getStatus() != null && config.getStatus() == 0) {
            log.debug("配置状态未启用(Status=0): ConfigUUID={}", config.getUuid());
            return false;
        }

        int onlineUsers = Optional.ofNullable(room.getNumber_of_people()).orElse(0);
        if (config.getMinOnlineUsers() != null && onlineUsers < config.getMinOnlineUsers()) {
            log.debug("房间人数低于下限: Room={}, Current={}, Min={}", room.getUser_id(), onlineUsers, config.getMinOnlineUsers());
            return false;
        }
        if (config.getMaxOnlineUsers() != null && config.getMaxOnlineUsers() > 0 && onlineUsers > config.getMaxOnlineUsers()) {
            log.debug("房间人数高于上限: Room={}, Current={}, Max={}", room.getUser_id(), onlineUsers, config.getMaxOnlineUsers());
            return false;
        }

        long liveDurationSeconds = 0;
        if (room.getStart_time() != null) {
            liveDurationSeconds = (System.currentTimeMillis() / 1000) - room.getStart_time();
        }
        if (config.getMinLiveDurationInSeconds() != null && liveDurationSeconds < config.getMinLiveDurationInSeconds()) {
            log.debug("开播时长低于下限: Room={}, Current={}, Min={}", room.getUser_id(), liveDurationSeconds, config.getMinLiveDurationInSeconds());
            return false;
        }
        if (config.getMaxLiveDurationInSeconds() != null && config.getMaxLiveDurationInSeconds() > 0 && liveDurationSeconds > config.getMaxLiveDurationInSeconds()) {
            log.debug("开播时长高于上限: Room={}, Current={}, Max={}", room.getUser_id(), liveDurationSeconds, config.getMaxLiveDurationInSeconds());
            return false;
        }

        boolean freqCheck = checkFrequencyLimit(relation.getRobotUuid(), room.getUser_id(), config);
        if (!freqCheck) {
            log.debug("频率限制校验未通过: Account={}, Room={}", relation.getRobotUuid(), room.getUser_id());
        }
        return freqCheck;
    }
    // --- Redis Accessors ---

    private String getCurrentRoomIdCache(String robotUuid) {
        Object val = redisTemplate.opsForValue().get(REDIS_PREFIX_CURRENT_ROOM + robotUuid);
        return val != null ? val.toString() : null;
    }

    private void setCurrentRoomCache(String robotUuid, Integer roomId) {
        redisTemplate.opsForValue().set(REDIS_PREFIX_CURRENT_ROOM + robotUuid, roomId, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
    }

    private void clearCurrentRoomCache(String robotUuid) {
        redisTemplate.delete(REDIS_PREFIX_CURRENT_ROOM + robotUuid);
    }

    private void addEnteredHistoryCache(String robotUuid, Integer roomId) {
        redisTemplate.opsForSet().add(REDIS_PREFIX_ENTERED_ROOMS + robotUuid, roomId);
        redisTemplate.expire(REDIS_PREFIX_ENTERED_ROOMS + robotUuid, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
    }

    private Set<Object> getEnteredHistoryCache(String robotUuid) {
        return redisTemplate.opsForSet().members(REDIS_PREFIX_ENTERED_ROOMS + robotUuid);
    }

    private void addFrequencyTimestamp(String robotUuid, Integer roomId) {
        String key = REDIS_PREFIX_ENTER_HISTORY + robotUuid + ":" + roomId;
        redisTemplate.opsForList().rightPush(key, System.currentTimeMillis());
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    private boolean checkFrequencyLimit(String robotUuid, Integer roomId, RRunningConfig config) {
        if (config.getRepeatEntetimes() == null || config.getRepeatEntetimes() <= 0) {
            return true;
        }
        String key = REDIS_PREFIX_ENTER_HISTORY + robotUuid + ":" + roomId;
        List<Object> history = redisTemplate.opsForList().range(key, 0, -1);
        if (CollUtil.isEmpty(history)) {
            return true;
        }

        long now = System.currentTimeMillis();
        long windowMillis = calculateTimeWindow(config);
        long countInWindow = history.stream().map(obj -> (Long) obj).filter(ts -> now - ts <= windowMillis).count();

        return countInWindow < config.getRepeatEntetimes();
    }

    private long calculateTimeWindow(RRunningConfig config) {
        long interval = config.getRepeatEnteintervalInSeconds() != null ? config.getRepeatEnteintervalInSeconds() : 1;
        return interval * 1000L;
//        int cycleType = config.getRepeatEntecycle() != null ? config.getRepeatEntecycle() : 2;
//        switch (cycleType) {
//            case 1: return interval * 3600 * 1000L;
//            case 2: return interval * 24 * 3600 * 1000L;
//            case 3: return interval * 7 * 24 * 3600 * 1000L;
//            case 4: return interval * 30 * 24 * 3600 * 1000L;
//            default: return 24 * 3600 * 1000L;
//        }
    }

    private void initRoomDetailCache(String robotUuid, Integer roomId, LiveRoom room) {
        String key = REDIS_PREFIX_ROOM_DETAIL + robotUuid + ":" + roomId;
        Map<String, Object> map = new HashMap<>();
        map.put("enterTime", System.currentTimeMillis());
        map.put("anchorId", room.getUser_number());
        map.remove("hasLiked");
        map.remove("hasFollowed");
        map.remove("hasShared");
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
    }

    private void clearRoomDetailCache(String robotUuid, Integer roomId) {
        redisTemplate.delete(REDIS_PREFIX_ROOM_DETAIL + robotUuid + ":" + roomId);
    }

    // --- 聊天上下文存储方法 ---

    /**
     * 加载聊天上下文
     */
    private ChatContext loadChatContext(String robotUuid, Integer roomId) {
        String key = REDIS_PREFIX_CHAT_CONTEXT + robotUuid + ":" + roomId;
        Object val = redisTemplate.opsForValue().get(key);
        if (val != null) {
            try {
                // 从Redis中反序列化ChatContext对象
                String json = val.toString();
                ChatContext context = JSONUtil.toBean(json, ChatContext.class);
                log.debug("加载聊天上下文: Account={}, RoomID={}, LastChatTime={}, ChatCount={}",
                        robotUuid, roomId, context.getLastChatTime(), context.getChatCount());
                return context;
            } catch (Exception e) {
                log.warn("反序列化聊天上下文失败: Account={}, RoomID={}", robotUuid, roomId, e);
            }
        }

        // 如果不存在或反序列化失败，返回新的上下文
        ChatContext context = new ChatContext();
        context.setLastChatTime(0L);
        context.setChatCount(0);
        return context;
    }

    /**
     * 保存聊天上下文
     */
    private void saveChatContext(String robotUuid, Integer roomId, ChatContext context) {
        String key = REDIS_PREFIX_CHAT_CONTEXT + robotUuid + ":" + roomId;
        try {
            String json = JSONUtil.toJsonStr(context);
            redisTemplate.opsForValue().set(key, json, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
            log.debug("保存聊天上下文: Account={}, RoomID={}, LastChatTime={}, ChatCount={}",
                    robotUuid, roomId, context.getLastChatTime(), context.getChatCount());
        } catch (Exception e) {
            log.warn("序列化聊天上下文失败: Account={}, RoomID={}", robotUuid, roomId, e);
        }
    }

    /**
     * 清理聊天上下文
     */
    private void clearChatContext(String robotUuid, Integer roomId) {
        String key = REDIS_PREFIX_CHAT_CONTEXT + robotUuid + ":" + roomId;
        redisTemplate.delete(key);
        log.debug("清理聊天上下文: Account={}, RoomID={}", robotUuid, roomId);
    }

    private long getStayDuration(String robotUuid, Integer roomId) {
        String key = REDIS_PREFIX_ROOM_DETAIL + robotUuid + ":" + roomId;
        Object enterTimeObj = redisTemplate.opsForHash().get(key, "enterTime");
        if (enterTimeObj == null) {
            return 0;
        }
        long enterTime = Long.parseLong(enterTimeObj.toString());
        return (System.currentTimeMillis() - enterTime) / 1000;
    }

    private boolean hasPerformedActionInSession(String robotUuid, Integer roomId, String actionField) {
        String key = REDIS_PREFIX_ROOM_DETAIL + robotUuid + ":" + roomId;
        return redisTemplate.opsForHash().hasKey(key, actionField);
    }

    private void markActionPerformedInSession(String robotUuid, Integer roomId, String actionField) {
        String key = REDIS_PREFIX_ROOM_DETAIL + robotUuid + ":" + roomId;
        redisTemplate.opsForHash().put(key, actionField, 1);
    }

    private void addGiftedAnchorCache(String robotUuid, Integer anchorId) {
        String key = REDIS_PREFIX_GIFTED_ANCHORS + robotUuid;
        List<Integer> list = getGiftedAnchorsCache(robotUuid);
        if (!list.contains(anchorId)) {
            list.add(anchorId);
            redisTemplate.opsForValue().set(key, list, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
        }
    }

    @SuppressWarnings("unchecked")
    private List<Integer> getGiftedAnchorsCache(String robotUuid) {
        String key = REDIS_PREFIX_GIFTED_ANCHORS + robotUuid;
        Object val = redisTemplate.opsForValue().get(key);
        if (val instanceof List) {
            return (List<Integer>) val;
        }
        return new ArrayList<>();
    }

    private boolean shouldExecute(Integer probability, LiveRoom room, RRobotAccount account, RRunningConfig config) {
        // 获取当前停留时长
        long currentStayDuration = getStayDuration(account.getUuid(), room.getUser_id());
        int minStayDuration = config.getMinStayDurationInSeconds() != null ? config.getMinStayDurationInSeconds() : 60;
        int maxStayDuration = config.getMaxStayDurationInSeconds() != null ? config.getMaxStayDurationInSeconds() : 120;

        // 确保minStayDuration <= maxStayDuration，避免bound must be greater than origin异常
        if (minStayDuration > maxStayDuration) {
            int temp = minStayDuration;
            minStayDuration = maxStayDuration;
            maxStayDuration = temp;
        }

        // 如果minStayDuration等于maxStayDuration，直接使用minStayDuration，避免随机数生成异常
        int stayDuration;
        if (minStayDuration == maxStayDuration) {
            stayDuration = minStayDuration;
        } else {
            //获取min和max间的随机值
            stayDuration = RandomUtil.randomInt(minStayDuration, maxStayDuration);
        }

        //没有达到随机停留时间，不做处理
        if (currentStayDuration <= stayDuration) {
            return false;
        }
        return probability != null && probability > 0 && ThreadLocalRandom.current().nextInt(100) < probability;
    }

    /**
     * 清理所有缓存 (Public)
     */
    public void clearAllCacheOnTaskEnd() {
        Set<String> keys = redisTemplate.keys("robot:*");
        if (CollUtil.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
        liveRoomStrategyService.clearAllLoadCounts();
    }

    /**
     * 登出时清理缓存 (Public)
     */
    public void clearRobotCacheOnLogout(RRobotAccount account) {
        handleLiveRoomUnavailable(account);
        redisTemplate.delete(REDIS_PREFIX_ENTERED_ROOMS + account.getUuid());
        redisTemplate.delete(REDIS_PREFIX_GIFTED_ANCHORS + account.getUuid());
        Set<String> keys = redisTemplate.keys(REDIS_PREFIX_ROOM_DETAIL + account.getUuid() + ":*");
        if (CollUtil.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    public void handleLiveRoomUnavailable(RRobotAccount account) {
        String roomIdStr = getCurrentRoomIdCache(account.getUuid());
        if (roomIdStr != null) {
            try {
                liveRoomStrategyService.decrementRoomLoad(Integer.parseInt(roomIdStr));
            } catch (Exception e) {
                log.warn("清理负载失败: {}", e.getMessage());
            }
        }
        clearCurrentRoomCache(account.getUuid());
    }
}