package com.qqchen.deploy.backend.workflow.service.impl;

import com.qqchen.deploy.backend.framework.utils.RedisUtil;
import com.qqchen.deploy.backend.workflow.dto.WorkflowNodeLogDTO;
import com.qqchen.deploy.backend.workflow.enums.LogLevel;
import com.qqchen.deploy.backend.workflow.enums.LogSource;
import com.qqchen.deploy.backend.workflow.service.IWorkflowNodeLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 工作流节点日志服务实现 - 基于 Redis Stream
 * 日志保存在 Redis Stream 中，保留 7 天后自动删除
 * 
 * @author qqchen
 * @since 2025-11-03
 */
@Slf4j
@Service
public class WorkflowNodeLogServiceImpl implements IWorkflowNodeLogService {
    
    @Resource
    private RedisUtil redisUtil;
    
    /**
     * 日志流保留时间：7天
     */
    private static final int LOG_STREAM_TTL_DAYS = 7;
    
    /**
     * 每个流最多保留的日志条数（防止单个流过大）
     */
    private static final int LOG_STREAM_MAX_LEN = 50000;
    
    /**
     * 生成 Redis Stream Key
     * 格式: workflow:log:stream:{processInstanceId}:{nodeId}
     */
    private String buildStreamKey(String processInstanceId, String nodeId) {
        return String.format("workflow:log:stream:%s:%s", processInstanceId, nodeId);
    }
    
    @Override
    public void log(String processInstanceId, String nodeId, LogSource source, LogLevel level, String message) {
        try {
            String streamKey = buildStreamKey(processInstanceId, nodeId);
            
            // 构建日志条目
            Map<String, String> logEntry = new HashMap<>();
            logEntry.put("timestamp", String.valueOf(System.currentTimeMillis()));
            logEntry.put("level", level.name());
            logEntry.put("source", source.name());
            logEntry.put("message", message);
            
            // 写入 Redis Stream
            redisUtil.streamAdd(streamKey, logEntry);
            
            // 限制流长度（保留最新的日志）
            redisUtil.streamTrim(streamKey, LOG_STREAM_MAX_LEN);
            
            // 设置过期时间：7天（转换为秒）
            redisUtil.expire(streamKey, LOG_STREAM_TTL_DAYS * 24 * 60 * 60);
            
        } catch (Exception e) {
            // ❌ Redis 失败不影响业务流程
            log.error("Failed to save workflow log to Redis (non-blocking): processInstanceId={}, nodeId={}", 
                processInstanceId, nodeId, e);
        }
    }
    
    @Override
    public void batchLog(String processInstanceId, String nodeId, LogSource source, LogLevel level, List<String> messages) {
        if (messages == null || messages.isEmpty()) {
            return;
        }
        
        try {
            String streamKey = buildStreamKey(processInstanceId, nodeId);
            
            // 批量添加到 Stream
            for (String message : messages) {
                Map<String, String> logEntry = new HashMap<>();
                logEntry.put("timestamp", String.valueOf(System.currentTimeMillis()));
                logEntry.put("level", level.name());
                logEntry.put("source", source.name());
                logEntry.put("message", message);
                
                redisUtil.streamAdd(streamKey, logEntry);
            }
            
            // 限制流长度并设置过期（转换为秒）
            redisUtil.streamTrim(streamKey, LOG_STREAM_MAX_LEN);
            redisUtil.expire(streamKey, LOG_STREAM_TTL_DAYS * 24 * 60 * 60);
            
        } catch (Exception e) {
            log.error("Failed to batch save workflow logs to Redis (non-blocking): processInstanceId={}, nodeId={}, count={}", 
                processInstanceId, nodeId, messages.size(), e);
        }
    }
    
    @Override
    public void info(String processInstanceId, String nodeId, LogSource source, String message) {
        log(processInstanceId, nodeId, source, LogLevel.INFO, message);
    }
    
    @Override
    public void warn(String processInstanceId, String nodeId, LogSource source, String message) {
        log(processInstanceId, nodeId, source, LogLevel.WARN, message);
    }
    
    @Override
    public void error(String processInstanceId, String nodeId, LogSource source, String message) {
        log(processInstanceId, nodeId, source, LogLevel.ERROR, message);
    }
    
    @Override
    public List<WorkflowNodeLogDTO> getNodeLogs(String processInstanceId, String nodeId) {
        try {
            String streamKey = buildStreamKey(processInstanceId, nodeId);
            
            // 检查流是否存在
            if (!redisUtil.hasKey(streamKey)) {
                log.warn("日志流不存在，可能已过期: processInstanceId={}, nodeId={}", processInstanceId, nodeId);
                return Collections.emptyList();
            }
            
            // 从 Stream 读取所有日志 (从头到尾)
            List<MapRecord<String, Object, Object>> records = redisUtil.streamRangeAll(streamKey);
            
            if (records == null || records.isEmpty()) {
                return Collections.emptyList();
            }
            
            // 转换为 DTO
            return records.stream()
                .map(record -> convertToWorkflowNodeLog(processInstanceId, nodeId, record))
                .collect(Collectors.toList());
                
        } catch (Exception e) {
            log.error("Failed to get workflow logs from Redis: processInstanceId={}, nodeId={}", 
                processInstanceId, nodeId, e);
            return Collections.emptyList();
        }
    }
    
    @Override
    public Page<WorkflowNodeLogDTO> getNodeLogs(String processInstanceId, String nodeId, Pageable pageable) {
        List<WorkflowNodeLogDTO> allLogs = getNodeLogs(processInstanceId, nodeId);
        
        int start = (int) pageable.getOffset();
        int end = Math.min(start + pageable.getPageSize(), allLogs.size());
        
        if (start >= allLogs.size()) {
            return new PageImpl<>(Collections.emptyList(), pageable, allLogs.size());
        }
        
        List<WorkflowNodeLogDTO> pageContent = allLogs.subList(start, end);
        return new PageImpl<>(pageContent, pageable, allLogs.size());
    }
    
    @Override
    public void deleteNodeLogs(String processInstanceId, String nodeId) {
        try {
            String streamKey = buildStreamKey(processInstanceId, nodeId);
            redisUtil.del(streamKey);
            log.info("删除节点日志流: processInstanceId={}, nodeId={}", processInstanceId, nodeId);
        } catch (Exception e) {
            log.error("Failed to delete workflow logs from Redis: processInstanceId={}, nodeId={}", 
                processInstanceId, nodeId, e);
        }
    }
    
    /**
     * 将 Redis Stream Record 转换为 WorkflowNodeLogDTO
     */
    private WorkflowNodeLogDTO convertToWorkflowNodeLog(String processInstanceId, String nodeId, 
                                                      MapRecord<String, Object, Object> record) {
        WorkflowNodeLogDTO log = new WorkflowNodeLogDTO();
        log.setProcessInstanceId(processInstanceId);
        log.setNodeId(nodeId);
        
        Map<Object, Object> value = record.getValue();
        log.setTimestamp(Long.parseLong(value.get("timestamp").toString()));
        log.setLevel(LogLevel.valueOf(value.get("level").toString()));
        log.setSource(LogSource.valueOf(value.get("source").toString()));
        log.setMessage(value.get("message").toString());
        
        // Redis Stream ID 的第一部分作为序列号 (毫秒时间戳)
        String streamId = record.getId().getValue();
        log.setSequenceId(Long.parseLong(streamId.split("-")[0]));
        
        return log;
    }
}


