package com.red.circle.other.app.manager;

import com.alibaba.fastjson.JSON;
import com.red.circle.other.app.convertor.RocketConvertor;
import com.red.circle.other.app.dto.clientobject.RocketStatusCO;
import com.red.circle.other.app.dto.cmd.RocketEnergyAddCmd;
import com.red.circle.other.app.util.DistributedLockUtil;
import com.red.circle.other.domain.gateway.RocketConfigGateway;
import com.red.circle.other.domain.gateway.RocketStatusGateway;
import com.red.circle.other.domain.rocket.RocketConfig;
import com.red.circle.other.domain.rocket.RocketStatus;
import com.red.circle.other.domain.rocket.RocketStatusEnum;
import com.red.circle.other.infra.database.cache.key.RocketKeys;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.gateway.RocketStatusGatewayImpl;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 火箭能量聚合器（Redis队列 + 多实例消费版本）
 *
 * @author system
 * @date 2025-01-15
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RocketEnergyAggregator {

    private final RocketStatusGateway rocketStatusGateway;
    private final RocketStatusGatewayImpl rocketStatusGatewayImpl;
    private final DistributedLockUtil distributedLockUtil;
    private final RocketConfigGateway rocketConfigGateway;
    private final RocketImPushManager rocketImPushManager;
    private final RocketLaunchManager rocketLaunchManager;
    private final RoomProfileManagerService roomProfileManagerService;
    private final StringRedisTemplate redisTemplate;
    @Value("${spring.profiles.active}")
    private String activeProfile;

    /**
     * Redis 队列 Key 前缀
     */
    private static final String ENERGY_QUEUE_PREFIX = "rocket:energy:queue:";
    
    /**
     * Redis 房间列表 Key（记录所有有数据的房间）
     */
    private static final String ACTIVE_ROOMS_KEY = "rocket:energy:active:rooms";
    
    /**
     * 推送间隔
     */
    private static final long PUSH_INTERVAL = 3000L;
    
    /**
     * 批量刷新间隔
     */
    private static final long FLUSH_INTERVAL = 5000L;
    
    /**
     * 每次最大处理数量
     */
    private static final int MAX_BATCH_SIZE = 100;
    
    /**
     * 锁超时时间（秒）
     */
    private static final long LOCK_TIMEOUT = 10L;

    /**
     * 上次推送时间（本地缓存）
     */
    private final Map<Long, Long> lastPushTime = new ConcurrentHashMap<>();

    // 根据环境动态生成 Key 前缀
    private String getKey(String roomId) {
        return activeProfile + ":" + RocketEnergyAggregator.ENERGY_QUEUE_PREFIX + roomId;
    }

    private String getActiveRoomsKey() {
        return activeProfile + ":" + RocketEnergyAggregator.ACTIVE_ROOMS_KEY;
    }

    /**
     * 接收能量增加请求（写入Redis队列）
     */
    public void addEnergy(RocketEnergyAddCmd cmd) {
        try {
            Long roomId = cmd.getRoomId();
            String queueKey = getKey(String.valueOf(roomId)) ;
            String value = JSON.toJSONString(cmd);
            
            // 写入队列
            redisTemplate.opsForList().rightPush(queueKey, value);
            
            // 记录活跃房间（使用 Set 去重）
            redisTemplate.opsForSet().add(getActiveRoomsKey(), roomId.toString());
            
            // 设置队列过期时间（24小时）
            redisTemplate.expire(queueKey, 24, TimeUnit.HOURS);
            
            log.debug("能量写入Redis队列: roomId={}, userId={}, energy={}", 
                    roomId, cmd.getUserId(), cmd.getGiftGoldValue());
                    
        } catch (Exception e) {
            log.error("写入能量队列失败: roomId={}", cmd.getRoomId(), e);
            throw e;
        }
    }

    /**
     * 定时批量刷新
     */
    @Scheduled(fixedDelay = FLUSH_INTERVAL)
    public void flushToDatabase() {
        try {
            // 获取所有活跃房间
            Set<String> activeRooms = redisTemplate.opsForSet().members(getActiveRoomsKey());
            
            if (activeRooms == null || activeRooms.isEmpty()) {
                log.debug("没有待处理的房间");
                return;
            }

            log.info("开始批量刷新火箭能量, 待处理房间数={}", activeRooms.size());
            
            int successCount = 0;
            int skipCount = 0;
            int errorCount = 0;

            // 遍历所有活跃房间，尝试获取锁并处理
            for (String roomIdStr : activeRooms) {
                Long roomId = null;
                try {
                    roomId = Long.parseLong(roomIdStr);
                    
                    // 尝试获取房间锁（非阻塞）
                    String lockKey = RocketKeys.LOCK.getKey(roomId);
                    Boolean locked = distributedLockUtil.tryLock(lockKey, LOCK_TIMEOUT);
                    
                    if (!locked) {
                        // 其他实例正在处理这个房间，跳过
                        skipCount++;
                        log.debug("房间{}被其他实例占用，跳过", roomId);
                        continue;
                    }

                    try {
                        // 获取锁成功，处理这个房间
                        boolean processed = processRoomEnergyFromRedis(roomId);
                        if (processed) {
                            successCount++;
                        }
                    } finally {
                        // 释放锁
                        distributedLockUtil.unlock(lockKey);
                    }
                    
                } catch (Exception e) {
                    errorCount++;
                    log.error("处理房间{}能量失败", roomId, e);
                }
            }

            log.info("批量刷新完成: 成功={}, 跳过={}, 失败={}", successCount, skipCount, errorCount);
            
        } catch (Exception e) {
            log.error("批量刷新火箭能量失败", e);
        }
    }

    /**
     * 从Redis队列处理房间能量
     * @return 是否处理了数据
     */
    private boolean processRoomEnergyFromRedis(Long roomId) {
        String queueKey = getKey(String.valueOf(roomId)) ;
        
        try {
            // 检查队列长度
            Long queueSize = redisTemplate.opsForList().size(queueKey);
            if (queueSize == null || queueSize == 0) {
                // 队列为空，从活跃房间列表中移除
                redisTemplate.opsForSet().remove(getActiveRoomsKey(), roomId.toString());
                log.debug("房间{}队列为空，从活跃列表移除", roomId);
                return false;
            }

            // 批量取出数据（使用 LRANGE 查看，不删除）
            List<String> values = redisTemplate.opsForList().range(queueKey, 0, MAX_BATCH_SIZE - 1);
            
            if (values == null || values.isEmpty()) {
                return false;
            }

            // 解析数据
            List<RocketEnergyAddCmd> batch = new ArrayList<>();
            for (String value : values) {
                try {
                    RocketEnergyAddCmd cmd = JSON.parseObject(value, RocketEnergyAddCmd.class);
                    batch.add(cmd);
                } catch (Exception e) {
                    log.error("解析能量数据失败，写入死信队列: {}", value, e);
                    redisTemplate.opsForList().rightPush("rocket:energy:dead:letter", value);
                }
            }

            if (batch.isEmpty()) {
                // 数据解析失败，删除这些无效数据
                redisTemplate.opsForList().trim(queueKey, values.size(), -1);
                return false;
            }

            // 处理能量增加
            processRoomEnergyBatch(roomId, batch);
            
            // 处理成功后才删除Redis中的数据（使用 LTRIM）
            redisTemplate.opsForList().trim(queueKey, batch.size(), -1);
            
            log.info("成功处理房间{}能量, 处理数量={}, 剩余={}", 
                    roomId, batch.size(), queueSize - batch.size());
            
            return true;
            
        } catch (Exception e) {
            log.error("处理房间{}能量失败，数据保留在Redis中等待重试", roomId, e);
            throw e;
        }
    }

    /**
     * 处理单个房间的能量批次
     */
    private void processRoomEnergyBatch(Long roomId, List<RocketEnergyAddCmd> batch) {
        String riyadhToday = ZonedDateTimeAsiaRiyadhUtils.nowFormat("yyyy-MM-dd");

        // 查询当前火箭状态
        RocketStatus rocketStatus = rocketStatusGatewayImpl
                .findByRoomIdAndDateWithoutContributors(roomId, riyadhToday);

        if (rocketStatus == null) {
            // 初始化火箭
            RocketConfig level1Config = rocketConfigGateway.findByLevel(1);
            if (level1Config == null || level1Config.getStatus() != 1) {
                log.error("一级火箭配置不存在或未启用");
                throw new RuntimeException("一级火箭配置不存在");
            }
            rocketStatus = RocketStatus.init(roomId, level1Config);
            rocketStatus.setDate(riyadhToday);
            log.info("初始化房间火箭, roomId={}, date={}, maxEnergy={}", roomId, riyadhToday, level1Config.getMaxEnergy());
        }

        // 校验状态
        if (!rocketStatus.canAddEnergy()) {
            log.warn("火箭不可充能, roomId={}, status={}", roomId, rocketStatus.getStatus());
            return;
        }

        // 记录增加前的状态
        long energyBeforeAdd = rocketStatus.getCurrentEnergy();
        long maxEnergy = rocketStatus.getMaxEnergy();
        
        // 批量增加能量
        long totalEnergy = 0L;
        Long triggerUserId = null;
        
        for (RocketEnergyAddCmd cmd : batch) {
            long beforeEnergy = rocketStatus.getCurrentEnergy();
            
            // 增加能量
            rocketStatus.addEnergy(cmd.getUserId(), cmd.getGiftGoldValue());
            totalEnergy += cmd.getGiftGoldValue();
            
            long afterEnergy = rocketStatus.getCurrentEnergy();
            
            // 记录触发满能量的用户
            if (beforeEnergy < maxEnergy && afterEnergy >= maxEnergy && triggerUserId == null) {
                triggerUserId = cmd.getUserId();
                log.info("找到触发升级的用户: userId={}, beforeEnergy={}, afterEnergy={}, maxEnergy={}",
                        triggerUserId, beforeEnergy, afterEnergy, maxEnergy);
            }
        }

        // 保存状态
        try {
            rocketStatusGateway.save(rocketStatus);
        } catch (Exception e) {
            log.error("保存火箭状态失败: roomId={}", roomId, e);
            throw e;
        }

        log.info("批量增加火箭能量: roomId={}, 批次数量={}, 总能量={}, 更新前={}, 更新后={}/{}", 
                roomId, batch.size(), totalEnergy, energyBeforeAdd,
                rocketStatus.getCurrentEnergy(), rocketStatus.getMaxEnergy());

        // 推送更新消息（节流）
        pushEnergyUpdate(roomId, rocketStatus);

        // 检查是否需要发射（添加状态检查）
        if (rocketStatus.isFullEnergy() && rocketStatus.getStatus() == RocketStatusEnum.CHARGING) {
            log.info("火箭能量已满, 准备发射, roomId={}, level={}, 触发用户={}", 
                    roomId, rocketStatus.getLevel().getLevel(), triggerUserId);
            
            // 异步触发发射
            rocketLaunchManager.launchAsync(roomId, triggerUserId);
        }
    }

    /**
     * 推送能量更新（节流：3秒推送一次）
     */
    private void pushEnergyUpdate(Long roomId, RocketStatus rocketStatus) {
        long now = System.currentTimeMillis();
        Long lastPush = lastPushTime.get(roomId);
        
        // 节流：距离上次推送不足3秒，跳过
        if (lastPush != null && (now - lastPush) < PUSH_INTERVAL) {
            log.debug("推送节流：距离上次推送{}ms，跳过本次推送", now - lastPush);
            return;
        }

        try {
            String roomAccount = roomProfileManagerService.getRoomAccount(roomId);
            RocketStatusCO statusCO = RocketConvertor.toStatusCO(rocketStatus);
            rocketImPushManager.pushEnergyUpdate(roomAccount, statusCO);
            
            // 更新推送时间
            lastPushTime.put(roomId, now);
            
            log.info("推送火箭能量更新: roomId={}, energy={}/{}", 
                    roomId, rocketStatus.getCurrentEnergy(), rocketStatus.getMaxEnergy());
        } catch (Exception e) {
            log.error("推送火箭能量更新失败: roomId={}", roomId, e);
        }
    }

    /**
     * 定时清理过期数据（每小时）
     */
    @Scheduled(fixedRate = 3600000)
    public void cleanupExpiredData() {
        try {
            // 清理本地推送时间缓存
            Set<String> activeRooms = redisTemplate.opsForSet().members(getActiveRoomsKey());
            if (activeRooms != null) {
                Set<Long> activeRoomIds = new HashSet<>();
                for (String roomIdStr : activeRooms) {
                    try {
                        activeRoomIds.add(Long.parseLong(roomIdStr));
                    } catch (Exception ignored) {
                    }
                }

                // 移除不活跃房间的推送时间
                lastPushTime.keySet().removeIf(roomId -> !activeRoomIds.contains(roomId));
            }

            log.info("清理过期数据完成, 当前活跃房间数={}", activeRooms != null ? activeRooms.size() : 0);
        } catch (Exception e) {
            log.error("清理过期数据失败", e);
        }
    }

    /**
     * 应用关闭时强制刷新
     */
    @javax.annotation.PreDestroy
    public void shutdown() {
        log.info("应用关闭，强制刷新所有待处理能量");
        try {
            flushToDatabase();
        } catch (Exception e) {
            log.error("关闭时刷新失败", e);
        }
    }

    /**
     * 手动触发刷新（供运维使用）
     */
    public void manualFlush() {
        log.info("手动触发刷新");
        flushToDatabase();
    }

    /**
     * 获取队列状态（监控使用）
     */
    public Map<String, Object> getQueueStatus() {
        Map<String, Object> status = new HashMap<>();
        
        Set<String> activeRooms = redisTemplate.opsForSet().members(getActiveRoomsKey());
        status.put("activeRoomCount", activeRooms != null ? activeRooms.size() : 0);
        status.put("localPushCacheSize", lastPushTime.size());
        
        if (activeRooms != null && !activeRooms.isEmpty()) {
            Map<Long, Long> queueSizes = new HashMap<>();
            for (String roomIdStr : activeRooms) {
                try {
                    Long roomId = Long.parseLong(roomIdStr);
                    String queueKey = ENERGY_QUEUE_PREFIX + roomId;
                    Long size = redisTemplate.opsForList().size(queueKey);
                    queueSizes.put(roomId, size != null ? size : 0L);
                } catch (Exception ignored) {
                }
            }
            status.put("queueSizes", queueSizes);
        }
        
        return status;
    }
}