package com.ssyf.task.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * WS 消息存储服务 (Write-Behind Pattern)
 * 解析 -> 缓冲(Redis) -> 持久化(DB)
 */
@Slf4j
@Service
public class WsMessageStorageService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // Redis List 缓冲 Key
    private static final String KEY_MSG_BUFFER = "ws:msg:buffer:queue";
    // Redis ZSet 最近消息缓存 Key 前缀 (ws:msg:recent:{roomId})
    private static final String KEY_MSG_RECENT_PREFIX = "ws:msg:recent:";
    // Redis Set 消息去重 Key 前缀 (ws:msg:dedup:{roomId})
    private static final String KEY_MSG_DEDUP_PREFIX = "ws:msg:dedup:";
    // Redis List 最近消息内容 Key 前缀 (ws:msg:content:{roomId})
    private static final String KEY_MSG_CONTENT_PREFIX = "ws:msg:content:";
    // Redis Hash 用户最近消息 Key 前缀 (ws:msg:user:{roomId}:{userId})
    private static final String KEY_MSG_USER_PREFIX = "ws:msg:user:";

    // 批量入库阈值
    private static final int BATCH_SIZE = 500;
    // 缓存保留最近消息数量
    private static final int RECENT_MSG_LIMIT = 50;
    // 去重缓存过期时间（秒）
    private static final int DEDUP_EXPIRE_SECONDS = 3600;
    // 内容相似度阈值（0-1之间，值越大要求越相似）
    private static final double CONTENT_SIMILARITY_THRESHOLD = 0.8;
    // 时间窗口大小（秒）- 前后5秒
    private static final int TIME_WINDOW_SECONDS = 5;

    /**
     * 处理收到的原始 WS 消息
     * 由 WsManager 回调
     */
    public void bufferMessage(Integer roomId, String anchorName, String msgType, String content, String rawData) {
        bufferMessage(roomId, anchorName, msgType, content, rawData, null, null);
    }

    /**
     * 处理收到的原始 WS 消息（带角色标识）
     * 由 WsManager 回调
     */
    public void bufferMessage(Integer roomId, String anchorName, String msgType, String content, String rawData, 
                             String sourceRole, String sourceUserId) {
        RRobotWsMessage entity = new RRobotWsMessage()
                .setRoomId(String.valueOf(roomId))
                .setAnchorName(anchorName)
                .setMsgType(msgType)
                .setContent(content)
                .setRawData(rawData)
                .setSourceRole(sourceRole)
                .setSourceUserId(sourceUserId)
                .setReceiveTime(LocalDateTime.now())
                .setTimestamp(System.currentTimeMillis());

        // 1. 推入 Redis 缓冲队列 (用于异步入库)
        // 建议存储 JSON 字符串或序列化对象
        redisTemplate.opsForList().rightPush(KEY_MSG_BUFFER, entity);
        // 2. 推入 Redis 最近消息缓存 (用于前端展示)
        updateRecentCache(roomId, entity);

    }

    /**
     * 更新最近消息缓存 (ZSet)
     * Score = Timestamp
     */
    private void updateRecentCache(Integer roomId, RRobotWsMessage msg) {
        String key = KEY_MSG_RECENT_PREFIX + roomId;
        
        // 1. 基础去重检查：通过时间、直播间id、来源角色、来源用户进行去重
        String dedupKey = KEY_MSG_DEDUP_PREFIX + roomId;
        String dedupValue = generateDedupValue(msg);
        
        // 使用Redis Set进行去重，避免重复消息
        Boolean isDuplicate = redisTemplate.opsForSet().isMember(dedupKey, dedupValue);
        if (Boolean.TRUE.equals(isDuplicate)) {
            log.debug("检测到重复消息，跳过缓存: roomId={}, dedupValue={}", roomId, dedupValue);
            return;
        }
        
        // 2. 增强去重检查：基于内容相似度和时间窗口
        if (isDuplicateByContentAndTimeWindow(roomId, msg)) {
            log.debug("检测到内容相似消息，跳过缓存: roomId={}, content={}", roomId, msg.getContent());
            return;
        }
        
        // 3. 添加去重标识
        redisTemplate.opsForSet().add(dedupKey, dedupValue);
        redisTemplate.expire(dedupKey, DEDUP_EXPIRE_SECONDS, TimeUnit.SECONDS);
        
        // 4. 记录消息内容用于后续去重检查
        recordMessageContent(roomId, msg);
        
        // 5. 添加消息到ZSet（使用Pipeline提高性能）
        redisTemplate.opsForZSet().add(key, msg, msg.getTimestamp());

        // 6. 维持固定长度，移除旧消息（使用ZREMRANGEBYRANK进行批量删除）
        Long size = redisTemplate.opsForZSet().size(key);
        if (size != null && size > RECENT_MSG_LIMIT + 10) { // 加点缓冲，别每次都删
            redisTemplate.opsForZSet().removeRange(key, 0, size - RECENT_MSG_LIMIT - 1);
        }
        
        // 7. 设置过期时间，房间没人气后自动清理
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
    }

    /**
     * 生成去重标识值
     * 格式：timestamp|roomId|sourceRole|sourceUserId
     */
    private String generateDedupValue(RRobotWsMessage msg) {
        return String.format("%d|%s|%s|%s", 
            msg.getTimestamp(), 
            msg.getRoomId(), 
            msg.getSourceRole() != null ? msg.getSourceRole() : "unknown",
            msg.getSourceUserId() != null ? msg.getSourceUserId() : "unknown");
    }

    /**
     * 获取指定直播间的最近消息（已去重和排序）
     * 按时间顺序从小到大排序
     */
    public List<RRobotWsMessage> getRecentMessages(Integer roomId, int limit) {
        String key = KEY_MSG_RECENT_PREFIX + roomId;
        
        // 使用ZRANGE获取消息（按时间戳升序）
        return redisTemplate.opsForZSet().range(key, 0, limit - 1)
                .stream()
                .map(obj -> (RRobotWsMessage) obj)
                .collect(java.util.stream.Collectors.toList());
    }

    /**
     * 获取指定直播间的消息数量
     */
    public Long getMessageCount(Integer roomId) {
        String key = KEY_MSG_RECENT_PREFIX + roomId;
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 基于内容相似度和时间窗口的去重检查
     * 检查前后5秒范围内相同发送人的相似消息内容
     */
    private boolean isDuplicateByContentAndTimeWindow(Integer roomId, RRobotWsMessage msg) {
        if (StrUtil.isBlank(msg.getContent()) || StrUtil.isBlank(msg.getSourceUserId())) {
            return false;
        }

        try {
            // 1. 获取用户最近消息
            String userKey = KEY_MSG_USER_PREFIX + roomId + ":" + msg.getSourceUserId();
            Object recentMsgObj = redisTemplate.opsForValue().get(userKey);
            
            if (recentMsgObj == null) {
                return false;
            }

            // 2. 解析最近消息
            RRobotWsMessage recentMsg = JSONUtil.toBean(recentMsgObj.toString(), RRobotWsMessage.class);
            
            // 3. 检查时间窗口（前后5秒）
            long timeDiff = Math.abs(msg.getTimestamp() - recentMsg.getTimestamp());
            if (timeDiff > TIME_WINDOW_SECONDS * 1000L) {
                return false;
            }

            // 4. 检查内容相似度
            double similarity = calculateContentSimilarity(msg.getContent(), recentMsg.getContent());
            
            if (similarity >= CONTENT_SIMILARITY_THRESHOLD) {
                log.debug("检测到相似消息，相似度: {}, 当前内容: {}, 最近内容: {}", 
                        similarity, msg.getContent(), recentMsg.getContent());
                return true;
            }

        } catch (Exception e) {
            log.error("内容相似度检查异常，roomId: {}, userId: {}", roomId, msg.getSourceUserId(), e);
        }

        return false;
    }

    /**
     * 计算两个消息内容的相似度
     * 使用编辑距离算法计算相似度（0-1之间）
     */
    private double calculateContentSimilarity(String content1, String content2) {
        if (StrUtil.isBlank(content1) || StrUtil.isBlank(content2)) {
            return 0.0;
        }

        // 使用Hutool的字符串相似度算法
        return Objects.equals(content1, content2) ? 1 : 0;
    }

    /**
     * 记录消息内容用于后续去重检查
     */
    private void recordMessageContent(Integer roomId, RRobotWsMessage msg) {
        if (StrUtil.isBlank(msg.getSourceUserId())) {
            return;
        }

        try {
            // 1. 记录用户最近消息
            String userKey = KEY_MSG_USER_PREFIX + roomId + ":" + msg.getSourceUserId();
            redisTemplate.opsForValue().set(userKey, JSONUtil.toJsonStr(msg), TIME_WINDOW_SECONDS * 2, TimeUnit.SECONDS);

            // 2. 记录消息内容到最近内容列表（用于全局相似度检查）
            String contentKey = KEY_MSG_CONTENT_PREFIX + roomId;
            
            // 使用List存储最近的消息内容，保留最近100条
            redisTemplate.opsForList().rightPush(contentKey, msg.getContent());
            
            // 维持列表长度，避免无限增长
            Long listSize = redisTemplate.opsForList().size(contentKey);
            if (listSize != null && listSize > 100) {
                redisTemplate.opsForList().trim(contentKey, -100, -1);
            }
            
            redisTemplate.expire(contentKey, TIME_WINDOW_SECONDS * 2, TimeUnit.SECONDS);

        } catch (Exception e) {
            log.error("记录消息内容异常，roomId: {}", roomId, e);
        }
    }

    /**
     * 检查全局内容相似度（跨用户）
     * 检查当前消息是否与最近的其他用户消息相似
     */
    private boolean checkGlobalContentSimilarity(Integer roomId, String content) {
        if (StrUtil.isBlank(content)) {
            return false;
        }

        try {
            String contentKey = KEY_MSG_CONTENT_PREFIX + roomId;
            List<Object> recentContents = redisTemplate.opsForList().range(contentKey, 0, -1);
            
            if (CollUtil.isEmpty(recentContents)) {
                return false;
            }

            // 检查与最近10条消息的相似度
            int checkCount = Math.min(recentContents.size(), 10);
            for (int i = 0; i < checkCount; i++) {
                Object recentContentObj = recentContents.get(i);
                if (recentContentObj instanceof String) {
                    String recentContent = (String) recentContentObj;
                    double similarity = calculateContentSimilarity(content, recentContent);
                    
                    if (similarity >= CONTENT_SIMILARITY_THRESHOLD) {
                        log.debug("检测到全局相似消息，相似度: {}, 当前内容: {}, 最近内容: {}", 
                                similarity, content, recentContent);
                        return true;
                    }
                }
            }

        } catch (Exception e) {
            log.error("全局内容相似度检查异常，roomId: {}", roomId, e);
        }

        return false;
    }

    /**
     * 增强的去重检查方法（包含全局相似度检查）
     */
    public boolean isDuplicateMessage(Integer roomId, RRobotWsMessage msg) {
        // 1. 基础去重检查
        String dedupKey = KEY_MSG_DEDUP_PREFIX + roomId;
        String dedupValue = generateDedupValue(msg);
        
        Boolean isDuplicate = redisTemplate.opsForSet().isMember(dedupKey, dedupValue);
        if (Boolean.TRUE.equals(isDuplicate)) {
            return true;
        }

        // 2. 基于内容相似度和时间窗口的去重
        if (isDuplicateByContentAndTimeWindow(roomId, msg)) {
            return true;
        }

        // 3. 全局内容相似度检查
        if (checkGlobalContentSimilarity(roomId, msg.getContent())) {
            return true;
        }

        return false;
    }

    /**
     * 获取指定时间范围内的消息
     * 
     * @param roomId 直播间ID
     * @param endTime 截止时间（毫秒时间戳），如果为null则使用当前时间
     * @param timeRange 时间范围（秒），从截止时间往前的时间范围
     * @return 时间范围内的消息列表，按时间顺序从小到大排序
     */
    public List<RRobotWsMessage> getMessagesByTimeRange(Integer roomId, Long endTime, int timeRange) {
        if (roomId == null || timeRange <= 0) {
            return new ArrayList<>();
        }

        try {
            String key = KEY_MSG_RECENT_PREFIX + roomId;
            
            // 计算时间范围
            long currentEndTime = endTime != null ? endTime : System.currentTimeMillis();
            long startTime = currentEndTime - (timeRange * 1000L);
            
            // 使用ZRANGEBYSCORE获取指定时间范围内的消息（按时间升序排列）
            // 参数：min=startTime, max=endTime, offset=0, count=-1（获取全部）
            Set<Object> messages = redisTemplate.opsForZSet().rangeByScore(
                key, startTime, currentEndTime, 0, -1
            );
            
            if (messages == null) {
                return new ArrayList<>();
            }
            
            return messages.stream()
                    .map(obj -> (RRobotWsMessage) obj)
                    .collect(java.util.stream.Collectors.toList());
                    
        } catch (Exception e) {
            log.error("获取时间范围消息异常，roomId: {}, endTime: {}, timeRange: {}", 
                    roomId, endTime, timeRange, e);
            return new ArrayList<>();
        }
    }

    /**
     * 分页获取消息
     * 
     * @param roomId 直播间ID
     * @param skip 跳过的消息数量
     * @param limit 获取的消息数量，如果为null则默认100条
     * @return 分页消息列表，按时间顺序从小到大排序
     */
    public List<RRobotWsMessage> getMessagesByPage(Integer roomId, int skip, Integer limit) {
        if (roomId == null || skip < 0) {
            return new ArrayList<>();
        }

        try {
            String key = KEY_MSG_RECENT_PREFIX + roomId;
            
            // 设置默认限制
            int actualLimit = limit != null ? limit : 100;
            
            // 计算起始位置
            long startIndex = skip;
            long endIndex = skip + actualLimit - 1;
            
            // 使用ZRANGE获取分页消息（按时间戳升序）
            Set<Object> messages = redisTemplate.opsForZSet().range(key, startIndex, endIndex);
            
            if (messages == null) {
                return new ArrayList<>();
            }
            
            return messages.stream()
                    .map(obj -> (RRobotWsMessage) obj)
                    .collect(java.util.stream.Collectors.toList());
                    
        } catch (Exception e) {
            log.error("分页获取消息异常，roomId: {}, skip: {}, limit: {}", roomId, skip, limit, e);
            return new ArrayList<>();
        }
    }

    /**
     * 获取消息总数（用于分页计算）
     */
    public long getTotalMessageCount(Integer roomId) {
        if (roomId == null) {
            return 0;
        }

        try {
            String key = KEY_MSG_RECENT_PREFIX + roomId;
            Long count = redisTemplate.opsForZSet().size(key);
            return count != null ? count : 0;
        } catch (Exception e) {
            log.error("获取消息总数异常，roomId: {}", roomId, e);
            return 0;
        }
    }

    /**
     * 获取指定时间戳之后的消息（用于增量同步）
     * 
     * @param roomId 直播间ID
     * @param sinceTimestamp 起始时间戳（毫秒）
     * @param limit 最大消息数量
     * @return 指定时间戳之后的消息列表
     */
    public List<RRobotWsMessage> getMessagesSince(Integer roomId, long sinceTimestamp, int limit) {
        if (roomId == null || sinceTimestamp <= 0) {
            return new ArrayList<>();
        }

        try {
            String key = KEY_MSG_RECENT_PREFIX + roomId;
            
            // 获取sinceTimestamp之后的消息，按时间升序排列
            Set<Object> messages = redisTemplate.opsForZSet().rangeByScore(
                key, sinceTimestamp + 1, Double.MAX_VALUE, 0, limit
            );
            
            if (messages == null) {
                return new ArrayList<>();
            }
            
            return messages.stream()
                    .map(obj -> (RRobotWsMessage) obj)
                    .collect(java.util.stream.Collectors.toList());
                    
        } catch (Exception e) {
            log.error("获取增量消息异常，roomId: {}, sinceTimestamp: {}", roomId, sinceTimestamp, e);
            return new ArrayList<>();
        }
    }

    /**
     * 定时批量落库任务
     * 每 5 秒 或 队列堆积时执行
     */
    @Scheduled(fixedDelay = 35000)
    public void flushBufferToDb() {
        // 1. 检查队列长度
        Long size = redisTemplate.opsForList().size(KEY_MSG_BUFFER);
        if (size == null || size == 0) {
            return;
        }

        log.debug("Flushing WS message buffer. Size: {}", size);

        //删除队列
        redisTemplate.opsForList().trim(KEY_MSG_BUFFER, size - BATCH_SIZE, size - 1);

        // 2. 分批处理 (防止一次 pop 太多内存溢出)
        // 注意：Redis List 的 pop 是原子的，但在多实例部署下，建议使用 distinct job lock 
        // 或者直接多实例并行消费（MyBatis Batch Insert 只要 DB 能扛住就行）
        // 这里为了简单，采用一次循环取出 BATCH_SIZE

//        List<RRobotWsMessage> batchList = new ArrayList<>();
//        long fetchStart = System.currentTimeMillis();
//
//        // 使用 Pipeline 或者简单的循环 Pop。LPOP count 是 Redis 6.2+ 特性，如果版本低只能循环。
//        // 为了兼容性，这里用循环，实际生产建议用 Lua 脚本或 pipeline 批量 pop
//        for (int i = 0; i < BATCH_SIZE; i++) {
//            Object obj = redisTemplate.opsForList().leftPop(KEY_MSG_BUFFER);
//            if (obj == null) {
//                break;
//            }
//
//            if (obj instanceof RRobotWsMessage) {
//                batchList.add((RRobotWsMessage) obj);
//            }
//        }
//
//        if (CollUtil.isNotEmpty(batchList)) {
//            try {
//                // 3. MyBatis-Plus 批量插入
//                // 需要确保 Service 层开启了 rewriteBatchedStatements=true 的 JDBC 配置以获得高性能
////                boolean success = messageDbService.saveBatch(batchList);
//                boolean success = true;
//
//                long cost = System.currentTimeMillis() - fetchStart;
//                if (success) {
//                    log.info("Persisted {} messages to DB in {} ms", batchList.size(), cost);
//                } else {
//                    log.error("Failed to save batch messages to DB");
//                    // 极端情况下的补偿机制：比如写回 Redis 或者写本地文件日志
//                    // 这是一个设计权衡：丢消息 vs 系统崩溃。通常聊天记录允许少量丢失。
//                }
//            } catch (Exception e) {
//                log.error("DB Batch Insert Error", e);
//                // 可以考虑重试机制，将 List 再 push 回去，或者推到死信队列
//            }
//        }
    }

    /**
     * 清理指定直播间的消息缓存
     * 
     * @param roomId 直播间ID
     */
    public void clearRoomMessageCache(Integer roomId) {
        if (roomId == null) {
            return;
        }

        try {
            // 1. 清理最近消息缓存
            String recentKey = KEY_MSG_RECENT_PREFIX + roomId;
            redisTemplate.delete(recentKey);
            
            // 2. 清理去重缓存
            String dedupKey = KEY_MSG_DEDUP_PREFIX + roomId;
            redisTemplate.delete(dedupKey);
            
            // 3. 清理消息内容缓存
            String contentKey = KEY_MSG_CONTENT_PREFIX + roomId;
            redisTemplate.delete(contentKey);
            
            // 4. 清理用户消息缓存（使用模式匹配删除所有相关key）
            String userKeyPattern = KEY_MSG_USER_PREFIX + roomId + ":*";
            Set<String> userKeys = redisTemplate.keys(userKeyPattern);
            if (userKeys != null && !userKeys.isEmpty()) {
                redisTemplate.delete(userKeys);
            }
            
            log.info("直播间消息缓存已清理，roomId: {}", roomId);
            
        } catch (Exception e) {
            log.error("清理直播间消息缓存异常，roomId: {}", roomId, e);
        }
    }

    // 优雅停机：应用关闭时尽可能刷盘
    @PostConstruct
    public void init() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.info("Application shutting down, flushing remaining messages...");
//            flushBufferToDb();
        }));
    }
}