package com.patzn.lims.workflow.service.impl;

import com.patzn.lims.core.api.PtAssert;
import com.patzn.lims.core.exception.PtException;
import com.patzn.lims.core.spring.SpringHelper;
import com.patzn.lims.core.web.Account;
import com.patzn.lims.workflow.FlowInfo;
import com.patzn.lims.workflow.FlowProcess;
import com.patzn.lims.workflow.FlowVariables;
import com.patzn.lims.workflow.FlowableHelper;
import com.patzn.lims.workflow.service.IFlowProcessService;
import org.apache.commons.collections.CollectionUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.form.TaskFormData;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 工作流执行服务
 * </p>
 *
 * @author patzn
 * @since 2017-09-25
 */
@Service
public class FlowProcessServiceImpl implements IFlowProcessService {
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
//    @Autowired
//    private HistoryService historyService;
    @Autowired
    private IdentityService identityService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private FormService formService;


    @Override
    public List<FlowProcess> saveTask(List<FlowInfo> flowTaskList, Account account) {
        if (CollectionUtils.isEmpty(flowTaskList)) {
            return null;
        }
        return flowTaskList.stream().map(flowInfo -> saveTask(account,
                flowInfo)).collect(Collectors.toList());
    }


    @Override
    public boolean activateProcess(String processDefinitionKey, Account account) {
        try {
            // 激活 流程，并且级联整个执行实例，时间 null 立即挂起
            repositoryService.activateProcessDefinitionByKey(processDefinitionKey, true, null, account.getCompanyIdStr());
        } catch (Throwable throwable) {
            PtAssert.fail("流程激活失败");
        }
        return true;
    }


    @Override
    public boolean suspendProcess(String processDefinitionKey, Account account) {
        try {
            // 挂起 流程，并且级联整个执行实例，时间 null 立即挂起
            repositoryService.suspendProcessDefinitionByKey(processDefinitionKey, true, null, account.getCompanyIdStr());
        } catch (Throwable throwable) {
            PtAssert.fail("流程挂起失败");
        }
        return true;
    }


    @Override
    public boolean deleteDeployment(String deploymentId, boolean cascade) {
        try {
            // 级联删除实例
            repositoryService.deleteDeployment(deploymentId, true);
        } catch (Throwable throwable) {
            PtAssert.fail("流程删除失败");
        }
        return true;
    }


    @Override
    public boolean deploy(Long companyId, MultipartFile file) {
        //将流程定义部署到Flowable引擎
        Deployment deployment = null;
        try {
            deployment = processEngine.getRepositoryService().createDeployment().tenantId(String.valueOf(companyId))
                    .addInputStream(file.getOriginalFilename(), file.getInputStream()).deploy();
        } catch (Exception e) {
            PtAssert.fail("上传失败");
        }
        return null != deployment;
    }


    @Override
    public FlowInfo notifyApplyer(String taskId, String remark) {
        FlowInfo flowInfo = getFlowInfo(taskId);
        if (null != flowInfo) {
            Map map = new HashMap<>(2);
            map.put("applyId", flowInfo.getApplyId());
            map.put("remark", remark);
            taskService.complete(taskId, map);
            return flowInfo;
        }
        return null;
    }


    /**
     * <p>
     * 保存工作流任务
     * </p>
     *
     * @param account  账户信息
     * @param flowInfo 工作流信息
     * @return
     */
    private FlowProcess saveTask(Account account, FlowInfo flowInfo) {
        //设置流程开启人
        Authentication.setAuthenticatedUserId(account.getUserIdStr());
        Map<String, Object> variables = flowInfo.getVariables();
        if (null == variables) {
            variables = new HashMap<>(2);
        }
        if (null != flowInfo) {
            variables.put(FlowInfo.FLOW_INFO, flowInfo);
            variables.put(FlowInfo.FLOW_NAME, flowInfo.getFlowName());
        }
        try {
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(flowInfo.getFlowType(),
                    flowInfo.getBusinessKey(), variables, account.getCompanyIdStr());
            return FlowableHelper.convertFlowProcess(processInstance);
        } catch (FlowableException flowableException) {
            throw new PtException("流程实例不存在，请部署工作流");
        }
    }


