package com.huigou.explorer.application.impl;

import com.huigou.exception.ApplicationException;
import com.huigou.explorer.application.ModelApplication;
import com.huigou.explorer.application.TaskFormKeyGenerator;
import com.huigou.explorer.converters.WorkflowModeConverter;
import com.huigou.explorer.domain.model.Model;
import com.huigou.explorer.repository.ModelRepository;
import com.huigou.uasp.bpm.managment.application.ProcDefinitionApplication;
import com.huigou.uasp.bpm.managment.domain.model.ProcDefinition;
import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.util.mxXmlUtils;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.ExtensionAttribute;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.io.IOUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.w3c.dom.Document;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;

/**
 * @author yonghuan
 */
@Aspect
@Service
public class ModelApplicationImpl implements ModelApplication {

    private final Logger LOG = LoggerFactory.getLogger(ModelApplicationImpl.class);
    @Autowired
    private ModelRepository modelRepository;
    @Autowired
    private ProcDefinitionApplication procDefinitionApplication;
    @Autowired
    private WorkflowModeConverter workflowModeConverter;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private BpmnXMLConverter bpmnXMLConverter;
    @Autowired
    private TaskFormKeyGenerator taskFormKeyGenerator;

    private String mxGraphXmlTemplate;

    @Value("classpath:bpmnModelTemplate.bpmn")
    public void setBpmnModelTemplate(Resource bpmnModelTemplate) throws IOException, XMLStreamException {
        if (bpmnModelTemplate != null) {
            String bpmnModelXml = String.join("\n", IOUtils.readLines(bpmnModelTemplate.getInputStream(), StandardCharsets.UTF_8.name()));
            XMLStreamReader xtr = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(bpmnModelXml));
            BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xtr);
            byte[] bs = workflowModeConverter.convertToXML(bpmnModel, StandardCharsets.UTF_8);
            mxGraphXmlTemplate = new String(bs);
        }
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public Model save(Model model) {
        return modelRepository.save(model);
    }

    @Override
    public Model findByProcDefinitionId(String procDefinitionId) {
        return modelRepository.findByProcDefinitionId(procDefinitionId);
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void deploy(String id) {
        ProcDefinition pd = procDefinitionApplication.loadProcDefinition(id);
        Model model = modelRepository.findByProcDefinitionId(id);
        Assert.notNull(model, "模型不存在或者模型还未保存，请先保存模型再进行部署");
        BpmnModel bpmnModel;
        try {
            if (LOG.isInfoEnabled()) {
                LOG.info("mxgraphXml:{}", model.getXml());
            }
            bpmnModel = workflowModeConverter.convertToBpmnModel(model.getXml().getBytes(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new ApplicationException(e);
        }

        Process process = bpmnModel.getProcesses().get(0);
        String formNo = process.getExtensionElements()
                .getOrDefault("field", Collections.emptyList())
                .stream()
                .flatMap(field -> field.getAttributes().get("formNo").stream())
                .findAny()
                .map(ExtensionAttribute::getValue)
                .orElse(null);
        // 为流程定义绑定自定义表单编号
        pd.setFormNo(formNo);
        if (formNo != null) {
            // 为用户任务设置自定义表单formKey
            process.getFlowElements().stream()
                    .filter(fe -> fe instanceof UserTask)
                    .forEach(fe -> {
                        UserTask task = (UserTask) fe;
                        task.setFormKey(taskFormKeyGenerator.generateFormKey(process, task, formNo));
                    });
        }
        byte[] bpmnXml = bpmnXMLConverter.convertToXML(bpmnModel);
        if (LOG.isInfoEnabled()) {
            LOG.info(new String(bpmnXml, StandardCharsets.UTF_8));
        }
        Deployment deployment = this.repositoryService.createDeployment()
                .addInputStream(pd.getCode() + ".bpmn", new ByteArrayInputStream(bpmnXml))
                .deploy();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
        pd.setProcId(processDefinition.getKey());

        procDefinitionApplication.updateProcDefinition(pd);
        // 自动导入流程环节
        procDefinitionApplication.importProcUnits(pd.getId());
    }

    /**
     * 创建流程时候自动生成流程模型。
     */
    @Transactional
    @Around("execution(* com.huigou.uasp.bpm.managment.application.ProcDefinitionApplication.insertProcDefinition(..))")
    protected Object onInsertProcDefinition(ProceedingJoinPoint pjp) throws Throwable {
        ProcDefinition procDefinition = (ProcDefinition) pjp.getArgs()[0];
        Object returnVal = pjp.proceed(pjp.getArgs());
        if ("proc".equals(procDefinition.getNodeKindId())) {
            // 创建流程时候自动生成流程模型
            Model model = new Model();
            model.setProcDefinitionId(procDefinition.getId());
            Document document = mxXmlUtils.parseXml(mxGraphXmlTemplate);
            mxCodec codec = new mxCodec(document);
            mxGraphModel graphModel = (mxGraphModel) codec.decode(document.getDocumentElement());
            mxCell rootCell = (mxCell) graphModel.getRoot();
            rootCell.setAttribute("label", procDefinition.getName());
            rootCell.setAttribute("code", procDefinition.getCode());
            model.setXml(mxXmlUtils.getXml(codec.encode(graphModel)));
            model.setImageXml("formNo");
            modelRepository.save(model);
        }
        return returnVal;
    }
}
