package com.ningdatech.pmapi.todocenter.manage;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Sets;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.function.VUtils;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.pmapi.common.constant.CommonConst;
import com.ningdatech.pmapi.common.model.entity.ExcelExportWriter;
import com.ningdatech.pmapi.common.statemachine.event.ProjectStatusChangeEvent;
import com.ningdatech.pmapi.common.statemachine.util.StateMachineUtils;
import com.ningdatech.pmapi.common.util.ExcelDownUtil;
import com.ningdatech.pmapi.organization.model.entity.DingEmployeeInfo;
import com.ningdatech.pmapi.organization.model.entity.DingOrganization;
import com.ningdatech.pmapi.organization.service.IDingEmployeeInfoService;
import com.ningdatech.pmapi.organization.service.IDingOrganizationService;
import com.ningdatech.pmapi.projectdeclared.manage.DefaultDeclaredProjectManage;
import com.ningdatech.pmapi.projectdeclared.model.dto.DefaultDeclaredDTO;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.manage.ProjectLibManage;
import com.ningdatech.pmapi.projectlib.model.dto.ProjectDTO;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectApplication;
import com.ningdatech.pmapi.projectlib.model.req.ProjectListReq;
import com.ningdatech.pmapi.projectlib.model.vo.ProjectLibListItemVO;
import com.ningdatech.pmapi.projectlib.service.IProjectApplicationService;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.staging.enums.MsgTypeEnum;
import com.ningdatech.pmapi.staging.service.INdWorkNoticeStagingService;
import com.ningdatech.pmapi.todocenter.bean.entity.WorkNoticeInfo;
import com.ningdatech.pmapi.todocenter.model.dto.AdjustHandleDTO;
import com.ningdatech.pmapi.todocenter.model.vo.ProcessProgressDetailVo;
import com.ningdatech.pmapi.todocenter.enumeration.IsAppendProjectEnum;
import com.ningdatech.pmapi.todocenter.model.req.ProcessDetailReq;
import com.ningdatech.pmapi.todocenter.model.req.ToBeProcessedExportReq;
import com.ningdatech.pmapi.todocenter.model.req.ToBeProcessedReq;
import com.ningdatech.pmapi.todocenter.model.vo.ResToBeProcessedVO;
import com.ningdatech.pmapi.user.entity.UserInfo;
import com.ningdatech.pmapi.user.service.IUserInfoService;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import com.wflow.contants.HisProInsEndActId;
import com.wflow.exception.BusinessException;
import com.wflow.workflow.bean.dto.ProcessInstanceUserDto;
import com.wflow.workflow.bean.dto.ReqProcessHandlerDTO;
import com.wflow.workflow.bean.process.ProcessComment;
import com.wflow.workflow.bean.process.ProgressNode;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.vo.ProcessInstanceVo;
import com.wflow.workflow.bean.vo.ProcessProgressVo;
import com.wflow.workflow.bean.vo.ProcessTaskVo;
import com.wflow.workflow.enums.ProcessHandlerEnum;
import com.wflow.workflow.enums.ProcessStatusEnum;
import com.wflow.workflow.service.ProcessInstanceService;
import com.wflow.workflow.service.ProcessTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ActivityInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.ningdatech.pmapi.todocenter.constant.WorkNotice.*;
import static com.wflow.workflow.task.TriggerServiceTask.runtimeService;

