package com.ruoyi.platform.service.impl;

import com.ruoyi.platform.domain.ActiveLearn;
import com.ruoyi.platform.domain.ActiveLearnIns;
import com.ruoyi.platform.mapper.ActiveLearnDao;
import com.ruoyi.platform.mapper.ActiveLearnInsDao;
import com.ruoyi.platform.service.ActiveLearnInsService;
import com.ruoyi.platform.service.ResourceOccupyService;
import com.ruoyi.platform.utils.*;
import com.ruoyi.system.api.constant.Constant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;

@Service
public class ActiveLearnInsServiceImpl implements ActiveLearnInsService {
    private static final Logger logger = LoggerFactory.getLogger(ActiveLearnInsServiceImpl.class);

    @Value("${argo.url}")
    private String argoUrl;
    @Value("${argo.workflowStatus}")
    private String argoWorkflowStatus;
    @Value("${argo.workflowTermination}")
    private String argoWorkflowTermination;
    @Value("${minio.endpointIp}")
    String endpoint;
    @Value("${aim.url}")
    private String aimUrl;
    @Value("${aim.proxyUrl}")
    private String aimProxyUrl;

    @Resource
    private ActiveLearnInsDao activeLearnInsDao;
    @Resource
    private ActiveLearnDao activeLearnDao;
    @Resource
    private ResourceOccupyService resourceOccupyService;
    @Resource
    private MinioUtil minioUtil;

    @Override
    public Page<ActiveLearnIns> queryByPage(Long activeLearnId, PageRequest pageRequest) throws IOException {
        long total = this.activeLearnInsDao.count(activeLearnId);
        List<ActiveLearnIns> activeLearnInsList = activeLearnInsDao.queryAllByLimit(activeLearnId, pageRequest);
        return new PageImpl<>(activeLearnInsList, pageRequest, total);
    }

    @Override
    public ActiveLearnIns insert(ActiveLearnIns activeLearnIns) {
        activeLearnInsDao.insert(activeLearnIns);
        return activeLearnIns;
    }

    @Override
    public ActiveLearnIns update(ActiveLearnIns activeLearnIns) {
        activeLearnIns.setUpdateTime(new Date());
        activeLearnIns = queryStatusFromArgo(activeLearnIns);
        activeLearnInsDao.update(activeLearnIns);
        updateActiveLearnStatus(activeLearnIns.getActiveLearnId());
        if (Constant.Failed.equals(activeLearnIns.getStatus()) || Constant.Succeeded.equals(activeLearnIns.getStatus())) {
            resourceOccupyService.endDeduce(Constant.TaskType_ActiveLearn, null, activeLearnIns.getId(), null, null);
        }
        return activeLearnIns;
    }

    @Override
    @Transactional
    public String deleteById(Long id) {
        ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id);
        if (activeLearnIns == null) {
            return "实验实例不存在";
        }
        if (StringUtils.isEmpty(activeLearnIns.getStatus())) {
            activeLearnIns = queryStatusFromArgo(activeLearnIns);
        }
        if (StringUtils.equals(activeLearnIns.getStatus(), Constant.Running)) {
            return "实验实例正在运行，不可删除";
        }

