/**
 * Copyright (c) 2020 人人开源 All rights reserved.
 * <p>
 * https://www.kioor.io
 * <p>
 * 版权所有，侵权必究！
 */
package com.kioor.modules.flow.service;

import cn.hutool.core.util.StrUtil;
import com.kioor.common.exception.RenException;
import com.kioor.common.page.PageData;
import com.kioor.common.utils.JsonUtils;
import com.kioor.modules.flow.dao.BpmDefinitionExtDao;
import com.kioor.modules.flow.dto.BpmFormDTO;
import com.kioor.modules.flow.dto.ModelDTO;
import com.kioor.modules.flow.dto.ModelRequestDTO;
import com.kioor.modules.flow.dto.ProcessDefinitionDTO;
import com.kioor.modules.flow.entity.BpmDefinitionExtEntity;
import com.kioor.modules.flow.utils.PageUtils;
import lombok.AllArgsConstructor;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模型管理
 *
 * @author Mark sunlightcs@gmail.com
 */
@Service
@AllArgsConstructor
public class FlowModelService {
    private final RepositoryService repositoryService;
    private final BpmFormService bpmFormService;
    private final BpmDefinitionExtDao bpmDefinitionExtDao;

    public PageData<ModelDTO> page(Map<String, Object> params) {
        String key = (String) params.get("key");
        String name = (String) params.get("name");
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StrUtil.isNotBlank(key)) {
            modelQuery.modelKey(key);
        }
        if (StrUtil.isNotBlank(name)) {
            modelQuery.modelNameLike("%" + name + "%");
        }

        List<Model> models = modelQuery.orderByCreateTime().desc()
                .listPage(PageUtils.getPageOffset(params), PageUtils.getPageLimit(params));

        List<ModelDTO> modelList = new ArrayList<>();
        models.forEach(model -> {
            ModelDTO modelDTO = new ModelDTO();
            modelDTO.setId(model.getId());
            modelDTO.setKey(model.getKey());
            modelDTO.setName(model.getName());
            modelDTO.setVersion(model.getVersion());
            modelDTO.setDeploymentId(model.getDeploymentId());
            modelDTO.setCreated(model.getCreateTime());
            modelDTO.setLastUpdated(model.getLastUpdateTime());

            if (StrUtil.isNotBlank(model.getMetaInfo())) {
                modelDTO.setMetaInfo(JsonUtils.parseObject(model.getMetaInfo(), Map.class));
            }

            if (StrUtil.isNotBlank(model.getDeploymentId())) {
                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(model.getDeploymentId()).singleResult();

                ProcessDefinitionDTO processDefinitionDTO = new ProcessDefinitionDTO();
                processDefinitionDTO.setId(processDefinition.getId());
                processDefinitionDTO.setSuspended(processDefinition.isSuspended());
                modelDTO.setProcessDefinition(processDefinitionDTO);
            }

            modelList.add(modelDTO);
        });

        return new PageData<>(modelList, modelQuery.count());
    }

    @Transactional(rollbackFor = Exception.class)
    public String saveModel(ModelRequestDTO dto) {
        // 是否存在
        Model model = getModelByKey(dto.getKey());
        if (model != null) {
            throw new RenException("流程key已存在");
        }

        // 创建模型
        model = repositoryService.newModel();
        model.setKey(dto.getKey());
        model.setName(dto.getName());

        // 保存模型
        repositoryService.saveModel(model);

        // 保存BPMN XML
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(dto.getBpmnXml()));

        return model.getId();
    }

    public ModelDTO getModel(String id) {
        Model model = repositoryService.getModel(id);
        if (model == null) {
            throw new RenException("流程不存在");
        }

        ModelDTO modelDTO = new ModelDTO();
        modelDTO.setId(model.getId());
        modelDTO.setKey(model.getKey());
        modelDTO.setName(model.getName());

        // 拼接 bpmn XML
        byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
        modelDTO.setBpmnXml(StrUtil.utf8Str(bpmnBytes));
        return modelDTO;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateModel(ModelRequestDTO dto) {
        // 是否存在
        Model model = repositoryService.getModel(dto.getId());
        if (model == null) {
            throw new RenException("流程不存在");
        }

        // 修改模型
        if (StrUtil.isNotBlank(dto.getKey())) {
            model.setKey(dto.getKey());
        }

        if (StrUtil.isNotBlank(dto.getName())) {
            model.setName(dto.getName());
        }
        if (StrUtil.isNotBlank(dto.getFormId())) {
            Map<String, String> metaInfo = new HashMap<>();
            metaInfo.put("formId", dto.getFormId());
            metaInfo.put("formName", dto.getFormName());
            model.setMetaInfo(JsonUtils.toJsonString(metaInfo));
        }
        repositoryService.saveModel(model);

        // 保存BPMN XML
        if (StrUtil.isNotBlank(dto.getBpmnXml())) {
            repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(dto.getBpmnXml()));
        }

    }

    private Model getModelByKey(String key) {
        return repositoryService.createModelQuery().modelKey(key).singleResult();
    }

    /**
     * 部署
     *
     * @param modelId 模型ID
     */
    public String deploymentByModelId(String modelId) {
        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new RenException("流程不存在");
        }

        if (StrUtil.isBlank(model.getMetaInfo())) {
            throw new RenException("未配置流程表单");
        }

        Map<String, String> metaInfo = JsonUtils.parseObject(model.getMetaInfo(), Map.class);
        Long formId = Long.parseLong(metaInfo.get("formId"));
        BpmFormDTO bpmForm = bpmFormService.get(formId);
        if (bpmForm == null) {
            throw new RenException("流程表单不存在");
        }

        // 拼接 bpmn XML
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        Deployment deployment = repositoryService.createDeployment()
                .name(model.getName())
                .addBytes(model.getKey() + ".bpmn20.xml", bpmnBytes).deploy();

        // 获取 ProcessDefinition
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deployment.getId()).singleResult();

        // 新增 流程定义扩展
        BpmDefinitionExtEntity definitionExt = new BpmDefinitionExtEntity();
        definitionExt.setModelId(modelId);
        definitionExt.setProcessDefinitionId(processDefinition.getId());
        definitionExt.setFormId(bpmForm.getId());
        definitionExt.setFormContent(bpmForm.getContent());
        bpmDefinitionExtDao.insert(definitionExt);

        // update deploymentId
        model.setDeploymentId(deployment.getId());
        repositoryService.saveModel(model);

        return deployment.getId();
    }

    public void deleteModel(String id) {
        // 是否存在
        Model model = repositoryService.getModel(id);
        if (model == null) {
            throw new RenException("流程不存在");
        }

        // 删除
        repositoryService.deleteModel(id);
        if (StrUtil.isNotBlank(model.getDeploymentId())) {
            repositoryService.deleteDeployment(model.getDeploymentId(), true);
        }

    }
}