package cd.casic.ci.process.engine.manager.impl;

import cd.casic.ci.process.engine.constant.EngineRuntimeConstant;
import cd.casic.ci.process.engine.manager.LoggerManager;
import cd.casic.ci.process.engine.runContext.TaskRunContext;
import cd.casic.ci.process.process.dao.pipeline.PipTaskLogDao;
import cd.casic.ci.process.process.dataObject.log.PipTaskLog;
import cd.casic.ci.process.process.dataObject.task.PipTask;
import cd.casic.ci.process.process.service.task.TaskService;
import cd.casic.ci.process.util.snowflake.SnowflakeIdentifierGenerator;
import cd.casic.framework.commons.util.network.IpUtil;
import cd.casic.ci.process.util.WebFrameworkUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 *  流水线运行时日志管理
 * */
@Service
@Slf4j
public class MemoryLogManager implements LoggerManager {
    /**
     *  第一级 是taskId，可能同一个账号、IP、建立多个连接所以保存为list
     * */
    private final  Map<String, List<SseEmitter>> taskIdSSEMap = new ConcurrentHashMap<>();

    private final  Map<String,StringBuffer> taskIdMemoryLogMap = new ConcurrentHashMap<>();
    @Resource
    private SnowflakeIdentifierGenerator identifierGenerator;
    @Resource
    private TaskService taskService;

    public final Integer FLUSH_DB_SIZE=2*1024*1024;
//    public final Integer FLUSH_DB_SIZE=1000;
    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    /**
     *  缓存最近一次执行的日志，key是taskId，val是数据库id（已入库的情况下）用于buffer满了增加日志内容
     *  读取日志的时候同时读取数据库和内存中的日志
     * */
    private final static Map<String,String> taskIdDbMap = new ConcurrentHashMap<>();
    @Resource
    private PipTaskLogDao logDao;
    @Override
    public SseEmitter subscribe(String taskId,HttpServletRequest request) {
        if (request!=null) {
            String ipAddr = IpUtil.getIpAddr(request);
            log.info("当前请求ip：{}",ipAddr);
        }
        SseEmitter emitter = new SseEmitter(30000L);
        emitterInit(emitter,taskId);
        Long loginUserId = WebFrameworkUtils.getLoginUserId();
        log.info("SSE连接建立");
        log.info("当前用户id：{}",loginUserId);
        log.info("当前taskId：{}",loginUserId);
        List<SseEmitter> taskIdSSEList = taskIdSSEMap.computeIfAbsent(taskId, k->new ArrayList<>(1));
        taskIdSSEList.add(emitter);

        return emitter;
    }

