package com.ruoyi.platform.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.AssetWorkflow;
import com.ruoyi.platform.domain.ComputingResource;
import com.ruoyi.platform.domain.ServiceVersion;
import com.ruoyi.platform.mapper.AssetWorkflowDao;
import com.ruoyi.platform.mapper.ComputingResourceDao;
import com.ruoyi.platform.mapper.ServiceDao;
import com.ruoyi.platform.service.ResourceOccupyService;
import com.ruoyi.platform.service.ServiceService;
import com.ruoyi.platform.utils.ConvertUtil;
import com.ruoyi.platform.utils.HttpUtils;
import com.ruoyi.platform.utils.JacksonUtil;
import com.ruoyi.platform.utils.JsonUtils;
import com.ruoyi.platform.vo.serviceVos.ServiceCodeConfigVo;
import com.ruoyi.platform.vo.serviceVos.ServiceModelVo;
import com.ruoyi.platform.vo.serviceVos.ServiceVersionVo;
import com.ruoyi.system.api.constant.Constant;
import com.ruoyi.system.api.model.LoginUser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
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.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service("serviceService")
public class ServiceServiceImpl implements ServiceService {

    @Value("${argo.url}")
    private String argoUrl;

    @Value("${argo.modelService}")
    private String modelService;

    @Resource
    private ServiceDao serviceDao;
    @Resource
    private AssetWorkflowDao assetWorkflowDao;

    @Resource
    private ResourceOccupyService resourceOccupyService;

    @Resource
    private ComputingResourceDao computingResourceDao;

    @Override
    public Page<com.ruoyi.platform.domain.Service> queryByPageService(com.ruoyi.platform.domain.Service service, PageRequest pageRequest) {
        long total = serviceDao.countService(service);
        List<com.ruoyi.platform.domain.Service> services = serviceDao.queryByPageService(service, pageRequest);
        return new PageImpl<>(services, pageRequest, total);
    }

    @Override
    public Page<ServiceVersionVo> queryByPageServiceVersion(ServiceVersion serviceVersion, int page, int size) throws IOException {
        PageRequest pageRequest;
        if (StringUtils.isNotEmpty(serviceVersion.getRunState())) {
            pageRequest = PageRequest.of(page, Integer.MAX_VALUE);
            List<ServiceVersion> serviceVersions = serviceDao.queryByPageServiceVersion(serviceVersion, pageRequest);

            List<String> deploymentNames = serviceVersions.stream().map(ServiceVersion::getDeploymentName).collect(Collectors.toList());
            Map<String, String> runStates = getRunState(deploymentNames);
            if (runStates != null) {
                updateRunState(runStates, serviceVersions);
            }

            List<ServiceVersion> serviceVersionList = serviceVersions.stream().filter(serviceVersion1 -> {
                String runState;
                if (runStates != null) {
                    runState = runStates.get(serviceVersion1.getDeploymentName());
                } else {
                    runState = serviceVersion1.getRunState();
                }
                return serviceVersion.getRunState().equals(runState);
            }).collect(Collectors.toList());

            List<ServiceVersionVo> result = getServiceVersionVoList(serviceVersionList);

            PageRequest.of(page, size);
            return new PageImpl<>(result, pageRequest, result.size());
        } else {
            pageRequest = PageRequest.of(page, size);
            long total = serviceDao.countServiceVersion(serviceVersion);
            List<ServiceVersion> serviceVersions = serviceDao.queryByPageServiceVersion(serviceVersion, pageRequest);

            List<String> deploymentNames = serviceVersions.stream().map(ServiceVersion::getDeploymentName).collect(Collectors.toList());
            Map<String, String> runStates = getRunState(deploymentNames);
            if (runStates != null) {
                updateRunState(runStates, serviceVersions);
            }
            List<ServiceVersionVo> result = getServiceVersionVoList(serviceVersions);

            return new PageImpl<>(result, pageRequest, total);
        }
    }