    /**
     * <p>
     * 获取流程信息
     * </p>
     *
     * @param taskId 任务 ID
     * @return
     */
    private FlowInfo getFlowInfo(String taskId) {
        FlowInfo flowInfo = null;
        Map map = taskService.getVariables(taskId);
        if (null != map) {
            flowInfo = (FlowInfo) map.get(FlowInfo.FLOW_INFO);
        }
        return flowInfo;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveFlowMemberShip(String groupId, String[] userIds) {
        for (String userId : userIds) {
            identityService.createMembership(userId, groupId);
        }
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeFlowMemberShip(String groupId, String[] userIds) {
        for (String userId : userIds) {
            identityService.deleteMembership(userId, groupId);
        }
        return true;
    }


    @Override
    public boolean claimTask(String taskId, String userId) {
        try {
            taskService.claim(taskId, userId);
        } catch (Throwable throwable) {
            return false;
        }
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean claimTaskByExecutionIds(List<String> executionIds, String userId) {
        if (CollectionUtils.isEmpty(executionIds)) {
            return false;
        }
        for (String executionId : executionIds) {
            Task task = taskService.createTaskQuery().executionId(executionId).singleResult();
            if (null == task) {
                return false;
            }
            claimTask(task.getId(), userId);
        }
        return true;
    }


    @Override
    public String fromKeyByTaskId(String taskId) {
        TaskFormData taskFormData = formService.getTaskFormData(taskId);
        StringBuilder formKey = new StringBuilder();
        formKey.append(taskFormData.getFormKey());
        FlowInfo flowInfo = getFlowInfo(taskId);
        if (null != flowInfo) {
            formKey.append(formKey.toString().contains("?") ? "&" : "?");
            formKey.append("id=").append(flowInfo.getId());
        }
        return formKey.toString();
    }


    @Override
    public String fromKeyByExecutionId(String executionId) {
        Task task = taskService.createTaskQuery().executionId(executionId).singleResult();
        if (null == task) {
            return null;
        }
        return fromKeyByTaskId(task.getId());
    }


    @Override
    public FlowInfo complete(String taskId) {
        try {
            FlowInfo flowInfo = getFlowInfo(taskId);
            taskService.complete(taskId);
            return flowInfo;
        } catch (Throwable throwable) {
            return null;
        }
    }


    @Override
    public FlowInfo complete(FlowVariables flowVariables) {
        try {
            // 流程执行 ID 获取执行任务
            if (null != flowVariables.getId()) {
                Task task = taskService.createTaskQuery().executionId(flowVariables.getId()).singleResult();
                PtAssert.fail(null == task, "该流程任务不存在");
                flowVariables.setTaskId(task.getId());
            }
            // 执行任务处理
            FlowInfo flowInfo = getFlowInfo(flowVariables.getTaskId());
            if (null == flowVariables.getVariables()) {
                taskService.complete(flowVariables.getTaskId());
            } else {
                taskService.complete(flowVariables.getTaskId(), flowVariables.getVariables());
            }
            return flowInfo;
        } catch (Throwable throwable) {
            PtAssert.fail(throwable.getMessage());
            return null;
        }
    }


    @Override
    public void diagramByTaskId(HttpServletResponse response, String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        try {
            //高亮线路id集合
            BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(task.getProcessDefinitionId());
            response.setCharacterEncoding("UTF-8");
            response.setContentType("image/png");
            OutputStream stream = response.getOutputStream();
            ProcessEngineConfiguration processEngineConfiguration = SpringHelper.getBean(ProcessEngineConfiguration.class);
            ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            BufferedImage image = diagramGenerator.generatePngImage(bpmnModel, 1.0);
            stream.write(FlowableHelper.createByteArrayForImage(image, "png"));
            stream.flush();
            stream.close();
        } catch (Exception e) {
            PtAssert.fail("流程图加载异常，稍后重试！");
        }
    }


    @Override
    public void diagramByExecutionId(HttpServletResponse response, String executionId) {
        Task task = taskService.createTaskQuery().executionId(executionId).singleResult();
        PtAssert.fail(null == task, "流程图加载异常，未找到可执行任务！");
        diagramByTaskId(response, task.getId());
    }
}
