/*
 * Copyright (c) 2023 codvision.com All Rights Reserved.
 */

package com.codvision.workflow.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.codvision.auth.entity.vo.SysUserVO;
import com.codvision.auth.support.UserContext;
import com.codvision.commoncore.exception.BusinessException;
import com.codvision.commoncore.utils.Preconditions;
import com.codvision.webcore.bean.QueryPage;
import com.codvision.webcore.util.QueryPageUtil;
import com.codvision.workflow.config.WorkFlowServiceFactory;
import com.codvision.workflow.entity.WorkFlowDefinition;
import com.codvision.workflow.entity.WorkFlowFormLink;
import com.codvision.workflow.entity.WorkFlowVersionRecord;
import com.codvision.workflow.entity.dto.WorkFlowDefinitionDTO;
import com.codvision.workflow.entity.dto.WorkFlowFormLinkDTO;
import com.codvision.workflow.entity.vo.WorkFlowElementVO;
import com.codvision.workflow.mapper.WorkFlowDefinitionMapper;
import com.codvision.workflow.mapper.WorkFlowVersionRecordMapper;
import com.codvision.workflow.service.WorkFlowDataService;
import com.codvision.workflow.service.WorkFlowDefinitionService;
import com.codvision.workflow.service.WorkFlowFormLinkService;
import com.codvision.workflow.service.WorkFlowFormService;
import com.codvision.workflow.util.WorkFlowUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.engine.impl.util.ExecutionGraphUtil;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hxl
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkFlowDefinitionServiceImpl extends WorkFlowServiceFactory implements WorkFlowDefinitionService {

    private final WorkFlowDefinitionMapper mapper;
    private final WorkFlowFormService formService;
    private final WorkFlowDataService dataService;
    private final WorkFlowFormLinkService formLinkService;
    private final WorkFlowVersionRecordMapper versionRecordMapper;

    @Override
    public IPage<WorkFlowDefinition> getPageResult(QueryPage<Map<String, Object>> queryPage) {
        return mapper.getPageResult(queryPage.getPage(), QueryPageUtil.queryMap(queryPage));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(WorkFlowDefinitionDTO definitionDTO, InputStream in) {
        // 部署
        Deployment deploy = repositoryService.createDeployment()
                .addInputStream(definitionDTO.getFlowName() + ".bpmn", in).name(definitionDTO.getFlowName()).category(definitionDTO.getCategory()).deploy();
        // 获取流程定义信息
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId()).singleResult();
        // 设置分类
        repositoryService.setProcessDefinitionCategory(processDefinition.getId(), definitionDTO.getCategory());
        // 版本更新记录
        SysUserVO sysUserVO = UserContext.get();
        WorkFlowVersionRecord versionRecord = WorkFlowVersionRecord.builder().definitionName(definitionDTO.getFlowName()).definitionId(processDefinition.getId())
                .definitionKey(processDefinition.getKey()).version(processDefinition.getVersion())
                .operatorName(sysUserVO.getName()).operatorUid(sysUserVO.getId()).operatorAccount(sysUserVO.getLoginName()).build();
        versionRecordMapper.insert(versionRecord);
        // 获取开始事件
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        StartEvent startEvent = WorkFlowUtil.getStartEvent(bpmnModel);
        Preconditions.checkArgument(startEvent != null, "获取开始事件失败");

        Map<String, Object> formAuthJsonMap = JSONUtil.parseObj(definitionDTO.getFormAuthJson());
        if (!formAuthJsonMap.containsKey(startEvent.getId())) {
            throw new BusinessException("请先配置发起表单权限");
        }

        // 设置业务模块关联
        List<WorkFlowFormLink> links = formLinkService.list(Wrappers.<WorkFlowFormLink>lambdaQuery().eq(WorkFlowFormLink::getDefinitionKey, processDefinition.getKey()));
        if (CollectionUtils.isNotEmpty(links)) {
            WorkFlowFormLink link = links.get(0);
            List<Integer> menuIdList = links.stream().map(WorkFlowFormLink::getMenuId).collect(Collectors.toList());
            WorkFlowFormLinkDTO workFlowFormLinkDTO = WorkFlowFormLinkDTO.builder()
                    .definitionId(processDefinition.getId()).definitionKey(processDefinition.getKey()).formId(link.getFormId()).menuIds(menuIdList).build();
            formLinkService.delAllByDefinitionKey(processDefinition.getKey());
            formLinkService.addFormLink(workFlowFormLinkDTO);
        }

        // 添加表单权限
        formService.addFormAuth(processDefinition.getId(), processDefinition.getKey(), startEvent.getId(), definitionDTO.getFormId(), formAuthJsonMap);
    }

    @Override
    public void activeOrSuspend(Integer state, String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        Preconditions.checkArgument(processDefinition != null,"获取流程定义信息失败");
        switch (state) {
            case 1: repositoryService.activateProcessDefinitionById(processDefinition.getId(), true, null); break;
            case 2: repositoryService.suspendProcessDefinitionById(processDefinition.getId(), true, null); break;
            default: throw new BusinessException(30001, "state参数错误");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(List<String> deployIds) {
        if (CollectionUtils.isNotEmpty(deployIds)) {
            for (String deployId : deployIds) {
                int count = dataService.getCountByDeployId(deployId);
                Preconditions.checkArgument(count <= 0,"已有流程数据，不支持删除操作");
                // 删除流程数据
                dataService.delAllByDeployId(deployId);
                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
                if (processDefinition != null) {
                    // 删除流程定义表单权限
                    formService.delAllByDefinitionId(processDefinition.getId());
                }
                repositoryService.deleteDeployment(deployId, true);
            }
        }
    }

    @Override
    @SneakyThrows
    public String getXml(String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        Preconditions.checkArgument(processDefinition != null,"获取流程定义信息失败");
        InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
    }

    @Override
    public InputStream getImage(String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        Preconditions.checkArgument(processDefinition != null,"获取流程定义信息失败");
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        //输出为图片
        return diagramGenerator.generateDiagram(bpmnModel,"png", Collections.emptyList(), Collections.emptyList(),
                "宋体","宋体","宋体", null,
                1.0, false);
    }

    @Override
    public List<WorkFlowElementVO> getReturnTaskList(String xmlStr, String currentTaskKey) {
        List<WorkFlowElementVO> flowElements = new ArrayList<>();
        BpmnXMLConverter converter = new BpmnXMLConverter();
        XMLInputFactory factory = XMLInputFactory.newInstance();
        try (InputStream in = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8))) {
            XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(in);
            BpmnModel bpmnModel = converter.convertToBpmnModel(xmlStreamReader);
            List<UserTask> userTaskList = (List<UserTask>) WorkFlowUtil.getAllUserTaskEvent(bpmnModel);
            if (CollectionUtils.isEmpty(userTaskList)) {
                return flowElements;
            }

            FlowElement source = WorkFlowUtil.getFlowElementById(bpmnModel, currentTaskKey);
            userTaskList.remove((UserTask) source);

            for (UserTask userTask : userTaskList) {
                if (ExecutionGraphUtil.isReachable(bpmnModel.getMainProcess(), userTask, (FlowNode) source, new HashSet<>())) {
                    WorkFlowElementVO workFlowElementVO = WorkFlowElementVO.builder().taskKey(userTask.getId()).taskName(userTask.getName()).build();
                    flowElements.add(workFlowElementVO);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return flowElements;
    }
}