/**
 * @author ZPF
 * @since 2023/02/19 16:09
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class TodoCenterManage {

    private final ProcessTaskService processTaskService;
    private final HistoryService historyService;
    private final IUserInfoService userInfoService;
    private final IProjectService projectService;

    private final ProjectLibManage projectLibManage;
    private final StateMachineUtils stateMachineUtils;

    private final IDingEmployeeInfoService dingEmployeeInfoService;
    private final IDingOrganizationService dingOrganizationService;
    private final ProcessInstanceService processInstanceService;
    private final INdWorkNoticeStagingService workNoticeStagingService;
    private final DefaultDeclaredProjectManage defaultDeclaredProjectManage;
    private final IProjectApplicationService projectApplicationService;


    /**
     * 待办中心待我处理项目列表查询
     *
     * @param param
     * @author zpf
     * @since 2023/02/18
     */
    public PageVo<ResToBeProcessedVO> todoProjectList(ToBeProcessedReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        //因为要解耦 不能把项目和工作流一起查
        //1.先查出用户工作流
        List<ProcessTaskVo> userTodoList = processTaskService.getUserTodoList(param.getProcessDefId(), String.valueOf(userId));
        if (CollUtil.isEmpty(userTodoList)) {
            return PageVo.empty();
        }
        List<String> instCodes = userTodoList.stream().map(ProcessTaskVo::getInstanceId).collect(Collectors.toList());

        Map<String, ProcessTaskVo> taskVoMap = userTodoList.stream().collect(Collectors.toMap(ProcessTaskVo::getInstanceId, v -> v));
        //2.再分页查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);
        PageVo<ProjectLibListItemVO> projectPage = projectLibManage.projectLibList(projectListReq);

        if (0L == projectPage.getTotal()) {
            return PageVo.empty();
        }

        List<ResToBeProcessedVO> resVos = projectPage.getRecords().stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            BeanUtils.copyProperties(d, res);
            res.setProjectId(d.getId());
            ProcessTaskVo taskVo = taskVoMap.get(d.getInstCode());
            res.setNodeId(taskVo.getTaskDefKey());
            res.setProcessStatusName(taskVo.getStatus());
            res.setProcessLaunchTime(d.getCreateOn());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, projectPage.getTotal());
    }

    /**
     * 待办中心列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    public void exportTodoList(HttpServletResponse response, ToBeProcessedExportReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        param.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
        param.setPageSize(CommonConst.EXPORT_PAGE_SIZE);

        //因为要解耦 不能把项目和工作流一起查
        //1.先查出用户工作流
        List<ProcessTaskVo> userTodoList = processTaskService.getUserTodoList(param.getProcessDefId(), String.valueOf(userId));
        List<String> instCodes = userTodoList.stream().map(ProcessTaskVo::getInstanceId).collect(Collectors.toList());

        //2.再查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = CollUtil.isEmpty(userTodoList) ? Collections.emptyList() : projectLibManage.projectList(projectListReq);
        PageVo<ProjectLibListItemVO> projectLibList = projectLibManage.projectLibList(projectListReq);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();
        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_待我处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_待我处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);
        List<String> sheetsNames = Lists.newArrayList(fileName);
        List<List<String>> exportDatas = ExcelDownUtil.getExportDatas(param.getExportOptionList(), projects);
        // sheet列表集合
        List<List<List<String>>> sheets = Lists.newArrayList();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);
    }

    /**
     * 流程处理相关操作
     *
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    public void handler(ReqProcessHandlerDTO param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();

        // 获取入参
        String processInstanceId = param.getInstanceId();
        Long projectId = param.getProjectId();
        VUtils.isTrue(Objects.isNull(processInstanceId)).throwMessage("获取流程实例ID失败!");
        VUtils.isTrue(Objects.isNull(projectId)).throwMessage("获取项目ID失败!");
        // 获取当前申报项目
        Project declaredProject = projectService.getOne(Wrappers.lambdaQuery(Project.class)
                .eq(Project::getInstCode, processInstanceId)
                .eq(Project::getId, projectId));
        VUtils.isTrue(Objects.isNull(projectId)).throwMessage("获取项目ID失败!");
        // 获取当前项目名称
        String projectName = declaredProject.getProjectName();
        // 获取当前项目状态
        Integer projectStatus = declaredProject.getStatus();
        // 获取当前未处理流程详情
        ProcessProgressVo currentInstanceDetail = processInstanceService.getProgressInstanceDetail(null, processInstanceId);
        // 获取当前未处理流程状态
        String currentProcessStatus = currentInstanceDetail.getStatus();

        // 获取当前要处理的流程实例
        HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        // 获取流程发起人信息
        String startUserId = instance.getStartUserId();
        UserInfo startUserInfo = userInfoService.getById(Long.valueOf(startUserId));
        // 获取流程定义名称
        String processDefinitionName = instance.getProcessDefinitionName();

        switch (param.getAction()) {
            // 通过
            case PASS:
                // 通过该任务，流程到下一审核人处
                processTaskService.handleTask(param, userId);
                // 获取流程通过后的流程实例
                HistoricProcessInstance newInstance = historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .singleResult();
                // 获取流程通过后当前流程详情
                ProcessProgressVo newInstanceDetail = processInstanceService.getProgressInstanceDetail(null, processInstanceId);
                // 获取流程通过后当前审核人信息，向其发送工作通知
                List<ProgressNode> newProgressInfo = newInstanceDetail.getProgressInfo();
                ProgressNode currentNode = newProgressInfo.get(newProgressInfo.size() - 1);
                UserInfo auditUserInfo = null;
                // 说明当前节点是子流程节点
                if (currentNode.getNodeType().name().equals(NodeTypeEnum.SUB.name())) {
                    List<ProgressNode> children = currentNode.getChildren();
                    // 获取子流程当前审核人节点
                    ProgressNode subCurrentNode = children.get(children.size() - 1);
                    auditUserInfo = userInfoService.getById(Long.valueOf(subCurrentNode.getUserId()));
                } else {
                    auditUserInfo = userInfoService.getById(Long.valueOf(currentNode.getUserId()));
                }

                // 如果流程状态是被退回状态，流程通过后，进入下一个审核人，
                // 当前通过审核人一定不是最后一个审核人(下一个审核人至多是最后一个)，更新流程状态为审核中
                if (ProcessStatusEnum.BE_BACKED.getDesc().equals(currentProcessStatus)) {
                    // 获取发送浙政钉工作通知必要信息
                    WorkNoticeInfo passWorkNoticeInfo = getSendWorkNoticeInfo(auditUserInfo);
                    String passMsg = String.format(PASS_MSG_TEMPLATE, passWorkNoticeInfo.getOrganizationName(), projectName);
                    passWorkNoticeInfo.setMsg(passMsg);
                    // 放入工作通知暂存表中，通过扫表异步发送
                    workNoticeStagingService.addByWorkNotice(passWorkNoticeInfo, MsgTypeEnum.AUDIT);
                    return;
                }
                // 若不是被退回状态，流程通过后，判断当前登录用户是不是最后一个审核人
                // 若当前登录用户是最后一个审批人，需更新流程状态为审核完成，项目状态到下个状态
                // 并向流程发起人发送浙政钉工作通知：【项目名称】已通过【流程名称】，请及时开始下一步操作。
                if (HisProInsEndActId.END.equals(newInstance.getEndActivityId())) {
                    switch (Objects.requireNonNull(ProjectStatusEnum.getValue(projectStatus))) {
                        // 当前项目状态是单位内部审核中
                        case UNDER_INTERNAL_AUDIT:
                            // 当前项目状态是预审中
                        case PRE_APPLYING:
                            // 当前项目状态是部门联审中
                        case DEPARTMENT_JOINT_REVIEW:
                            // 当前项目状态是方案评审中
                        case SCHEME_UNDER_REVIEW:
                            // 当前项目状态是终验审核中
                        case FINAL_ACCEPTANCE_IS_UNDER_REVIEW:
                            updatePassProjectStatus(userId, declaredProject);
                            break;
                        default:
                            throw new IllegalStateException("Unexpected value: " + projectStatus);
                    }
                    // 获取发送浙政钉工作通知必要信息
                    WorkNoticeInfo passWorkNoticeInfo2 = getSendWorkNoticeInfo(startUserInfo);
                    String passMsg2 = String.format(PASS_MSG_TEMPLATE2, projectName, processDefinitionName);
                    passWorkNoticeInfo2.setMsg(passMsg2);
                    // 放入工作通知暂存表中，通过扫表异步发送
                    workNoticeStagingService.addByWorkNotice(passWorkNoticeInfo2, MsgTypeEnum.PASS);
                } else {
                    // 若有下一个审核人(当前节点的用户)，
                    // 向其发送浙政钉工作通知：标题：审核任务 内容：【单位名称】的【项目名称】需要您审核。
                    // 获取发送浙政钉工作通知必要信息
                    if (Objects.isNull(auditUserInfo)) {
                        throw new BizException("审核人信息不存在!");
                    }
                    WorkNoticeInfo sendWorkNoticeInfo = getSendWorkNoticeInfo(auditUserInfo);
                    String msg = String.format(PASS_MSG_TEMPLATE, sendWorkNoticeInfo.getOrganizationName(), projectName);
                    sendWorkNoticeInfo.setMsg(msg);
                    // 放入工作通知暂存表中，通过扫表异步发送
                    workNoticeStagingService.addByWorkNotice(sendWorkNoticeInfo, MsgTypeEnum.AUDIT);
                }
                break;
            // 盖章并通过
            case SEAL_PASS:
                // TODO 调用盖章接口，返回盖章后的文件流
                // 通过该任务，流程到下一审核人处
                processTaskService.handleTask(param, userId);
                break;
            // 驳回
            case REJECT:
                // 驳回该任务，中止流程并使项目进入对应状态,给项目创建人、流程发起人发送浙政钉工作通知：
                // 【项目名称】的【流程名称】被驳回，请及时处理。
                processTaskService.handleTask(param, userId);
                // 更新项目状态和流程状态
                updateRejectProjectStatus(userId, declaredProject);
                // 获取发送浙政钉工作通知必要信息
                WorkNoticeInfo rejectWorkNoticeInfo = getSendWorkNoticeInfo(startUserInfo);
                String rejectMsg = String.format(REJECT_MSG_TEMPLATE, projectName, processDefinitionName);
                rejectWorkNoticeInfo.setMsg(rejectMsg);
                // 放入工作通知暂存表中，通过扫表异步发送
                workNoticeStagingService.addByWorkNotice(rejectWorkNoticeInfo, MsgTypeEnum.REJECTED);
                break;
            // 退回
            case BACK:
                // 退回该任务
                processTaskService.handleTask(param, userId);
                // 给项目创建人、流程发起人发送浙政钉工作通知：【项目名称】的【流程名称】被退回，请及时处理。
                // 获取发送浙政钉工作通知必要信息
                WorkNoticeInfo backWorkNoticeInfo = getSendWorkNoticeInfo(startUserInfo);
                String backMsg = String.format(BACK_MSG_TEMPLATE, projectName, processDefinitionName);
                backWorkNoticeInfo.setMsg(backMsg);
                // 放入工作通知暂存表中，通过扫表异步发送
                workNoticeStagingService.addByWorkNotice(backWorkNoticeInfo, MsgTypeEnum.BACKED);
                break;
            // 撤回(流程发起人和当前流程审核人的前一个审核人操作)
            case WITHDRAW:
                // 当前登录用户是流程发起人
                if (userId.equals(Long.valueOf(startUserId))) {
                    processTaskService.handleTask(param, userId);
                    // 若是流程发起人点击撤回，项目回到上一个状态，需调用状态机更新项目状态,流程状态更新为审核通过
                    switch (Objects.requireNonNull(ProjectStatusEnum.getValue(projectStatus))) {
                        // 当前项目状态是单位内部审核中
                        case UNDER_INTERNAL_AUDIT:
                            // 当前项目状态是预审中
                        case PRE_APPLYING:
                            // 当前项目状态是部门联审中
                        case DEPARTMENT_JOINT_REVIEW:
                            // 当前项目状态是方案评审中
                        case SCHEME_UNDER_REVIEW:
                            // 当前项目状态是终验审核中
                        case FINAL_ACCEPTANCE_IS_UNDER_REVIEW:
                            updateWithdrawProjectStatus(userId, declaredProject);
                            break;
                        default:
                            throw new IllegalStateException("Unexpected value: " + projectStatus);
                    }
                } else {
                    // 当前登录用户不是流程发起人
                    List<ProgressNode> currentProgressInfo = currentInstanceDetail.getProgressInfo();
                    // 获取当前工作流任务前一个审核人信息
                    ProgressNode beforeProgressNode = currentProgressInfo.get(currentProgressInfo.size() - 2);
                    ProcessInstanceUserDto beforeUser = beforeProgressNode.getUser();
                    // 获取当前当前工作流任务当前审核人信息
                    ProgressNode currentProgressNode = currentProgressInfo.get(currentProgressInfo.size() - 1);
                    ProcessInstanceUserDto currentUser = currentProgressNode.getUser();
                    // 判断当前工作流任务前一个审核人的部门和当前登录用户的部门是否是同一个，如果是同一个才可以撤回，否则抛出异常
                    boolean orgFlag = currentUser.getOrgCode().equals(beforeUser.getOrgCode());
                    boolean userFlag = beforeUser.getUserId().equals(String.valueOf(userId));
                    if (!orgFlag) {
                        throw new BizException("下一个审核人和您不是同一个部门,无法撤回!");
                    } else if (!userFlag) {
                        throw new BizException("当前登录用户无法进行撤回操作!");
                    } else {
                        processTaskService.handleTask(param, userId);
                    }
                }
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + param.getAction());
        }
    }


    /**
     * 当为驳回操作时，更新项目表中的项目状态
     *
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updateRejectProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer status = declaredProject.getStatus();
        // 根据当前状态获取对应的通过事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusRejectEvent(status);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
        projectService.updateById(declaredProject);
    }


    /**
     * 获取发送浙政钉工作通知的信息
     *
     * @param auditUserInfo
     * @return com.ningdatech.pmapi.todocenter.bean.entity.WorkNoticeInfo
     * @author CMM
     * @since 2023/02/15 14:04
     */
    public WorkNoticeInfo getSendWorkNoticeInfo(UserInfo auditUserInfo) {
        WorkNoticeInfo workNoticeInfo = new WorkNoticeInfo();
        Long accountId = auditUserInfo.getAccountId();
        if (Objects.isNull(accountId)) {
            throw new BizException("该用户没有录入浙政钉信息!");
        }
        workNoticeInfo.setAccountId(accountId);
        // 根据浙政钉用户ID获取部门code
        DingEmployeeInfo employeeInfo = dingEmployeeInfoService.getOne(Wrappers.lambdaQuery(DingEmployeeInfo.class)
                .eq(DingEmployeeInfo::getAccountId, accountId).last("limit 1"));
        String organizationCode = employeeInfo.getOrganizationCode();
        workNoticeInfo.setOrganizationCode(organizationCode);
        // 根据部门code获取部门名称
        DingOrganization dingOrganization = dingOrganizationService.getOne(Wrappers.lambdaQuery(DingOrganization.class)
                .eq(DingOrganization::getOrganizationCode, organizationCode));
        String organizationName = dingOrganization.getOrganizationName();
        workNoticeInfo.setOrganizationName(organizationName);
        // 构建唯一的消息ID
        String bizMsgId = "ZD_WORK_NOTICE_" + StrUtil.UNDERLINE + organizationCode + StrUtil.UNDERLINE
                + organizationName + accountId + StrUtil.UNDERLINE + System.currentTimeMillis();
        workNoticeInfo.setBizMsgId(bizMsgId);
        String receiverUserId = String.valueOf(accountId);
        workNoticeInfo.setReceiverUserId(receiverUserId);
        return workNoticeInfo;
    }

    /**
     * 当为通过操作时，更新项目表中项目状态
     *
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updatePassProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer status = declaredProject.getStatus();
        // 根据当前状态获取对应的通过事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusPassEvent(status);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
        projectService.updateById(declaredProject);
    }

    /**
     * 获取流程发起节点的用户ID
     *
     * @param rootNode          根节点
     * @param processInstanceId
     * @return java.lang.String
     * @author CMM
     * @since 2023/02/02
     */
    private String getRootUserId(FlowNode rootNode, String processInstanceId) {
        String rootUserId = null;
        // 输出连线
        List<SequenceFlow> outgoingFlows = rootNode.getOutgoingFlows();
        // 遍历返回下一个节点信息
        for (SequenceFlow currentOutgoingFlow : outgoingFlows) {
            // 类型自己判断
            FlowElement targetFlowElement = currentOutgoingFlow.getTargetFlowElement();
            // TODO 若要会签需判断候选人
            // 发起事件
            if (targetFlowElement instanceof StartEvent) {
                String actId = targetFlowElement.getId();
                ActivityInstance activityInstance = runtimeService.createActivityInstanceQuery()
                        .processInstanceId(processInstanceId).activityId(actId).singleResult();
                String executionId = activityInstance.getExecutionId();
                rootUserId = runtimeService.getVariable(executionId, "initiator", String.class);
                break;
            }
        }
        return rootUserId;

    }

    /**
     * 获取当前节点的下一个节点的审核用户ID
     *
     * @param currentNode       当前节点
     * @param processInstanceId
     * @return java.lang.String 下一个节点的审核用户ID
     * @author CMM
     * @since 2023/02/02
     */
    private String getNextUserId(FlowNode currentNode, String processInstanceId) {
        String nextUserId = null;
        // 输出连线
        List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows();
        // 遍历返回下一个节点信息
        for (SequenceFlow currentOutgoingFlow : outgoingFlows) {
            // 类型自己判断
            FlowElement targetFlowElement = currentOutgoingFlow.getTargetFlowElement();
            // 如果下个审批节点为结束节点,那么跳过该节点
            if (targetFlowElement instanceof EndEvent) {
                continue;
            }
            // TODO 若要会签需判断候选人
            // 用户任务
            if (targetFlowElement instanceof UserTask) {
                String actId = targetFlowElement.getId();
                ActivityInstance activityInstance = runtimeService.createActivityInstanceQuery()
                        .processInstanceId(processInstanceId).activityId(actId).singleResult();
                String executionId = activityInstance.getExecutionId();
                nextUserId = runtimeService.getVariable(executionId, "assignee", String.class);
                break;
            }
        }
        return nextUserId;
    }

    /**
     * 当为撤回操作时，更新项目表中的项目状态为前一个状态
     *
     * @param userId
     * @param declaredProject
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    private void updateWithdrawProjectStatus(Long userId, Project declaredProject) {
        // 获取当前流程项目状态
        Integer projectStatusSecond = declaredProject.getStatus();
        // 根据当前状态获取对应的撤回事件
        ProjectStatusChangeEvent event = stateMachineUtils.getProjectStatusWithdrawEvent(projectStatusSecond);
        try {
            stateMachineUtils.execute(declaredProject, event);
            declaredProject.setUpdateOn(LocalDateTime.now());
            declaredProject.setUpdateBy(userId);
        } catch (Exception e) {
            throw new BizException("状态机执行失败!");
        }
    }

    /**
     * 查询流程表单数据及审批的进度步骤
     *
     * @param request 请求参数
     * @return 流程进度及表单详情
     */
    public ProcessProgressDetailVo getProcessDetail(ProcessDetailReq request) {
        String instanceId = request.getInstanceId();
        String nodeId = request.getNodeId();
        Long projectId = request.getProjectId();
        ProcessProgressVo progressInstanceDetail = processInstanceService.getProgressInstanceDetail(nodeId, instanceId);

        List<ProgressNode> progressInfo = progressInstanceDetail.getProgressInfo();

        Set<String> userSet = Sets.newHashSet();
        progressInfo.forEach(node -> {
            if (CollUtil.isNotEmpty(node.getChildren())) {
                for (ProgressNode innerNode : node.getChildren()) {
                    userSet.add(innerNode.getUserId());
                }
            } else {
                userSet.add(node.getUserId());
            }
        });
        Map<String, ProcessInstanceUserDto> userMap = userInfoService.getUserMapByIds(userSet);

        // 装配节点审核人员信息
        buildUser(progressInfo, userMap);

        ProcessProgressDetailVo res = new ProcessProgressDetailVo();
        res.setProcessProgressVo(progressInstanceDetail);
        res.setStatus(progressInstanceDetail.getStatus());
        res.setProjectId(projectId);
        return res;
    }

    private void buildUser(List<ProgressNode> progressInfo, Map<String, ProcessInstanceUserDto> userMap) {
        for (ProgressNode progressNode : progressInfo) {
            progressNode.setUser(userMap.get(progressNode.getUserId()));
            if (CollUtil.isNotEmpty(progressNode.getChildren())) {
                buildUser(progressNode.getChildren(), userMap);
            }
        }
    }

    /**
     * 待办中心我已处理项目列表查询
     *
     * @param param
     * @author CMM
     * @since 2023/02/03
     */
    public PageVo<ResToBeProcessedVO> idoList(ToBeProcessedReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();

        // 查出用户已处理工作流
        List<ProcessTaskVo> userIdoList = processTaskService.getUserIdoList(param.getProcessDefId(), String.valueOf(userId));
        if (CollUtil.isEmpty(userIdoList)) {
            return PageVo.empty();
        }
        List<String> instCodes = userIdoList.stream().map(ProcessTaskVo::getInstanceId).collect(Collectors.toList());
        Map<String, ProcessTaskVo> taskVoMap = userIdoList.stream().collect(Collectors.toMap(ProcessTaskVo::getInstanceId, v -> v));


        // 分页查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);

        PageVo<ProjectLibListItemVO> projectPage = projectLibManage.projectLibList(projectListReq);
        if (0L == projectPage.getTotal()) {
            return PageVo.empty();
        }
        List<ResToBeProcessedVO> resVos = projectPage.getRecords().stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            BeanUtils.copyProperties(d, res);
            res.setProjectId(d.getId());
            ProcessTaskVo taskVo = taskVoMap.get(d.getInstCode());
            res.setProcessStatusName(taskVo.getStatus());
            res.setProcessLaunchTime(d.getCreateOn());
            res.setProcessHandleTime(d.getUpdateOn());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, projectPage.getTotal());
    }

    /**
     * 导出我已处理项目列表
     *
     * @param response 返回数据流
     * @param param    请求参数
     * @return void
     * @author CMM
     * @since 2023/02/06
     */
    public void idoExport(HttpServletResponse response, ToBeProcessedExportReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();

        param.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
        param.setPageSize(CommonConst.EXPORT_PAGE_SIZE);

        // 查询用户已处理工作流
        List<ProcessTaskVo> userIdoList = processTaskService.getUserIdoList(param.getProcessDefId(), String.valueOf(userId));
        List<String> instCodes = userIdoList.stream().map(ProcessTaskVo::getInstanceId).collect(Collectors.toList());

        // 查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = CollUtil.isEmpty(userIdoList) ? Collections.emptyList() :
                projectLibManage.projectList(projectListReq);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我已处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我已处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        List<List<String>> exportDatas = ExcelDownUtil.getExportDatas(param.getExportOptionList(), projects);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 查询我提交的项目列表
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/06
     */
    public PageVo<ResToBeProcessedVO> mySubmittedList(ToBeProcessedReq param) {

        // 获取登录用户ID
        Long startUserId = LoginUserUtil.getUserId();
        // 查出登录用户已提交工作流
        List<ProcessInstanceVo> userSubmittedList = processInstanceService.getUserSubmittedList(String.valueOf(startUserId), param.getProcessDefId(), null);
        Map<String, ProcessInstanceVo> instanceVoMap = userSubmittedList.stream()
                .collect(Collectors.toMap(ProcessInstanceVo::getInstanceId, v -> v));
        if (CollUtil.isEmpty(userSubmittedList)) {
            return PageVo.empty();
        }
        List<String> instCodes = userSubmittedList.stream().map(ProcessInstanceVo::getInstanceId).collect(Collectors.toList());

        // 分页查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);

        PageVo<ProjectLibListItemVO> projectPage = projectLibManage.projectLibList(projectListReq);
        if (0L == projectPage.getTotal()) {
            return PageVo.empty();
        }

        List<ResToBeProcessedVO> resVos = projectPage.getRecords().stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            BeanUtils.copyProperties(d, res);
            res.setProjectId(d.getId());
            ProcessInstanceVo instanceVo = instanceVoMap.get(d.getInstCode());
            res.setProcessStatusName(instanceVo.getStatus());
            res.setProcessLaunchTime(d.getCreateOn());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, projectPage.getTotal());
    }

    /**
     * 待办中心-我发起的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportMySubmittedList(HttpServletResponse response, ToBeProcessedExportReq param) {

        // 获取登录用户ID
        long userId = LoginUserUtil.getUserId();

        param.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
        param.setPageSize(CommonConst.EXPORT_PAGE_SIZE);
        // 查询用户已处理工作流
        List<ProcessInstanceVo> userSubmittedList = processInstanceService.getUserSubmittedList(String.valueOf(userId), param.getProcessDefId(), null);
        List<String> instCodes = userSubmittedList.stream().map(ProcessInstanceVo::getInstanceId).collect(Collectors.toList());

        // 查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = CollUtil.isEmpty(userSubmittedList) ? Collections.emptyList() :
                projectLibManage.projectList(projectListReq);
        projectLibManage.projectList(projectListReq);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我发起的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我发起的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);
        // 表体行数据集合
        List<List<String>> exportDatas = ExcelDownUtil.getExportDatas(param.getExportOptionList(), projects);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 待办中心-抄送我的项目列表查询
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.ningdatech.pmapi.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/11
     */
    public PageVo<ResToBeProcessedVO> ccmeList(ToBeProcessedReq param) {
        // 获取当前登录用户ID
        Long userId = LoginUserUtil.getUserId();

        // 查询抄送登录用户的工作流
        List<ProcessInstanceVo> ccMeList = processInstanceService.getCcMeList(param.getProcessDefId(), String.valueOf(userId));
        if (CollUtil.isEmpty(ccMeList)) {
            return PageVo.empty();
        }
        List<String> instCodes = ccMeList.stream().map(ProcessInstanceVo::getInstanceId).collect(Collectors.toList());
        Map<String, ProcessInstanceVo> instanceVoMap = ccMeList.stream()
                .collect(Collectors.toMap(ProcessInstanceVo::getInstanceId, v -> v));

        // 分页查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);

        PageVo<ProjectLibListItemVO> projectPage = projectLibManage.projectLibList(projectListReq);
        if (0L == projectPage.getTotal()) {
            return PageVo.empty();
        }

        List<ResToBeProcessedVO> resVos = projectPage.getRecords().stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            BeanUtils.copyProperties(d, res);
            ProcessInstanceVo instanceVo = instanceVoMap.get(d.getInstCode());
            res.setNodeId(instanceVo.getNodeId());
            res.setProjectId(d.getId());
            res.setProcessStatusName(instanceVo.getStatus());
            res.setProcessLaunchTime(d.getCreateOn());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, projectPage.getTotal());
    }

    /**
     * 待办中心-抄送我的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportCcMeProjectList(HttpServletResponse response, ToBeProcessedExportReq param) {

        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();

        param.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
        param.setPageSize(CommonConst.EXPORT_PAGE_SIZE);

        // 查询抄送登录用户的工作流
        List<ProcessInstanceVo> ccMeList = processInstanceService.getCcMeList(param.getProcessDefId(), String.valueOf(userId));
        List<String> instCodes = ccMeList.stream().map(ProcessInstanceVo::getInstanceId).collect(Collectors.toList());

        // 查询项目信息
        ProjectListReq projectListReq = new ProjectListReq();
        projectListReq.setInstCodes(instCodes);
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = CollUtil.isEmpty(ccMeList) ? Collections.emptyList() : projectLibManage.projectList(projectListReq);

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_抄送我的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_抄送我的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);
        // 表体行数据集合
        List<List<String>> exportDatas = ExcelDownUtil.getExportDatas(param.getExportOptionList(), projects);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 被退回项目内容调整并重新通过
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String adjustmentAndHandle(AdjustHandleDTO dto) {
        ProjectDTO projectDto = dto.getProjectInfo();
        Project projectInfo = projectService.getById(projectDto.getId());

        VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("调整失败 此项目不存在!");

        //项目名称去重
        defaultDeclaredProjectManage.checkDuplication(projectDto);

        //修改项目内容
        if(!modifyProject(projectDto)){
            throw new BusinessException("调整项目失败!");
        }

        //最后去审批(通过)当前退回节点的审核任务
        ReqProcessHandlerDTO handlerDTO = new ReqProcessHandlerDTO();
        handlerDTO.setProjectId(dto.getProjectId());
        handlerDTO.setInstanceId(dto.getInstanceId());
        handlerDTO.setTaskId(dto.getTaskId());
        handlerDTO.setAction(ProcessHandlerEnum.PASS);
        handler(handlerDTO);
        return dto.getInstanceId();

    }

    private Boolean modifyProject(ProjectDTO projectDto) {
        //先修改项目信息
        Project project = new Project();
        BeanUtils.copyProperties(projectDto,project);
        project.setUpdateOn(LocalDateTime.now());
        if(!projectService.updateById(project)){
            throw new BusinessException("项目调整失败");
        }
        //再修改应用信息
        if(CollUtil.isNotEmpty(projectDto.getApplicationList())){
            //采取批量删除 批量添加的方式
            projectApplicationService.remove(Wrappers.lambdaQuery(ProjectApplication.class)
                    .eq(ProjectApplication::getProjectId,project.getId()));
            //批量添加
            List<ProjectApplication> applications = projectDto.getApplicationList().stream().map(application -> {
                ProjectApplication projectApplication = new ProjectApplication();
                BeanUtils.copyProperties(application, projectApplication);
                projectApplication.setProjectId(project.getId());
                return projectApplication;
            }).collect(Collectors.toList());
            projectApplicationService.saveBatch(applications);
        }
        return Boolean.TRUE;
    }
}