    @Override
    public void append(String taskId, String logContent) {
        /**
         *  先往内存里写
         *  内存满4mb入库，查询日志和入库操作用同一把锁
         *  然后新内容推sse
         * */
        StringBuffer logCache = taskIdMemoryLogMap.computeIfAbsent(taskId, k -> new StringBuffer());
        int length = logCache.length();
        if (length>=FLUSH_DB_SIZE) {
            synchronized (this){
                if (length>=FLUSH_DB_SIZE) {
                    StringBuffer stringBuffer = new StringBuffer();
                    //            清空缓存
                    taskIdMemoryLogMap.put(taskId, stringBuffer);
                    // 入库或者更新
                    if (taskIdDbMap.containsKey(taskId)) {
                        // 之前已经入库过
                        // 存在则更新
                        String id = taskIdDbMap.get(taskId);
                        PipTaskLog pipTaskLog = logDao.selectById(id);
                        pipTaskLog.setContent(pipTaskLog.getContent()+logCache.toString());
                        logDao.updateById(pipTaskLog);
                    } else {
                        // 不存在就新增
                        PipTaskLog pipTaskLog = new PipTaskLog();
                        pipTaskLog.setTaskId(taskId);
                        pipTaskLog.setContent(logCache.toString());
                        logDao.insert(pipTaskLog);
                        taskIdDbMap.put(taskId,pipTaskLog.getId());
                    }
                    logCache = stringBuffer;
                }
            }
        }
        logCache.append(logContent);
        List<SseEmitter> sseEmitters = taskIdSSEMap.get(taskId);
        if (!CollectionUtils.isEmpty(sseEmitters)) {
            for (SseEmitter sseEmitter : sseEmitters) {
                try {
                    sseEmitter.send(logContent);
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
    }
    @Transactional(rollbackFor = Exception.class)
    public void flushMemory(List<TaskRunContext> taskContextList){
        log.info("流水线日志开始入库");
        List<PipTaskLog> insertList = new ArrayList<>();
        List<PipTaskLog> updateList = new ArrayList<>();
        for (TaskRunContext taskRunContext : taskContextList) {
            String taskId = taskRunContext.getContextDef().getId();
            StringBuffer logCache = taskIdMemoryLogMap.get(taskId);
            if (taskIdDbMap.containsKey(taskId)) {
                // 之前已经入库过
                // 存在则更新
                String id = taskIdDbMap.get(taskId);
                PipTaskLog pipTaskLog = logDao.selectById(id);
                if (logCache!=null) {
                    pipTaskLog.setContent(pipTaskLog.getContent()+logCache);
                }
                updateList.add(pipTaskLog);
                taskIdMemoryLogMap.remove(taskId);
                taskRunContext.getLocalVariables().put(EngineRuntimeConstant.HIS_LOG_KEY,pipTaskLog.getId());
                List<SseEmitter> sseEmitters = taskIdSSEMap.get(taskId);
                if (!CollectionUtils.isEmpty(sseEmitters)) {
                    sseEmitters.forEach(ResponseBodyEmitter::complete);
                }
                logDao.updateById(pipTaskLog);
            } else {
                // 不存在就新增
                PipTaskLog pipTaskLog = new PipTaskLog();
                pipTaskLog.setTaskId(taskId);
                if (logCache!=null) {
                    pipTaskLog.setContent(logCache.toString());
                }
                pipTaskLog.setId(identifierGenerator.nextUUID(null));
                insertList.add(pipTaskLog);
                taskRunContext.getLocalVariables().put(EngineRuntimeConstant.HIS_LOG_KEY,pipTaskLog.getId());
                taskIdMemoryLogMap.remove(taskId);
                taskIdDbMap.put(taskId,pipTaskLog.getId());
                List<SseEmitter> sseEmitters = taskIdSSEMap.get(taskId);
                if (!CollectionUtils.isEmpty(sseEmitters)) {
                    sseEmitters.forEach(ResponseBodyEmitter::complete);
                }
            }
        }
        if (!CollectionUtils.isEmpty(insertList)) {
            logDao.insertBatch(insertList);
        }
        if (!CollectionUtils.isEmpty(updateList)) {
            logDao.updateBatch(updateList);
        }

    }

    @Override
    public String getLogContent(String taskId) {
        StringBuffer logCache = taskIdMemoryLogMap.getOrDefault(taskId, new StringBuffer());
        String id = taskIdDbMap.get(taskId);
        if (id != null) {
            PipTaskLog pipTaskLog = logDao.selectById(id);
            return pipTaskLog.getContent()+logCache.toString();
        }
        return logCache.toString();
    }
    public void clear(String pipelineId){
        PipTask query = new PipTask();
        query.setPipelineId(pipelineId);
        List<PipTask> taskList = taskService.getTask(query);
        taskList.forEach(it->taskIdDbMap.remove(it.getId()));
    }

    private void emitterInit(SseEmitter emitter,String taskId){
        // 维持心跳
        ScheduledFuture<?> scheduledFuture = scheduler.scheduleWithFixedDelay(() -> {
            try {
                emitter.send(SseEmitter.event().comment("heartbeat"));
            } catch (IOException e) {
                log.error("", e);
            }
        }, 0, 30, TimeUnit.SECONDS);
        emitter.onCompletion(()->{
            taskIdSSEMap.remove(taskId);
            scheduledFuture.cancel(true);
            log.info("===================taskId:{}断开连接===============",taskId);
        });
        emitter.onError((e)->{
            taskIdSSEMap.remove(taskId);
            scheduledFuture.cancel(true);
            log.error("===================错误，taskId:{}断开连接===============",taskId,e);
        });
        emitter.onTimeout(()->{
            taskIdSSEMap.remove(taskId);
            scheduledFuture.cancel(true);
            log.error("===================超时，taskId:{}断开连接===============",taskId);
        });
    }
}
