package com.ssyf.task.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ssyf.module.robot.domain.bo.RampUpRule;
import com.ssyf.module.robot.domain.entity.RRunningConfig;
import com.ssyf.module.robot.domain.entity.RRunningConfigGlobal;
import com.ssyf.module.robot.service.RRunningConfigGlobalService;
import com.ssyf.third.bmiss.model.LiveRoom;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 直播间选择策略服务
 * 负责负载均衡：优先选择负载低且未满员的直播间
 * 实现了并发控制和预占机制
 */
@Slf4j
@Service
public class LiveRoomStrategyService {

    @Autowired private RedisTemplate<String, Object> redisTemplate;
    @Autowired private RRunningConfigGlobalService rRunningConfigGlobalService;
    @Autowired private RedissonClient redissonClient;

    private static final String ROOM_ROBOT_COUNT_KEY = "robot:room:concurrency";
    private static final String LOCK_STRATEGY_KEY = "lock:room:strategy:select";
    private static final String LOCK_ROOM_WRITE_PREFIX = "lock:room:write:";
    // 新增：记录任务开始时间的 Redis Key 前缀
    private static final String REDIS_TASK_START_PREFIX = "robot:task:start:";
    // 独立的调度器用于释放预占锁，避免阻塞主线程
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);


    /**
     * 计算直播间的已开播时长（秒）
     */
    private long calculateLiveDuration(LiveRoom room) {
        if (room == null || room.getStart_time() == null) {
            return 0;
        }
        long currentTime = System.currentTimeMillis() / 1000; // 转换为秒
        long startTime = room.getStart_time();
        return Math.max(0, currentTime - startTime);
    }

    /**
     * 检查直播间是否满足开播时长要求
     */
    private boolean meetsLiveDurationRequirement(LiveRoom room, RRunningConfig config) {
        if (config == null) {
            return true;
        }
        
        Integer minLiveDuration = config.getMinLiveDuration();
        Integer maxLiveDuration = config.getMaxLiveDuration();
        
        // 如果最小值和最大值都为0或null，则不限制
        if ((minLiveDuration == null || minLiveDuration == 0) && 
            (maxLiveDuration == null || maxLiveDuration == 0)) {
            return true;
        }
        
        long actualDuration = calculateLiveDuration(room);
        
        // 如果只存在最小值，则必须大于等于最小值
        if ((minLiveDuration != null && minLiveDuration > 0) && 
            (maxLiveDuration == null || maxLiveDuration == 0)) {
            return actualDuration >= config.getMinLiveDurationInSeconds();
        }
        
        // 如果只存在最大值，则必须小于等于最大值
        if ((maxLiveDuration != null && maxLiveDuration > 0) && 
            (minLiveDuration == null || minLiveDuration == 0)) {
            return actualDuration <= config.getMaxLiveDurationInSeconds();
        }
        
        // 如果同时存在最小值和最大值，则取随机值判断
        if (minLiveDuration != null && minLiveDuration > 0 && 
            maxLiveDuration != null && maxLiveDuration > 0) {
            int randomThreshold = ThreadLocalRandom.current().nextInt(config.getMinLiveDurationInSeconds(), config.getMaxLiveDurationInSeconds() + 1);
            return actualDuration >= randomThreshold;
        }
        
        return true;
    }

    /**
     * 修改后的选房方法：增加 config 参数，用于获取爬坡规则和开播时长限制
     */
    public synchronized LiveRoom selectOptimalRoom(List<LiveRoom> candidates, RRunningConfig config) {
        if(CollUtil.isEmpty(candidates)){return  null;}

        RLock lock = redissonClient.getLock(LOCK_STRATEGY_KEY);
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(3, 5, TimeUnit.SECONDS);
            if (!isLocked) {
                log.warn("选房策略锁获取失败，降级为随机选择");
                return randomSelect(candidates);
            }

            // 1. 获取全局配置的单房间最大人数 (硬底线)
            RRunningConfigGlobal globalConfig = rRunningConfigGlobalService.getOneBy();
            int globalMax = Optional.ofNullable(globalConfig)
                    .map(RRunningConfigGlobal::getRoomRobotMax)
                    .filter(max -> max > 0)
                    .orElse(Integer.MAX_VALUE);

            // --- 新增逻辑：判断是否专属配置 ---
            boolean isExclusive = config != null && Integer.valueOf(1).equals(config.getExclusive());
            if (isExclusive) {
                log.debug("当前配置 {} 为专属配置，忽略全局最大人数限制", config.getConfigName());
                // 设为 MAX_VALUE 即相当于忽略该限制
                globalMax = Integer.MAX_VALUE;
            }

            // 2. 批量获取Redis中的当前人数
            List<Object> roomIds = candidates.stream()
                    .map(r -> String.valueOf(r.getUser_id()))
                    .collect(Collectors.toList());
            List<Object> counts = redisTemplate.opsForHash().multiGet(ROOM_ROBOT_COUNT_KEY, roomIds);

            // 3. 构建负载列表 (增加爬坡逻辑和开播时长判断)
            List<RoomLoad> validRooms = new ArrayList<>();

            // 解析爬坡规则 (如果存在)
            List<RampUpRule> rules = null;
            if (config != null && StrUtil.isNotBlank(config.getRampUpRules())) {
                try {
                    rules = JSONUtil.toList(config.getRampUpRules(), RampUpRule.class);
                    // 按分钟数从小到大排序
                    rules.sort(Comparator.comparingInt(RampUpRule::getMinute));
                } catch (Exception e) {
                    log.error("解析流量爬坡规则失败: {}", config.getRampUpRules());
                }
            }

            for (int i = 0; i < candidates.size(); i++) {
                LiveRoom room = candidates.get(i);
                int currentCount = 0;
                if (counts != null && i < counts.size() && counts.get(i) != null) {
                    try {
                        currentCount = Integer.parseInt(counts.get(i).toString());
                    } catch (NumberFormatException ignored) {}
                }

                // --- 新增逻辑：检查开播时长是否符合要求 ---
                if (!meetsLiveDurationRequirement(room, config)) {
                    log.debug("直播间 {} 开播时长不符合要求，跳过选择", room.getUser_id());
                    continue;
                }

                // --- 核心修改：动态上限计算 ---
                int dynamicLimit = globalMax;

                // 如果有爬坡规则，计算当前时间点的限制
                if (CollUtil.isNotEmpty(rules)) {
                    int rampUpLimit = calculateRampUpLimit(room.getUser_id(), config.getUuid(), rules);
                    if(rampUpLimit >0 ) {
                        dynamicLimit = Math.min(globalMax, rampUpLimit);
                    }

                    log.debug("Room {} 动态限制: {} (当前: {})", room.getUser_id(), dynamicLimit, currentCount);
                }

                // 只有未达到动态上限的房间才可选
                if (currentCount < dynamicLimit) {
                    validRooms.add(new RoomLoad(room, currentCount));
                }
            }

            if (validRooms.isEmpty()) {
                log.debug("所有候选直播间均已达到当前阶段人数限制");
                return null;
            }

            // 4. 排序：人数少的优先
            validRooms.sort((o1, o2) -> {
                if(o1.count != o2.count){return Integer.compare(o1.count, o2.count);}
                return ThreadLocalRandom.current().nextBoolean() ? 1 : -1;
            });

            LiveRoom bestRoom = validRooms.get(0).room;
            Integer roomId = bestRoom.getUser_id();

            // 5. 预占机制
            preoccupyRoom(roomId);

            return bestRoom;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } catch (Exception e) {
            log.error("选房策略异常，降级随机", e);
            return randomSelect(candidates);
        } finally {
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 计算当前的爬坡上限
     */
    private int calculateRampUpLimit(Integer roomId, String configUuid, List<RampUpRule> rules) {
        // 获取该配置在该房间的启动时间
        String key = REDIS_TASK_START_PREFIX + configUuid + ":" + roomId;
        Object startTimeObj = redisTemplate.opsForValue().get(key);

        long elapsedMinutes = 0;
        if (startTimeObj != null) {
            long startTime = Long.parseLong(startTimeObj.toString());
            elapsedMinutes = (System.currentTimeMillis() - startTime) / (1000 * 60);
        } else {
            // 如果Key不存在，说明还没人进去过，时间为0
            elapsedMinutes = 0;
        }

        // 找到当前分钟数对应的规则
        // 规则示例: 0->50, 10->150, 20->300
        // 如果 elapsed = 5，应匹配 0->50
        // 如果 elapsed = 15，应匹配 10->150
        // 如果 elapsed = 25，应匹配 20->300
        int limit = 0;
        for (RampUpRule rule : rules) {
            if(rule == null || rule.getMinute() == null || rule.getCount() == null){
                continue;
            }
            if (elapsedMinutes >= rule.getMinute()) {
                limit = rule.getCount();
            } else {
                // 因为已经排序，一旦 elapsedMinutes < rule.getMinute，后面的肯定也不符合
                break;
            }
        }

        return limit;
    }

    /**
     * 选择最优直播间（负载均衡核心）
     * 策略：
     * 1. 过滤已满员房间。
     * 2. 在未满员房间中选当前人数最少的。
     * 3. 选中后立即+1预占，防止瞬间并发涌入同一房间。
     * 注意：此方法不检查开播时长限制，仅用于无配置的场景
     */
    public synchronized LiveRoom selectOptimalRoom(List<LiveRoom> candidates) {
        if(CollUtil.isEmpty(candidates)){return  null;}

        RLock lock = redissonClient.getLock(LOCK_STRATEGY_KEY);
        // 尝试锁住"选房"过程，保证统计数据的快照一致性
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock(3, 5, TimeUnit.SECONDS);
            if (!isLocked) {
                log.warn("选房策略锁获取失败，降级为随机选择");
                return randomSelect(candidates);
            }

            // 1. 获取全局配置的单房间最大人数
            RRunningConfigGlobal globalConfig = rRunningConfigGlobalService.getOneBy();
            int maxRobots = Optional.ofNullable(globalConfig)
                    .map(RRunningConfigGlobal::getRoomRobotMax)
                    .filter(max -> max > 0)
                    .orElse(Integer.MAX_VALUE);

            // 2. 批量获取Redis中的当前人数
            List<Object> roomIds = candidates.stream()
                    .map(r -> String.valueOf(r.getUser_id()))
                    .collect(Collectors.toList());
            List<Object> counts = redisTemplate.opsForHash().multiGet(ROOM_ROBOT_COUNT_KEY, roomIds);

            // 3. 构建负载列表（无开播时长限制检查）
            List<RoomLoad> validRooms = new ArrayList<>();
            for (int i = 0; i < candidates.size(); i++) {
                int count = 0;
                if (counts != null && i < counts.size() && counts.get(i) != null) {
                    try {
                        count = Integer.parseInt(counts.get(i).toString());
                    } catch (NumberFormatException ignored) {}
                }
                if (count < maxRobots) {
                    validRooms.add(new RoomLoad(candidates.get(i), count));
                }
            }

            if (validRooms.isEmpty()) {
                log.debug("所有候选直播间均已满员 (Max: {})", maxRobots);
                return null;
            }

            // 4. 排序：人数少的优先，人数相同随机
            validRooms.sort((o1, o2) -> {
                if(o1.count != o2.count){return  Integer.compare(o1.count, o2.count);}
                return ThreadLocalRandom.current().nextBoolean() ? 1 : -1;
            });

            LiveRoom bestRoom = validRooms.get(0).room;
            Integer roomId = bestRoom.getUser_id();

            // 5. 预占机制：立即+1，随后由业务层正式确认，或者10秒后自动回滚
            preoccupyRoom(roomId);

            return bestRoom;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } catch (Exception e) {
            log.error("选房策略异常，降级随机", e);
            return randomSelect(candidates);
        } finally {
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private LiveRoom randomSelect(List<LiveRoom> list) {
        if(CollUtil.isEmpty(list)){return  null;}
        return list.get(ThreadLocalRandom.current().nextInt(list.size()));
    }

    private void preoccupyRoom(Integer roomId) {
        redisTemplate.opsForHash().increment(ROOM_ROBOT_COUNT_KEY, String.valueOf(roomId), 1);
        log.debug("直播间 {} 预占 +1", roomId);

        // 10秒后自动释放预占（无论成功失败，这里只负责抵消预占的那一个计数）
        // 如果业务层正式进入成功，会再次+1并持久化，这里回滚的是"预占"的临时状态
        // 修正逻辑：预占即视为+1。如果业务层失败，业务层负责调decrement。
        // 原逻辑有问题：10秒后自动-1会导致如果真实进入了，计数反而少了。
        // 新逻辑：预占+1后，通过延时任务检查，如果该机器人在该房间的Session没建立，才-1。
        // 但为了简化和保持原意：这里假设预占是为了防止并发。
        // 优化方案：这里仅仅+1。如果后续EnterLiveRoom失败，ActionService必须调用decrement。
        // 原代码有 schedule rollback，这里保留但需谨慎。
        scheduler.schedule(() -> {
            // 这里仅仅是回滚"预占"带来的+1。
            // 真实逻辑应是：Enter成功不处理，Enter失败手动调用decrement。
            // 但为了防止死锁或崩溃导致未decrement，保留一个超时修正机制比较复杂。
            // 这里遵循原逻辑：预占仅仅是为了让别人觉得人多了，10秒后这个"虚假"的人数消失。
            // 真实的+1由 EnterLiveRoom 里的 incrementRoomLoad 负责? 
            // 不，原代码 selectOptimalRoom 既然加了，EnterLiveRoom 不应该重复加。
            // 经检查 RobotAutomationService 调用链：
            // selectOptimalRoom (+1 temporary) -> schedule(-1)
            // enterLiveRoom -> incrementRoomLoad (persistent +1)
            // 逻辑是成立的：预占是为了在 enter 过程的几秒内，别的线程不会选这个房间。
            decrementRoomLoadInternal(roomId, true);
        }, 10, TimeUnit.SECONDS);
    }

    /**
     * 正式增加负载（进入成功）
     */
    public void incrementRoomLoad(Integer roomId) {
        modifyLoad(roomId, 1);
    }

    /**
     * 减少负载（退出或失败）
     */
    public void decrementRoomLoad(Integer roomId) {
        modifyLoad(roomId, -1);
    }

    private void decrementRoomLoadInternal(Integer roomId, boolean isTemp) {
        modifyLoad(roomId, -1);
    }

    private void modifyLoad(Integer roomId, long delta) {
        if(roomId == null){return;}
        String lockKey = LOCK_ROOM_WRITE_PREFIX + roomId;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(2, 5, TimeUnit.SECONDS)) {
                try {
                    Long val = redisTemplate.opsForHash().increment(ROOM_ROBOT_COUNT_KEY, String.valueOf(roomId), delta);
                    if (val != null && val < 0) {
                        redisTemplate.opsForHash().put(ROOM_ROBOT_COUNT_KEY, String.valueOf(roomId), 0);
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                redisTemplate.opsForHash().increment(ROOM_ROBOT_COUNT_KEY, String.valueOf(roomId), delta);
            }
        } catch (Exception e) {
            log.error("修改直播间负载异常: {}", roomId, e);
        }
    }

    public void clearAllLoadCounts() {
        RLock lock = redissonClient.getLock(LOCK_STRATEGY_KEY);
        try {
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                try {
                    redisTemplate.delete(ROOM_ROBOT_COUNT_KEY);
                    log.debug("全量重置直播间负载计数");
                } finally {
                    lock.unlock();
                }
            }
        } catch (Exception e) {
            log.error("重置负载计数异常", e);
        }
    }

    @PreDestroy
    public void destroy() {
        if (!scheduler.isShutdown()) {
            scheduler.shutdownNow();
        }
    }

    private static class RoomLoad {
        LiveRoom room;
        int count;
        public RoomLoad(LiveRoom room, int count) {
            this.room = room;
            this.count = count;
        }
    }
}