    @Override
    public com.ruoyi.platform.domain.Service addService(com.ruoyi.platform.domain.Service service) {
        com.ruoyi.platform.domain.Service serviceByName = serviceDao.getServiceByName(service.getServiceName());
        if (serviceByName != null) {
            throw new RuntimeException("服务名称已存在，无法新增");
        }

        LoginUser loginUser = SecurityUtils.getLoginUser();
        service.setCreateBy(loginUser.getUsername());
        service.setUpdateBy(loginUser.getUsername());
        serviceDao.insertService(service);
        return service;
    }

    @Override
    public ServiceVersion addServiceVersion(ServiceVersionVo serviceVersionVo) throws Exception {
        ServiceVersion svByVersion = serviceDao.getSvByVersion(serviceVersionVo.getVersion(), serviceVersionVo.getServiceId());
        if (svByVersion != null) {
            throw new RuntimeException("服务版本已存在，无法新增");
        }
        ServiceVersion serviceVersion = getServiceVersion(serviceVersionVo);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        serviceVersion.setCreateBy(loginUser.getUsername());
        serviceVersion.setUpdateBy(loginUser.getUsername());
        serviceVersion.setRunState(Constant.Pending);
        ComputingResource computingResource = computingResourceDao.queryById(serviceVersionVo.getComputingResourceId());
        serviceVersion.setResource(computingResource.getDescription());
        serviceVersion.setComputingResourceId(serviceVersionVo.getComputingResourceId());
        serviceDao.insertServiceVersion(serviceVersion);
        runServiceVersion(serviceVersion.getId());
        return serviceVersion;
    }

