package ink.wgink.module.activiti.service.activiti.impl;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.SearchException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.module.activiti.pojo.dtos.ModelDTO;
import ink.wgink.module.activiti.pojo.vos.ActivitiVO;
import ink.wgink.module.activiti.service.activiti.IActivitiModelService;
import ink.wgink.module.activiti.service.activiti.IActivitiService;
import ink.wgink.module.activiti.service.oa.INodeAssigneeService;
import ink.wgink.module.activiti.service.oa.INodeButtonService;
import ink.wgink.module.activiti.service.oa.INodeFieldService;
import ink.wgink.pojo.result.SuccessResultList;
import ink.wgink.util.date.DateUtil;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: ActivitiServiceImpl
 * @Description: activiti
 * @Author: WangGeng
 * @Date: 2021/7/11 16:46
 * @Version: 1.0
 **/
@Service
public class ActivitiServiceImpl extends DefaultBaseService implements IActivitiService {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private IActivitiModelService activitiModelService;
    @Autowired
    private INodeAssigneeService nodeAssigneeService;
    @Autowired
    private INodeFieldService nodeFieldService;
    @Autowired
    private INodeButtonService nodeButtonService;

    @Override
    public String create(ActivitiVO activitiVO) throws UnsupportedEncodingException {
        long count = repositoryService.createModelQuery().modelName(activitiVO.getModelName()).modelKey(activitiVO.getModelKey()).count();
        if (count >= 1) {
            throw new SearchException("模型已经存在");
        }
        Model model = repositoryService.newModel();
        model.setName(activitiVO.getModelName());
        model.setKey(activitiVO.getModelKey());
        model.setCategory(activitiVO.getModelCategory());
        model.setMetaInfo(createModelMetaInfo(activitiVO));
        model.setVersion(1);
        LOG.debug("保存模型");
        repositoryService.saveModel(model);
        addModelEditorSource(model.getId());
        return model.getId();
    }

