
package com.cmvr.inspection.listener;

import cn.hutool.core.util.StrUtil;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.framework.websocket.service.MessagePushService;
import com.cmvr.inspection.domain.InspectionTaskInstance;
import com.cmvr.inspection.domain.InspectionTaskLog;
import com.cmvr.inspection.enums.InspectionLogTypeEnum;
import com.cmvr.inspection.enums.TaskStatusEnum;
import com.cmvr.inspection.service.IInspectionTaskInstanceService;
import com.cmvr.inspection.service.IInspectionTaskLogService;
import com.cmvr.test.flow.runtime.event.FlowExecutionEvent;
import com.cmvr.test.flow.runtime.event.FlowExecutionListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
@Slf4j
public class InspectionFlowExecutionListener implements FlowExecutionListener {
    private final IInspectionTaskInstanceService inspectionTaskInstanceService;
    private final IInspectionTaskLogService inspectionTaskLogService;
    private final MessagePushService messagePushService;

    /** key:itemId, value:已完成节点集合 */
    private final Map<String, Set<String>> itemNodeExecutMap = new HashMap<>();
    /** key:taskInsId(流程实例id=taskInsId), value:已执行item集合 */
    private final Map<String, Set<String>> taskItemExecutMap = new HashMap<>();

    public InspectionFlowExecutionListener(IInspectionTaskInstanceService inspectionTaskInstanceService,
                                           IInspectionTaskLogService inspectionTaskLogService,
                                           MessagePushService messagePushService) {
        this.inspectionTaskInstanceService = inspectionTaskInstanceService;
        this.inspectionTaskLogService = inspectionTaskLogService;
        this.messagePushService = messagePushService;
    }