    @Override
    public com.ruoyi.platform.domain.Service editService(com.ruoyi.platform.domain.Service service) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        service.setUpdateBy(loginUser.getUsername());
        serviceDao.updateService(service);
        return serviceDao.getServiceById(service.getId());
    }

    @Override
    public String editServiceVersion(ServiceVersionVo serviceVersionVo) throws Exception {
        ServiceVersion serviceVersion = getServiceVersion(serviceVersionVo);
        ServiceVersion oldServiceVersion = serviceDao.getServiceVersionById(serviceVersionVo.getId());
        if (!oldServiceVersion.getReplicas().equals(serviceVersionVo.getReplicas()) || !oldServiceVersion.getComputingResourceId().equals(serviceVersionVo.getComputingResourceId())
                || serviceVersionVo.getRerun()) {
            updateServiceVersion(serviceVersion, serviceVersionVo.getRerun());
            serviceVersion.setRunState(Constant.Pending);
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        serviceVersion.setUpdateBy(loginUser.getUsername());
        serviceDao.updateServiceVersion(serviceVersion);

        return "修改成功";
    }

    @Override
    public com.ruoyi.platform.domain.Service getService(Long id) {
        return serviceDao.getServiceById(id);
    }

    @Override
    public ServiceVersionVo getServiceVersion(Long id) throws IOException {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        ServiceVersionVo serviceVersionVo = getServiceVersionVo(serviceVersion);
        com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(serviceVersion.getServiceId());
        serviceVersionVo.setServiceName(service.getServiceName());
        // 拼接文档地址和gradio地址
        if (StringUtils.isNotEmpty(serviceVersionVo.getUrl())) {
            try {
                URL url = new URL(serviceVersionVo.getUrl());
                // 获取协议、主机、端口
                String protocol = url.getProtocol();
                String host = url.getHost();
                int port = url.getPort();
                String baseUrl = (port == -1 || port == url.getDefaultPort())
                        ? protocol + "://" + host
                        : protocol + "://" + host + ":" + port;
                serviceVersionVo.setDocPath(baseUrl + "/redoc");
                serviceVersionVo.setPagePath(baseUrl + "/gradio");
            } catch (MalformedURLException e) {
                throw new RuntimeException(e.getMessage());
            }
        }
        return serviceVersionVo;
    }

    @Override
    public Map<String, Object> serviceVersionCompare(Long id1, Long id2) throws IllegalAccessException, IOException {
        HashMap<String, Object> result = new HashMap<>();

        ServiceVersion serviceVersion1 = serviceDao.getServiceVersionById(id1);
        ServiceVersion serviceVersion2 = serviceDao.getServiceVersionById(id2);
        com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(serviceVersion1.getServiceId());

        ServiceVersionVo serviceVersionVo1 = getServiceVersionVo(serviceVersion1);
        ServiceVersionVo serviceVersionVo2 = getServiceVersionVo(serviceVersion2);
        serviceVersionVo1.setServiceName(service.getServiceName());
        serviceVersionVo2.setServiceName(service.getServiceName());

        Map<String, String> compareMap = ConvertUtil.compareObjects(serviceVersion1, serviceVersion2);

        result.put("version1", serviceVersionVo1);
        result.put("version2", serviceVersionVo2);
        result.put("differences", compareMap);

        return result;
    }

    @Override
    public String deleteService(Long id) throws Exception {
        com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(id);
        if (service == null) {
            throw new RuntimeException("服务不存在");
        }

        List<AssetWorkflow> assetWorkflow = assetWorkflowDao.getAssetWorkflow(Long.valueOf(id), Constant.Asset_Type_Service, null);
        if (assetWorkflow != null && !assetWorkflow.isEmpty()) {
            String workflows = String.join(",", assetWorkflow.stream().map(AssetWorkflow::getWorkflowName).collect(Collectors.toSet()));
            throw new Exception("该服务被流水线：" + workflows + "使用，不能删除，请先删除流水线");
        }

        LoginUser loginUser = SecurityUtils.getLoginUser();
        String username = loginUser.getUsername();
        String createBy = service.getCreateBy();
        if (!(StringUtils.equals(username, "admin") || StringUtils.equals(username, createBy))) {
            throw new RuntimeException("无权限删除该服务");
        }

        ServiceVersion serviceVersion = new ServiceVersion();
        serviceVersion.setServiceId(id);
        long versionCount = serviceDao.countServiceVersion(serviceVersion);
        if (versionCount != 0) {
            throw new RuntimeException("该服务下还有版本，不能删除");
        }

        resourceOccupyService.deleteTaskState(Constant.TaskType_Service, id, null);
        service.setState(Constant.State_invalid);
        return serviceDao.updateService(service) > 0 ? "删除成功" : "删除失败";
    }

    @Override
    @Transactional
    public String deleteServiceVersion(Long id) {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        serviceVersion.setState(Constant.State_invalid);
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("deployment_name", serviceVersion.getDeploymentName());
        paramMap.put("svc_name", serviceVersion.getSvcName());
        String req = HttpUtils.sendPost(argoUrl + modelService + "/delete", JSON.toJSONString(paramMap));
        if (StringUtils.isNotEmpty(req)) {
            Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req);
//            if ((Integer) reqMap.get("code") == 200) {
            // 结束扣积分
            resourceOccupyService.endDeduce(Constant.TaskType_Service, null, id, null, null);
            resourceOccupyService.deleteTaskState(Constant.TaskType_Service, serviceVersion.getServiceId(), id);
            return serviceDao.updateServiceVersion(serviceVersion) > 0 ? "删除成功" : "删除失败";
//            }
        }
        return "删除失败";
    }

    @Override
    public String runServiceVersion(Long id) throws Exception {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(serviceVersion.getServiceId());
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("service_name", service.getServiceName());
        paramMap.put("description", serviceVersion.getDescription());
        paramMap.put("computing_resource_id", serviceVersion.getComputingResourceId());
        paramMap.put("mount_path", serviceVersion.getMountPath());
        paramMap.put("replicas", serviceVersion.getReplicas());
        paramMap.put("env", JSONObject.parseObject(serviceVersion.getEnvVariables()));
        paramMap.put("code_config", JSONObject.parseObject(serviceVersion.getCodeConfig()));
        String image = (String)JsonUtils.jsonToMap(serviceVersion.getImage()).get("value");
        paramMap.put("image", image);
        paramMap.put("model", JSONObject.parseObject(serviceVersion.getModel()));
        paramMap.put("service_type", service.getServiceType());
        paramMap.put("deploy_type", serviceVersion.getDeployType());

        // 判断是否有资源
        if (resourceOccupyService.haveResource(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas())) {
            String req = HttpUtils.sendPost(argoUrl + modelService + "/create", JSON.toJSONString(paramMap));
            if (StringUtils.isNotEmpty(req)) {
                Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req);
                if ((Integer) reqMap.get("code") == 200) {
                    Map<String, String> data = (Map<String, String>) reqMap.get("data");
                    serviceVersion.setUrl(data.get("url"));
                    serviceVersion.setDeploymentName(data.get("deployment_name"));
                    serviceVersion.setSvcName(data.get("svc_name"));
                    serviceVersion.setRunState(Constant.Pending);
                    serviceDao.updateServiceVersion(serviceVersion);

                    // 记录开始扣积分
                    resourceOccupyService.startDeduce(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas(), Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), null, service.getServiceName(), null, Constant.State_building);
                    return "启动成功";
                } else {
                    throw new RuntimeException("启动失败");
                }
            } else {
                throw new RuntimeException("启动失败");
            }
        }
        return "启动失败";
    }

    @Override
    public String stopServiceVersion(Long id) {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("deployment_name", serviceVersion.getDeploymentName());
        String req = HttpUtils.sendPost(argoUrl + modelService + "/stop", JSON.toJSONString(paramMap));
        if (StringUtils.isNotEmpty(req)) {
            serviceVersion.setRunState(Constant.Stopped);
            serviceDao.updateServiceVersion(serviceVersion);
            // 结束扣积分
            resourceOccupyService.endDeduce(Constant.TaskType_Service, null, id, null, null);
            return "停止成功";
        } else {
            throw new RuntimeException("停止失败");
        }
    }

    @Override
    public String updateServiceVersion(ServiceVersion serviceVersion, Boolean reRun) throws Exception {
        // 判断是否有资源
        if (resourceOccupyService.haveResource(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas())) {
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("deployment_name", serviceVersion.getDeploymentName());
            HashMap<String, Object> updateMap = new HashMap<>();
            updateMap.put("replicas", serviceVersion.getReplicas());
            updateMap.put("computing_resource_id", serviceVersion.getComputingResourceId());
            paramMap.put("update_model", new JSONObject(updateMap));
            String req = HttpUtils.sendPost(argoUrl + modelService + "/update", JSON.toJSONString(paramMap));
            if (StringUtils.isNotEmpty(req)) {
                com.ruoyi.platform.domain.Service service = serviceDao.getServiceById(serviceVersion.getServiceId());
                ServiceVersion serviceVersionById = serviceDao.getServiceVersionById(serviceVersion.getId());
                // 记录开始扣积分
                if (reRun) {
                    resourceOccupyService.startDeduce(serviceVersion.getComputingResourceId(), serviceVersion.getReplicas(), Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), null, service.getServiceName(), null, Constant.State_building);
                } else if (Constant.Running.equals(serviceVersionById.getRunState()) || Constant.Pending.equals(serviceVersionById.getRunState())) {
                    resourceOccupyService.update(Constant.TaskType_Service, serviceVersion.getServiceId(), serviceVersion.getId(), serviceVersion.getComputingResourceId(), serviceVersion.getReplicas(), service.getServiceName());
                }
                return "修改成功";
            } else {
                throw new RuntimeException("更新失败");
            }
        }
        throw new RuntimeException("更新失败");
    }

    @Override
    public HashMap<String, String> getServiceVersionLog(Long id, String startTime, String endTime) {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("deployment_name", serviceVersion.getDeploymentName());
        paramMap.put("start_time", startTime);
        paramMap.put("end_time", endTime);
        String req = HttpUtils.sendPost(argoUrl + modelService + "/getLog", JSON.toJSONString(paramMap));
        if (StringUtils.isNotEmpty(req)) {
            Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req);
            if (reqMap.get("code") != null && (Integer) reqMap.get("code") == 200) {
                return (HashMap<String, String>) reqMap.get("data");
            } else {
                throw new RuntimeException("获取日志失败");
            }
        } else {
            throw new RuntimeException("获取日志失败");
        }
    }

    @Override
    public Map<String, Object> getServiceVersionDocs(Long id) {
        ServiceVersion serviceVersion = serviceDao.getServiceVersionById(id);
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("deployment_name", serviceVersion.getDeploymentName());
        String req = HttpUtils.sendPost(argoUrl + modelService + "/getDocs", JSON.toJSONString(paramMap));
        if (StringUtils.isNotEmpty(req)) {
            Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req);
            Map<String, Object> data = (Map<String, Object>) reqMap.get("data");
            return data;
        } else {
            throw new RuntimeException("获取日志失败");
        }
    }

    @Override
    public List<ServiceVersion> serviceVersionList(Long id) {
        return serviceDao.getServiceVersionList(id);
    }


    ServiceVersion getServiceVersion(ServiceVersionVo serviceVersionVo) {
        ServiceVersion serviceVersion = new ServiceVersion();
        BeanUtils.copyProperties(serviceVersionVo, serviceVersion);
        serviceVersion.setModel(JSON.toJSONString(serviceVersionVo.getModel()));
        serviceVersion.setCodeConfig(JSON.toJSONString(serviceVersionVo.getCodeConfig()));
        serviceVersion.setEnvVariables(JSON.toJSONString(serviceVersionVo.getEnvVariables()));
        serviceVersion.setImage(JacksonUtil.toJSONString(serviceVersionVo.getImage()));

        return serviceVersion;
    }

    List<ServiceVersionVo> getServiceVersionVoList(List<ServiceVersion> serviceVersionList) throws IOException {
        List<ServiceVersionVo> result = new ArrayList<>();

        for (ServiceVersion sv : serviceVersionList) {
            ServiceVersionVo serviceVersionVo = getServiceVersionVo(sv);
            result.add(serviceVersionVo);
        }
        return result;
    }

    ServiceVersionVo getServiceVersionVo(ServiceVersion sv) throws IOException {
        ServiceVersionVo serviceVersionVo = new ServiceVersionVo();
        BeanUtils.copyProperties(sv, serviceVersionVo);
        ServiceModelVo serviceModelVo = JSON.parseObject(sv.getModel(), ServiceModelVo.class);
        serviceVersionVo.setModel(serviceModelVo);

        ServiceCodeConfigVo serviceCodeConfigVo = JSON.parseObject(sv.getCodeConfig(), ServiceCodeConfigVo.class);
        serviceVersionVo.setCodeConfig(serviceCodeConfigVo);

        serviceVersionVo.setEnvVariables(JacksonUtil.parseJSONStr2Map(sv.getEnvVariables()));
        serviceVersionVo.setImage(JsonUtils.jsonToMap(sv.getImage()));
        return serviceVersionVo;
    }

    @Override
    public Map<String, String> getRunState(List<String> deploymentNames) {
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("deployment_names", deploymentNames);
        String req = HttpUtils.sendPost(argoUrl + modelService + "/getStatus", JSON.toJSONString(paramMap));
        if (StringUtils.isNotEmpty(req)) {
            Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req);
            Map<String, Map<String, String>> data = (Map<String, Map<String, String>>) reqMap.get("data");
            if (data != null) {
                return data.get("status");
            }
            return null;
        } else {
            return null;
        }
    }

    @Override
    public void updateRunState(Map<String, String> runStates, List<ServiceVersion> serviceVersionList) {
        for (ServiceVersion sv : serviceVersionList) {
            String runState = runStates.get(sv.getDeploymentName());
            sv.setRunState(runState);
            serviceDao.updateRunState(sv.getDeploymentName(), runState);
        }
    }
}