    @Override
    public void saveDeploy(String modelId) throws IOException {
        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new SearchException("模型不存在");
        }
        byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
        if (modelEditorSource == null) {
            throw new SearchException("模型数据不存在");
        }
        LOG.debug("部署流程");
        JsonNode modelNode = new ObjectMapper().readTree(modelEditorSource);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);

        LOG.debug("获取流程节点列表");
        List<FlowNode> flowNodes = activitiModelService.listFlowNode(bpmnModel);
        LOG.debug("获得开始节点");
        StartEvent startEvent = activitiModelService.getStartEvent(flowNodes);
        // 判断是否存在表单，如果存在表单，说明是OA流程，进行初始化再部署，如果不是OA流程则正常部署
        String formKey = startEvent.getFormKey();
        String deploymentId;
        if (!StringUtils.isBlank(formKey)) {
            LOG.debug("存在自定义表单，是OA流程，执行初始化操作");
            LOG.debug("1.部署流程");
            deploymentId = deployProcess(model, bpmnModel);
            LOG.debug("2.绑定节点代理人");
            nodeAssigneeService.updateDeploymentId(modelId, model.getVersion(), deploymentId);
            LOG.debug("3.绑定节点表单字段");
            nodeFieldService.updateDeploymentId(modelId, model.getVersion(), deploymentId);
            LOG.debug("4.绑定节点按钮");
            nodeButtonService.updateDeploymentId(modelId, model.getVersion(), deploymentId);
        } else {
            LOG.debug("不存在自定义表单，不是OA流程，直接部署");
            deploymentId = deployProcess(model, bpmnModel);
        }
        model.setDeploymentId(deploymentId);
        LOG.debug("保存模型");
        repositoryService.saveModel(model);
    }

    /**
     * 部署流程
     *
     * @param model
     * @param bpmnModel
     * @return
     */
    private String deployProcess(Model model, BpmnModel bpmnModel) {
        Deployment deployment;
        try {
            deployment = repositoryService.createDeployment()
                    .name(model.getName())
                    .key(model.getKey())
                    .addBpmnModel(model.getKey() + "." + model.getVersion() + ".bpmn20.xml", bpmnModel)
                    .deploy();
        } catch (Exception e) {
            throw new SystemException(e.getMessage(), e);
        }
        return deployment.getId();
    }

    @Override
    public void revokeDeploy(String modelId) {
        Model modelData = repositoryService.getModel(modelId);
        if (modelData == null) {
            throw new SearchException("模型不存在");
        }
        /**
         * 参数不加true:为普通删除，如果当前规则下有正在执行的流程，则抛异常
         * 参数加true:为级联删除,会删除和当前规则相关的所有信息，包括历史
         */
        repositoryService.deleteDeployment(modelData.getDeploymentId());
    }

    @Override
    public List<ModelDTO> list() {
        return listModelDTO(repositoryService.createModelQuery().orderByCreateTime().asc().list());
    }

    @Override
    public List<ModelDTO> listByCategory(String modelCategory) {
        if(StringUtils.isBlank(modelCategory)) {
            return list();
        }
        return listModelDTO(repositoryService.createModelQuery().modelCategory(modelCategory).orderByCreateTime().asc().list());
    }

    @Override
    public SuccessResultList<List<ModelDTO>> listPage(int page, int rows) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        List<Model> models = modelQuery.orderByCreateTime().desc().listPage(page - 1, rows);
        return new SuccessResultList<>(listModelDTO(models), page, modelQuery.count());
    }

    @Override
    public SuccessResultList<List<ModelDTO>> listPageByCategory(String modelCategory, int page, int rows) {
        if(StringUtils.isBlank(modelCategory)) {
            return listPage(page, rows);
        }
        ModelQuery modelQuery = repositoryService.createModelQuery();
        List<Model> models = modelQuery.modelCategory(modelCategory).orderByCreateTime().desc().listPage(page - 1, rows);
        return new SuccessResultList<>(listModelDTO(models), page, modelQuery.count());
    }

    private List<ModelDTO> listModelDTO(List<Model> models) {
        if (models.isEmpty()) {
            return new ArrayList<>();
        }
        return models.stream().map(model -> {
            ModelDTO modelDTO = new ModelDTO();
            modelDTO.setId(model.getId());
            modelDTO.setName(model.getName());
            modelDTO.setModelCategory(model.getCategory());
            modelDTO.setKey(model.getKey());
            modelDTO.setVersion(model.getVersion());
            modelDTO.setDeploymentId(model.getDeploymentId());
            modelDTO.setCreateTime(DateUtil.getDateTime(model.getCreateTime()));
            modelDTO.setLastUpdateTime(DateUtil.getDateTime(model.getCreateTime()));
            return modelDTO;
        }).collect(Collectors.toList());
    }

    /**
     * 创建模型信息
     *
     * @param activitiVO
     * @return
     */
    private String createModelMetaInfo(ActivitiVO activitiVO) {
        JSONObject metaInfo = new JSONObject();
        metaInfo.put(ModelDataJsonConstants.MODEL_NAME, activitiVO.getModelName());
        metaInfo.put(ModelDataJsonConstants.MODEL_DESCRIPTION, activitiVO.getModelDescription());
        return metaInfo.toString();
    }

    /**
     * 添加模型时完善ModelEditorSource
     *
     * @param modelId
     */
    private void addModelEditorSource(String modelId) throws UnsupportedEncodingException {
        LOG.info("添加模型时完善ModelEditorSource入参模型ID：{}", modelId);
        JSONObject editorObject = new JSONObject();
        editorObject.put("id", "canvas");
        editorObject.put("resourceId", "canvas");

        JSONObject stencilSetObject = new JSONObject();
        stencilSetObject.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");

        editorObject.put("stencilset", stencilSetObject);
        repositoryService.addModelEditorSource(modelId, editorObject.toString().getBytes(ISystemConstant.CHARSET_UTF8));
    }

}