    @Override
    public void onEvent(FlowExecutionEvent event) {
        String instId = event.getInstId();
        String itemId = event.getItemId();
        String nodeId = event.getNodeId();
        if (StrUtil.isBlank(instId)) {
            return;
        }

        // 1.节点完成才记录当前item下node
        if (StrUtil.isNotBlank(itemId) && StrUtil.isNotBlank(nodeId)
                && FlowExecutionEvent.EventType.NODE_COMPLETED == event.getEventType()) {
            itemNodeExecutMap.computeIfAbsent(itemId, k -> new HashSet<>()).add(nodeId);
        }
        // 任意事件：当前任务绑定item（item一启动就入Map，所以统计已完成item要-1）
        if (StrUtil.isNotBlank(instId) && StrUtil.isNotBlank(itemId)) {
            taskItemExecutMap.computeIfAbsent(instId, k -> new HashSet<>()).add(itemId);
        }

        InspectionTaskInstance taskInstance = inspectionTaskInstanceService.lambdaQuery()
                .eq(InspectionTaskInstance::getTaskInsId, instId)
                .one();
        if (taskInstance == null) {
            log.warn("未查询到巡检实例,taskInsId:{}", instId);
            return;
        }

        int targetStatus = TaskStatusEnum.RUNNING.getCode();
        boolean needUpdateDb = false;
        Date now = DateUtils.getNowDate();
        switch (event.getEventType()) {
            case TASK_COMPLETED:
                targetStatus = TaskStatusEnum.SUCCESS.getCode();
                needUpdateDb = true;
                clearCache(instId);
                pushCompleteMsg(taskInstance.getId(), now, targetStatus);
                break;
            case TASK_FAILED:
                targetStatus = TaskStatusEnum.FAILED.getCode();
                needUpdateDb = true;
                clearCache(instId);
                break;
            case NODE_COMPLETED:
            default:
                break;
        }

        if (needUpdateDb) {
            taskInstance.setStatus(targetStatus);
            taskInstance.setEndTime(now);
            inspectionTaskInstanceService.updateById(taskInstance);
        }

        // 节点推送
        if (StrUtil.isNotBlank(nodeId)) {
            int waypointCount = taskInstance.getWaypointCount() == null ? 0 : taskInstance.getWaypointCount();
            double progress = 0D;
            if (waypointCount > 0) {
                // 当前任务已执行item数量（item启动就存入集合，已完整跑完的item=总数-1）
                Set<String> itemSet = taskItemExecutMap.getOrDefault(instId, new HashSet<>());
                int itemTotal = itemSet.size();
                int finishedItemNum = itemTotal - 1;

                // 当前item已完成节点数
                Set<String> finishedNodeSet = itemNodeExecutMap.getOrDefault(itemId, new HashSet<>());
                int finishedNode = finishedNodeSet.size();
                int totalNode = event.getNodeCount() == 0 ? 1 : event.getNodeCount();

                // 已完整跑完点位占比
                double itemTotalRatio = finishedItemNum / (double) waypointCount;
                // 当前运行item内部节点占比
                double innerRatio = finishedNode / (double) totalNode / waypointCount;

                // 还原你的兜底：防止循环节点进度超限，最低不低于极小值
                double singleItemBase = 1d / waypointCount;
                innerRatio = Math.max(singleItemBase - 0.01, innerRatio);

                progress = (itemTotalRatio + innerRatio) * 100;
            }

            String info = event.getEventType() == FlowExecutionEvent.EventType.NODE_STARTED ? "开始执行" : "执行完成";
            String logId = UUID.randomUUID().toString().replace("-", "");
            // 四舍五入保留2位小数
            progress = Math.round(progress * 100) / 100.0;
            // 保存日志到数据库
            InspectionTaskLog taskLog = InspectionTaskLog.builder()
                    .id(logId)
                    .taskInstanceId(taskInstance.getId())
                    .taskId(taskInstance.getTaskId())
                    .itemId(itemId)
                    .nodeId(nodeId)
                    .nodeName(event.getNodeName())
                    .logType(InspectionLogTypeEnum.TEXT.getCode())
                    .logContent("节点【" + event.getNodeName() + "】" + info)
                    .status(targetStatus)
                    .progress(progress)
                    .logTime(now)
                    .build();

            try {
                inspectionTaskLogService.insertInspectionTaskLog(taskLog);
                log.info("巡检任务日志保存成功,logId:{},taskInstanceId:{},nodeId:{}", logId, taskInstance.getId(), nodeId);
            } catch (Exception e) {
                log.error("巡检任务日志保存失败,taskInstanceId:{},nodeId:{}", taskInstance.getId(), nodeId, e);
            }
            try {
                messagePushService.pushToChannel("InspectionTaskInstance", taskLog);
            } catch (Exception e) {
                log.info("消息推送异常，无订阅忽略,itemId:{}", itemId, e);
            }
        }
    }

    /** 任务结束统一清理缓存 */
    private void clearCache(String taskInsId) {
        Set<String> itemList = taskItemExecutMap.getOrDefault(taskInsId, new HashSet<>());
        itemList.forEach(itemNodeExecutMap::remove);
        taskItemExecutMap.remove(taskInsId);
    }

    /**
     * 推送完成任务信息（参数dbInsId为数据库主键ID）
     */
    public void pushCompleteMsg(String dbInsId, Date now, int targetStatus) {
        String logId = UUID.randomUUID().toString().replace("-", "");

        // 保存完成日志
        InspectionTaskLog taskLog = InspectionTaskLog.builder()
                .id(logId)
                .taskInstanceId(dbInsId)
                .logType(InspectionLogTypeEnum.TEXT.getCode())
                .logContent("任务执行完成")
                .status(targetStatus)
                .logTime(now)
                .build();

        try {
            inspectionTaskLogService.insertInspectionTaskLog(taskLog);
            log.info("任务完成日志保存成功,logId:{},taskInstanceId:{}", logId, dbInsId);
        } catch (Exception e) {
            log.error("任务完成日志保存失败,taskInstanceId:{}", dbInsId, e);
        }
        try {
            messagePushService.pushToChannel("InspectionTaskInstance", taskLog);
        } catch (Exception e) {
            log.info("消息推送异常，无订阅忽略,insId:{}", dbInsId, e);
        }
    }
}