package com.css.simulation.resource.scheduler.app.service;

import api.common.pojo.constants.DictConstants;
import api.common.util.HttpUtil;
import api.common.util.TimeUtil;
import com.css.simulation.resource.scheduler.domain.service.ProjectDomainService;
import com.css.simulation.resource.scheduler.domain.service.TaskDomainService;
import com.css.simulation.resource.scheduler.infra.configuration.custom.CustomConfiguration;
import com.css.simulation.resource.scheduler.infra.configuration.kubernetes.KubernetesConfiguration;
import com.css.simulation.resource.scheduler.infra.configuration.kubernetes.KubernetesUtil;
import com.css.simulation.resource.scheduler.infra.fs.minio.MinioUtil;
import com.css.simulation.resource.scheduler.infra.db.redis.CustomRedisClient;
import com.css.simulation.resource.scheduler.infra.entity.PrefixEntity;
import com.css.simulation.resource.scheduler.infra.entity.ProjectEntity;
import com.css.simulation.resource.scheduler.infra.entity.TaskEntity;
import com.css.simulation.resource.scheduler.infra.db.mysql.mapper.SimulationManualProjectTaskMapper;
import com.css.simulation.resource.scheduler.infra.db.redis.RedisUtil;
import io.kubernetes.client.openapi.ApiClient;
import io.minio.MinioClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class TaskApplicationService {
    @Value("${minio.bucket-name}")
    private String bucketName;
    @Value("${scheduler.minio-path.project-result}")
    private String resultPathMinio;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private SimulationManualProjectTaskMapper simulationManualProjectTaskMapper;
    @Resource
    private MinioClient minioClient;
    @Resource
    private ProjectDomainService projectDomainService;
    @Resource
    private KubernetesConfiguration kubernetesConfiguration;
    @Resource
    private ApiClient apiClient;
    @Resource
    private CustomRedisClient customRedisClient;
    @Resource
    private CustomConfiguration customConfiguration;
    @Resource
    private TaskDomainService taskDomainService;


    // -------------------------------- Comment --------------------------------

    /**
     * state 接口不能阻塞，因为会导致 pod 里调用接口超时
     *
     * @param taskId  任务id
     * @param state   任务状态
     * @param podName podName
     */
    @Async
    public void state(String taskId, String state, String podName) {
        TaskEntity taskEntity = simulationManualProjectTaskMapper.selectById(taskId);
        if (taskEntity == null) {
            log.info("收到不存在的任务的状态消息：" + taskId);
            return;
        }
        String projectId = taskEntity.getPId(); // 项目 id
        String lock1 = "taskId:" + taskId + ":state:" + state + ":pod-name:" + podName;

        customRedisClient.lock(lock1, 1L, 30 * 60L);
        try {
            // 查询相关信息
            ProjectEntity projectEntity = projectDomainService.getProjectByProjectId(projectId);
            String projectType = projectEntity.getProjectType();  // 项目类型
            String maxSimulationTime = projectEntity.getMaxSimulationTime();  // 项目类型
            String projectUserId = taskEntity.getCreateUserId();   // 用户 id
            PrefixEntity redisPrefix = projectDomainService.getRedisPrefixByUserIdAndProjectIdAndTaskId(projectUserId, projectId, taskId);
            // 判断是否完成
            boolean isCompleted;
            String nodeName = projectDomainService.getNodeNameOfPod(projectId, podName);
            final String isChoiceGpu = projectDomainService.getIsChoiceGpuByProjectId(projectId);
            if (DictConstants.TASK_RUNNING.equals(state)) {  // 运行中的 pod 无需删除
                // 将运行中的任务的 pod 名称放入 redis
                stringRedisTemplate.opsForValue().set(redisPrefix.getTaskPodKey(), podName);
                log.info("修改任务 " + taskId + " 的状态为 " + state + "，pod 名称为：" + podName);
                simulationManualProjectTaskMapper.updateStateWithStartTime(taskId, state, TimeUtil.getNowForMysql());
                return;
            } else { // 结束的 pod 都直接删除，并判断项目是否完成
                // -------------------------------- 处理状态 --------------------------------
                log.info("修改任务 {} 的状态为 {} ，pod 名称为 {} ，并删除 pod。", taskId, state, podName);
                if (DictConstants.TASK_ABORTED.equals(state)) {
                    String minioPathOfErrorLog = resultPathMinio + projectId + "/" + taskId + "error.log";
                    boolean objectExist = MinioUtil.isObjectExist(minioClient, bucketName, minioPathOfErrorLog);
                    String targetEvaluate;
                    if (objectExist) {
                        String errorString = MinioUtil.downloadToString(minioClient, bucketName, minioPathOfErrorLog);
                        String[] lines = errorString.split("\n");
                        StringBuilder errorMessage = new StringBuilder();
                        for (String line : lines) {
                            if (line.startsWith("Original Error")) {
                                errorMessage.append(line).append("\n");
                            }
                            if (line.startsWith("Possible Cause")) {
                                errorMessage.append(line);
                                break;
                            }
                        }
                        targetEvaluate = errorMessage.toString();
                    } else {
                        targetEvaluate = DictConstants.TASK_ERROR_REASON_2;
                    }
                    simulationManualProjectTaskMapper.updateFailStateWithStopTime(taskId, state, TimeUtil.getNowForMysql(), targetEvaluate);
                } else if (DictConstants.TASK_TERMINATED.equals(state)) {
                    simulationManualProjectTaskMapper.updateFailStateWithStopTime(taskId, state, TimeUtil.getNowForMysql(), DictConstants.TASK_ERROR_REASON_3);
                } else if (DictConstants.TASK_ANALYSIS.equals(state)) { // 该状态只会获得一次
                    log.info("任务 {} 的状态为 {} ，pod 名称为 {}", taskId, state, podName);
                    simulationManualProjectTaskMapper.updateSuccessStateWithStopTime(taskId, state, TimeUtil.getNowForMysql());
                    // 查询项目是否使用 CPU 生成视频
                    if (DictConstants.VIDEO_CPU.equals(isChoiceGpu)) {
                        String generateVideoKey = "task:" + taskId + ":generateVideo";
                        customRedisClient.set(generateVideoKey, "0");
                        HttpUtil.get(customConfiguration.getGenerateVideoUrl().replace("simulation-resource-video", nodeName) + "?generateVideoKey=" + generateVideoKey + "&nodeName=" + nodeName + "&projectId=" + projectId + "&projectType=" + projectType + "&maxSimulationTime=" + maxSimulationTime + "&taskId=" + taskId);
//                    HttpUtil.get("http://" + nodeName + ":8007//simulation/resource/video/generate" + "?generateVideoKey=" + generateVideoKey + "&nodeName=" + nodeName + "&projectId" + projectId + "&projectType" + projectType + "&maxSimulationTime" + maxSimulationTime + "&taskId" + taskId);
//                    videoFeignClient.generateVideo(generateVideoKey, nodeName, projectId, projectType, maxSimulationTime, taskId);
                        log.info("任务 {} 使用 CPU 生成视频开始>>>>>>>", taskId);
                        while (true) {
                            TimeUnit.SECONDS.sleep(3);
                            final String generateVideoValue = customRedisClient.get(generateVideoKey);
                            if (DictConstants.YES.equals(generateVideoValue)) {
                                customRedisClient.delete(generateVideoKey);
                                break;
                            }
                        }
                        log.info("任务 {} 使用 CPU 生成视频结束<<<<<<<", taskId);
                    }
                }
                // -------------------------------- 判断项目是否结束 --------------------------------
                isCompleted = projectDomainService.complete(redisPrefix, projectId);
                if (isCompleted) {
                    log.info("项目 " + projectId + " 已完成。");
                    //如果项目已完成先把 pod 删除，并归还并行度
                    KubernetesUtil.deletePod2(apiClient, kubernetesConfiguration.getNamespace(), podName);
                    projectDomainService.incrementOneParallelism(isChoiceGpu, nodeName);
                    projectDomainService.releaseLicense(projectDomainService.getClusterUserIdByProjectUserId(projectUserId), projectDomainService.getModelTypeByProjectIdAndProjectType(projectId, projectType), 1);
                } else {
                    log.info("项目 " + projectId + " 还未运行完成。");
                    projectDomainService.createNextPod(projectUserId, projectId, projectType, nodeName, podName);
                }
                RedisUtil.deleteByPrefix(stringRedisTemplate, redisPrefix.getTaskMessageKey());
                RedisUtil.deleteByPrefix(stringRedisTemplate, redisPrefix.getTaskPodKey());
            }
            //* -------------------------------- 打分 --------------------------------
            String lock2 = "project:" + projectId + ":completed-lock";
            try {
                if (isCompleted) {
                    customRedisClient.tryLock(lock2, 10 * 60L);
                    log.info("项目 {} 开始打分。", projectId);
                    taskDomainService.score(redisPrefix.getProjectRunningKey(), projectUserId, projectId, projectType);
                    log.info("项目 {} 计算评价等级。", projectId);
                    taskDomainService.evaluationLevel(projectId);
                    log.info("项目 {} 开始释放资源。", projectId);
                    taskDomainService.done(redisPrefix, projectId, projectType);
                    log.info("项目 {} 运行结束。", projectId);
                }
            } finally {
                customRedisClient.unlock(lock2);
            }
        } catch (io.kubernetes.client.openapi.ApiException apiException) {
            log.info("POD：" + podName + "已删除。");
        } catch (Exception e) {
            log.error("项目 {} 已结束。", projectId, e);
        } finally {
            customRedisClient.unlock(lock1);
        }

    }


    public Boolean confirm(String taskId) {
        return taskDomainService.taskConfirm(taskId);
    }

    public void tick(String taskId) {
        taskDomainService.taskTick(taskId);
    }
}