        activeLearnIns.setState(Constant.State_invalid);
        int update = activeLearnInsDao.update(activeLearnIns);
        if (update > 0) {
            resourceOccupyService.deleteTaskState(Constant.TaskType_ActiveLearn, activeLearnIns.getActiveLearnId(), id);
            updateActiveLearnStatus(activeLearnIns.getActiveLearnId());
            return "删除成功";
        } else {
            return "删除失败";
        }
    }

    @Override
    public String batchDelete(List<Long> ids) {
        for (Long id : ids) {
            String result = deleteById(id);
            if (!"删除成功".equals(result)) {
                return result;
            }
        }
        return "删除成功";
    }

    @Override
    public boolean terminateActiveLearnIns(Long id) throws Exception {
        ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id);
        if (activeLearnIns == null) {
            throw new IllegalStateException("实验实例未查询到，id: " + id);
        }
        String currentStatus = activeLearnIns.getStatus();
        String name = activeLearnIns.getArgoInsName();
        String namespace = activeLearnIns.getArgoInsNs();

        // 获取当前状态，如果为空，则从Argo查询
        if (StringUtils.isEmpty(currentStatus)) {
            currentStatus = queryStatusFromArgo(activeLearnIns).getStatus();
        }

        // 只有状态是"Running"时才能终止实例
        if (!currentStatus.equalsIgnoreCase(Constant.Running)) {
            throw new Exception("终止错误，只有运行状态的实例才能终止"); // 如果不是"Running"状态，则不执行终止操作
        }

        // 创建请求数据map
        Map<String, Object> requestData = new HashMap<>();
        requestData.put("namespace", namespace);
        requestData.put("name", name);
        // 创建发送数据map，将请求数据作为"data"键的值
        Map<String, Object> res = new HashMap<>();
        res.put("data", requestData);

        try {
            // 发送POST请求到Argo工作流状态查询接口，并将请求数据转换为JSON
            String req = HttpUtils.sendPost(argoUrl + argoWorkflowTermination, null, JsonUtils.mapToJson(res));
            // 检查响应是否为空或无内容
            if (StringUtils.isEmpty(req)) {
                throw new RuntimeException("终止响应内容为空");

            }
            // 将响应的JSON字符串转换为Map对象
            Map<String, Object> runResMap = JsonUtils.jsonToMap(req);
            // 从响应Map中直接获取"errCode"的值
            Integer errCode = (Integer) runResMap.get("errCode");
            if (errCode != null && errCode == 0) {
                //更新autoMlIns,确保状态更新被保存到数据库
                ActiveLearnIns ins = queryStatusFromArgo(activeLearnIns);
                String nodeStatus = ins.getNodeStatus();
                Map<String, Object> nodeMap = JsonUtils.jsonToMap(nodeStatus);

                // 遍历 map
                for (Map.Entry<String, Object> entry : nodeMap.entrySet()) {
                    // 获取每个 Map 中的值并强制转换为 Map
                    Map<String, Object> innerMap = (Map<String, Object>) entry.getValue();
                    // 检查 phase 的值
                    if (innerMap.containsKey("phase")) {
                        String phaseValue = (String) innerMap.get("phase");
                        // 如果值不等于 Succeeded，则赋值为 Failed
                        if (!StringUtils.equals(Constant.Succeeded, phaseValue)) {
                            innerMap.put("phase", Constant.Failed);
                            innerMap.put("finishedAt", new Date());
                        }
                    }
                }
                ins.setNodeStatus(JsonUtils.mapToJson(nodeMap));
                ins.setStatus(Constant.Terminated);
                ins.setUpdateTime(new Date());
                ins.setFinishTime(new Date());
                activeLearnInsDao.update(ins);
                updateActiveLearnStatus(ins.getActiveLearnId());
                // 结束扣积分
                resourceOccupyService.endDeduce(Constant.TaskType_ActiveLearn, null, id, null, null);
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException("终止实例错误: " + e.getMessage(), e);
        }
    }

    @Override
    public ActiveLearnIns getDetailById(Long id) throws Exception {
        ActiveLearnIns activeLearnIns = activeLearnInsDao.queryById(id);
        if (Constant.Running.equals(activeLearnIns.getStatus()) || Constant.Pending.equals(activeLearnIns.getStatus())) {
            activeLearnIns = queryStatusFromArgo(activeLearnIns);
        }
        getTrialList(activeLearnIns);
        return activeLearnIns;
    }

    @Override
    public void updateActiveLearnStatus(Long activeLearnId) {
        List<ActiveLearnIns> insList = activeLearnInsDao.getByActiveLearnId(activeLearnId);
        List<String> statusList = new ArrayList<>();
        // 更新实验状态列表
        for (int i = 0; i < insList.size(); i++) {
            statusList.add(insList.get(i).getStatus());
        }
        String subStatus = statusList.toString().substring(1, statusList.toString().length() - 1);
        ActiveLearn activeLearn = activeLearnDao.getActiveLearnById(activeLearnId);
        if (!StringUtils.equals(activeLearn.getStatusList(), subStatus)) {
            activeLearn.setStatusList(subStatus);
            activeLearnDao.edit(activeLearn);
        }
    }

    @Override
    public ActiveLearnIns queryStatusFromArgo(ActiveLearnIns ins) {
        String namespace = ins.getArgoInsNs();
        String name = ins.getArgoInsName();

        // 创建请求数据map
        Map<String, Object> requestData = new HashMap<>();
        requestData.put("namespace", namespace);
        requestData.put("name", name);

        // 创建发送数据map，将请求数据作为"data"键的值
        Map<String, Object> res = new HashMap<>();
        res.put("data", requestData);

        try {
            // 发送POST请求到Argo工作流状态查询接口，并将请求数据转换为JSON
            String req = HttpUtils.sendPost(argoUrl + argoWorkflowStatus, null, JsonUtils.mapToJson(res));
            // 检查响应是否为空或无内容
            if (req == null || StringUtils.isEmpty(req)) {
                throw new RuntimeException("工作流状态响应为空");
            }
            // 将响应的JSON字符串转换为Map对象
            Map<String, Object> runResMap = JsonUtils.jsonToMap(req);
            // 从响应Map中获取"data"部分
            Map<String, Object> data = (Map<String, Object>) runResMap.get("data");
            if (data == null || data.isEmpty()) {
                throw new RuntimeException("工作流数据为空");
            }
            // 从"data"中获取"status"部分，并返回"phase"的值
            Map<String, Object> status = (Map<String, Object>) data.get("status");
            if (status == null || status.isEmpty()) {
                throw new RuntimeException("工作流状态为空");
            }

            //解析流水线结束时间
            String finishedAtString = (String) status.get("finishedAt");
            if (finishedAtString != null && !finishedAtString.isEmpty()) {
                Date finishTime = DateUtils.convertUTCtoShanghaiDate(finishedAtString);
                ins.setFinishTime(finishTime);
            }

            // 解析nodes字段,提取节点状态并转换为JSON字符串
            Map<String, Object> nodes = (Map<String, Object>) status.get("nodes");
            Map<String, Object> modifiedNodes = new LinkedHashMap<>();
            if (nodes != null) {
                for (Map.Entry<String, Object> nodeEntry : nodes.entrySet()) {
                    Map<String, Object> nodeDetails = (Map<String, Object>) nodeEntry.getValue();
                    String templateName = (String) nodeDetails.get("displayName");
                    modifiedNodes.put(templateName, nodeDetails);
                }
            }


            String nodeStatusJson = JsonUtils.mapToJson(modifiedNodes);
            ins.setNodeStatus(nodeStatusJson);

            //终止态为终止不改
            if (!StringUtils.equals(ins.getStatus(), Constant.Terminated)) {
                ins.setStatus(StringUtils.isNotEmpty((String) status.get("phase")) ? (String) status.get("phase") : Constant.Pending);
            }
            if (StringUtils.equals(ins.getStatus(), Constant.Error)) {
                ins.setStatus(Constant.Failed);
            }
            return ins;
        } catch (Exception e) {
            throw new RuntimeException("查询状态失败: " + e.getMessage(), e);
        }
    }

    @Override
    public List<ActiveLearnIns> queryActiveLearnInsIsNotTerminated() {
        return activeLearnInsDao.queryActiveLearnInsIsNotTerminated();
    }

    @Override
    public String getExpMetrics(String experimentInsId) throws Exception {
        String encodedUrlString = URLEncoder.encode("run.id in " + experimentInsId, "UTF-8");
        String url = aimProxyUrl + "/api/runs/search/run?query=" + encodedUrlString;
        String s = HttpUtils.sendGet(url, null);
        List<Map<String, Object>> responses = JacksonUtil.parseJSONStr2MapList(s);

        List<String> runIds = new ArrayList<>();
        for (Map response : responses) {
            runIds.add((String) response.get("run_hash"));
        }

        String decode = AIM64EncoderUtil.decode(runIds);
        return aimUrl + "/metrics?select=" + decode;
    }

    public void getTrialList(ActiveLearnIns ins) throws IOException {
        String directoryPath = ins.getResultPath();
        Map<String, Object> paramMap = JsonUtils.jsonToMap(ins.getParam());
        Map<String, Object> dataMap = (Map<String, Object>) paramMap.get("data");
        switch ((String) dataMap.get("framework_type")) {
            case Constant.AL_PYTORCH: {
                ins.setResultPath(endpoint + "/" + directoryPath + "/final_checkpoint/final_model_weights.pth");
                break;
            }
            case Constant.AL_SKLEARN: {
                ins.setResultPath(endpoint + "/" + directoryPath + "/final_checkpoint/final_model.joblib");
                break;
            }
            case Constant.AL_KERAS: {
                ins.setResultPath(endpoint + "/" + directoryPath + "/final_checkpoint/model.h5");
                break;
            }
        }

        try {
            String bucketName = directoryPath.substring(0, directoryPath.indexOf("/"));
            String prefix = directoryPath.substring(directoryPath.indexOf("/") + 1, directoryPath.length()) + "/";
            List<Map> fileMaps = minioUtil.listALFilesInDirectory(bucketName, prefix);

            if (!fileMaps.isEmpty()) {
                String trialJson = minioUtil.readObjectAsString(bucketName, prefix + "trial.json");
                List<Map<String, Object>> trialList = JacksonUtil.parseJSONStr2MapList(trialJson);
                ins.setTrialList(trialList);

                ins.setData(endpoint + "/" + directoryPath + "/data.json");
                for (Map<String, Object> trial : trialList) {
                    trial.put("query_idx", endpoint + "/" + directoryPath + "/" + trial.get("query_idx"));
                    Integer trialId = (Integer) trial.get("trial_id");
                    for (Map fileMap : fileMaps) {
                        String[] split = fileMap.get("name").toString().split("_");
                        if (split.length > 2) {
                            String idx = fileMap.get("name").toString().split("_")[2];
                            if (idx.equals(String.valueOf(trialId))) {
                                trial.put("file", fileMap);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("未找到结果文件");
        }
    }
}
