package com.hz.pm.api.todocenter.manage;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.hz.pm.api.common.model.constant.BizConst;
import com.hz.pm.api.common.model.constant.ProjectDeclareConst;
import com.hz.pm.api.common.statemachine.util.StateMachineUtil;
import com.hz.pm.api.filemanage.model.entity.ProjectApplyBorrow;
import com.hz.pm.api.filemanage.service.INdProjectApplyBorrowService;
import com.hz.pm.api.projectdeclared.converter.ApplicationConverter;
import com.hz.pm.api.projectdeclared.manage.DeclaredProjectManage;
import com.hz.pm.api.projectdeclared.model.dto.ProjectDraftSaveDTO;
import com.hz.pm.api.projectdeclared.model.entity.ProjectConstructionSuggestions;
import com.hz.pm.api.projectdeclared.service.IConstrctionSuggestionsService;
import com.hz.pm.api.projectlib.model.dto.ProjectApplicationDTO;
import com.hz.pm.api.projectlib.model.dto.ProjectDTO;
import com.hz.pm.api.projectlib.model.entity.Project;
import com.hz.pm.api.projectlib.model.entity.ProjectApplication;
import com.hz.pm.api.projectlib.model.entity.ProjectDelayApply;
import com.hz.pm.api.projectlib.model.entity.ProjectInst;
import com.hz.pm.api.projectlib.model.enumeration.InstTypeEnum;
import com.hz.pm.api.projectlib.model.enumeration.ProjectStatusEnum;
import com.hz.pm.api.projectlib.service.INdProjectDelayApplyService;
import com.hz.pm.api.projectlib.service.IProjectApplicationService;
import com.hz.pm.api.projectlib.service.IProjectInstService;
import com.hz.pm.api.projectlib.service.IProjectService;
import com.hz.pm.api.provincial.service.IJoinReviewProvincialBureauService;
import com.hz.pm.api.staging.enums.MsgTypeEnum;
import com.hz.pm.api.staging.service.IProjectStagingService;
import com.hz.pm.api.sys.manage.NoticeManage;
import com.hz.pm.api.todocenter.constant.TodoCenterConst;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.file.service.FileService;
import com.wflow.contants.HisProInsEndActId;
import com.wflow.workflow.bean.process.ProgressNode;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.vo.ProcessProgressVo;
import com.wflow.workflow.enums.ProcessHandlerEnum;
import com.wflow.workflow.service.ProcessInstanceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.hz.pm.api.todocenter.constant.WorkNoticeConstant.*;

/**
 * @Classname HandlerManage
 * @Description
 * @Date 2023/4/2 14:25
 * @Author PoffyZhang
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class HandlerManage {

    private final RuntimeService runtimeService;

    private final TaskService taskService;
    private final HistoryService historyService;
    private final IProjectService projectService;
    private final StateMachineUtil stateMachineUtil;
    private final ProcessInstanceService processInstanceService;
    private final IProjectApplicationService projectApplicationService;
    private final IProjectStagingService projectStagingService;
    private final IProjectInstService projectInstService;
    private final NoticeManage noticeManage;
    private final DeclaredProjectManage declaredProjectManage;
    private final FileService fileService;
    private final IJoinReviewProvincialBureauService joinReviewProvincialBureauService;
    private final INdProjectDelayApplyService projectDelayApplyService;
    private final INdProjectApplyBorrowService projectApplyBorrowService;

    private final IConstrctionSuggestionsService constrctionSuggestionsService;

    @Value("${spring.profiles.active}")
    private String active;


    /**
     * 审核通过后 所处理的逻辑
     *
     * @param declaredProject
     * @param instance
     */
    public void afterPassTodo(Project declaredProject, HistoricProcessInstance instance) {
        Long userId = LoginUserUtil.getUserId();
        // 获取流程通过后的流程实例
        HistoricProcessInstance newInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instance.getId())
                .singleResult();
        // 获取当前流程实例类型
        String instanceId = newInstance.getId();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId)
                .last(BizConst.LIMIT_1));
        Integer instType = projectInst.getInstType();
        InstTypeEnum instTypeEnum = InstTypeEnum.getByCode(instType);
        if (Objects.isNull(instTypeEnum)) {
            throw new BizException("当前审批流类型不存在,流程类型code:" + instType);
        }

        // 获取流程通过后当前流程详情
        ProcessProgressVo newInstanceDetail = processInstanceService.getProgressInstanceDetail(null, instance.getId());
        // 获取流程通过后当前审核人信息，向其发送工作通知
        // 会签/或签会有多个审核人
        List<String> currentEmployeeCodeList = Lists.newArrayList();
        List<ProgressNode> newProgressInfo = newInstanceDetail.getProgressInfo();
        ProgressNode currentNode = newProgressInfo.get(newProgressInfo.size() - 1);

        // 说明当前节点是子流程节点
        if (currentNode.getNodeType().name().equals(NodeTypeEnum.SUB.name())) {
            List<ProgressNode> children = currentNode.getChildren();
            // 获取子流程当前审核人节点
            ProgressNode subCurrentNode = CollUtil.isEmpty(children) ? new ProgressNode() : children.get(children.size() - 1);
            // 获取节点ID相同地审核节点
            List<ProgressNode> nodeList = children.stream()
                    .filter(c -> subCurrentNode.getNodeId().equals(c.getNodeId()))
                    .collect(Collectors.toList());
            List<String> userIdList = nodeList.stream().map(ProgressNode::getUserId).collect(Collectors.toList());
            currentEmployeeCodeList.addAll(userIdList);
        } else {
            List<ProgressNode> nodeList = newProgressInfo.stream()
                    .filter(c -> currentNode.getNodeId().equals(c.getNodeId()))
                    .collect(Collectors.toList());
            List<String> userIdList = nodeList.stream().map(ProgressNode::getUserId).collect(Collectors.toList());
            currentEmployeeCodeList.addAll(userIdList);
        }

        // 流程通过后，判断当前登录用户是不是最后一个审核人
        // 若当前登录用户是最后一个审批人，需更新流程状态为审核完成，项目状态到下个状态
        // 并向流程发起人发送浙政钉工作通知：【项目名称】已通过【流程名称】，请及时开始下一步操作。
        if (HisProInsEndActId.END.equals(newInstance.getEndActivityId())) {
            // 如果是申请延期和申请借阅审批流程，不走状态机
            if (InstTypeEnum.APPLY_DELAY.eq(instType) || InstTypeEnum.APPLY_BORROW.eq(instType)) {
                switch (instTypeEnum) {
                    case APPLY_DELAY:
                        // 保存延期申请记录,更新项目建设周期和计划验收时间
                        updateProjectDelayApplyInfo(declaredProject, instanceId);
                        break;
                    case APPLY_BORROW:
                        // 更新申请借阅状态为成功
                        updateProjectApplyBorrowInfo(declaredProject, instanceId);
                        break;
                    default:
                        throw new BizException("传入实例类型错误: " + instTypeEnum);
                }
            } else {
                switch (Objects.requireNonNull(ProjectStatusEnum.match(declaredProject.getStatus()))) {
                    // 当前项目状态是预审中
                    case PRE_APPLYING:
                        //先修改项目状态
                        updatePassProjectStatus(userId, declaredProject);
                        //然后入库暂存库
                        projectStagingService.addByProject(declaredProject, "暂存入库 待提交部门联审");
                        break;
                    // 当前项目状态是部门联审中
                    case DEPARTMENT_JOINT_REVIEW:
                    case UNDER_INTERNAL_AUDIT:
                        // 当前项目状态是方案评审中
                    case FINAL_ACCEPTANCE_IS_UNDER_REVIEW:
                        updatePassProjectStatus(userId, declaredProject);
                        break;
                    case SCHEME_UNDER_REVIEW:
                        // 当前项目状态是终验审核中
                        updatePassProjectStatus(userId, declaredProject);
                        // 判断是否 保存建设建议信息
                        saveContructionSuggestionInfo(instance.getId(), declaredProject);
                        break;
                    default:
                        throw new BizException("传入项目状态错误： " + declaredProject.getStatus());
                }
            }
            //发送消息
            noticeManage.sendNotice(userId, declaredProject, instance.getProcessDefinitionName(), PASS_MSG_TEMPLATE2, MsgTypeEnum.PROJECT_REVIEW_PASS);
        } else {
            // 若有下一个审核人(当前节点的用户)，会签/或签会有多个
            // 向其发送浙政钉工作通知：标题：审核任务 内容：【单位名称】的【项目名称】需要您审核。
            // 获取发送浙政钉工作通知必要信息
            for (String currentEmployeeCode : currentEmployeeCodeList) {
                if (Objects.isNull(currentEmployeeCode)) {
                    throw new BizException("审核人信息不存在!");
                }
                //发送消息
                noticeManage.sendNotice(userId, declaredProject, instance.getProcessDefinitionName(),
                        PASS_MSG_TEMPLATE, MsgTypeEnum.PROJECT_REVIEW);
            }
        }
    }

    /**
     * 去保存 建设建议信息
     *
     * @param instanceId
     * @param project
     */
    private void saveContructionSuggestionInfo(String instanceId, Project project) {
        ProjectConstructionSuggestions pcs = constrctionSuggestionsService.getOne(Wrappers.lambdaQuery(ProjectConstructionSuggestions.class)
                .eq(ProjectConstructionSuggestions::getProjectCode, project.getProjectCode())
                .eq(ProjectConstructionSuggestions::getInstanceId, instanceId)
                .last(BizConst.LIMIT_1));
        if (Objects.isNull(pcs)) {
            log.info("没有该流程的 建设 建议信息");
            return;
        }

        project.setProposeTotalInvest(pcs.getProposeTotalInvest());
        project.setProposeAnnualBudget(pcs.getProposeAnnualBudget());
        project.setProposeAttachFiles(pcs.getProposeAttachFiles());
        projectService.updateById(project);
    }

    public void updateProjectApplyBorrowInfo(Project declaredProject, String instanceId) {
        // 获取申请借阅信息
        ProjectApplyBorrow projectApplyBorrow = projectApplyBorrowService.getOne(Wrappers.lambdaQuery(ProjectApplyBorrow.class)
                .eq(ProjectApplyBorrow::getProjectId, declaredProject.getId())
                .eq(ProjectApplyBorrow::getInstanceId, instanceId));
        // 更新项目借阅信息为成功
        projectApplyBorrow.setIsSuccess(Boolean.TRUE);
        projectApplyBorrow.setUpdateOn(LocalDateTime.now());
        projectApplyBorrow.setUpdateBy(LoginUserUtil.getUserId());
        projectApplyBorrowService.updateById(projectApplyBorrow);
    }

    public void updateProjectDelayApplyInfo(Project declaredProject, String instanceId) {
        // 获取延期申请填写的相关信息
        ProjectDelayApply delayApply = projectDelayApplyService.getOne(Wrappers.lambdaQuery(ProjectDelayApply.class)
                .eq(ProjectDelayApply::getProjectId, declaredProject.getId())
                .eq(ProjectDelayApply::getInstanceId, instanceId));
        delayApply.setSuccess(Boolean.TRUE);
        delayApply.setUpdateOn(LocalDateTime.now());
        projectDelayApplyService.updateById(delayApply);
        // 获取项目的立项批复时间和建设周期
        LocalDateTime approvalDate = declaredProject.getApprovalDate();
        String buildCycle = declaredProject.getBuildCycle();
        // 重新计算建设周期、验收时间
        String constructCycle = String.valueOf(Integer.parseInt(buildCycle) + delayApply.getDelayTime());
        declaredProject.setBuildCycle(constructCycle);
        Long constructMonth = Long.valueOf(constructCycle);
        String acceptTime = NdDateUtils.format(approvalDate.plusMonths(constructMonth), DatePattern.NORM_DATETIME_MINUTE_PATTERN);
        LocalDateTime time = LocalDateTime.parse(acceptTime, DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_MINUTE_PATTERN));
        declaredProject.setPlanAcceptanceTime(time);
        declaredProject.setInstCode(instanceId);
        declaredProject.setApplyDelayFile(delayApply.getDelayApplyFile());
        declaredProject.setApplyDelayReason(delayApply.getDelayApplyReason());
        //增加总延期的月份
        declaredProject.setApplyDelayMonths(Objects.nonNull(declaredProject.getApplyDelayMonths()) ?
                declaredProject.getApplyDelayMonths() + delayApply.getDelayTime() : delayApply.getDelayTime());
        projectService.updateById(declaredProject);
    }


    /**
     * 驳回后 所处理的逻辑
     *
     * @param declaredProject
     * @param instance
     */
    public void afterRejectTodo(Project declaredProject, HistoricProcessInstance instance) {
        Long userId = LoginUserUtil.getUserId();
        // 获取当前流程实例类型
        String instanceId = declaredProject.getInstCode();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId));
        Integer instType = projectInst.getInstType();
        // 审批流程不是申请延期和申请借阅,需调用状态机
        if (!InstTypeEnum.APPLY_DELAY.getCode().equals(instType) && !InstTypeEnum.APPLY_BORROW.getCode().equals(instType)) {
            // 更新项目状态和流程状态
            updateRejectProjectStatus(userId, declaredProject, instType);
        }
        //发送消息
        noticeManage.sendNotice(userId, declaredProject, instance.getProcessDefinitionName(),
                REJECT_MSG_TEMPLATE, MsgTypeEnum.PROJECT_REVIEW_REJECT);
    }

    /**
     * 发起人 撤回逻辑
     */
    public void rootWithDraw(Project declaredProject) {
        Long userId = LoginUserUtil.getUserId();
        // 若是流程发起人点击撤回，项目回到上一个状态，需调用状态机更新项目状态,流程状态更新为审核通过
        switch (Objects.requireNonNull(ProjectStatusEnum.match(declaredProject.getStatus()))) {
            // 当前项目状态是单位内部审核中
            case UNDER_INTERNAL_AUDIT:
                // 更新项目状态为待申报
                updateWithdrawProjectStatus(userId, declaredProject);
                // 保存到草稿箱中
                saveToDraft(declaredProject);
                //删除所有项目相关的信息
                deleteProjectRelated(declaredProject);
                break;
            // 当前项目状态是预审中
            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: " + declaredProject.getStatus());
        }
    }

    // 保存到草稿箱中
    private void saveToDraft(Project declaredProject) {
        ProjectDraftSaveDTO draftSaveDto = new ProjectDraftSaveDTO();
        ProjectDTO projectInfo = new ProjectDTO();
        BeanUtils.copyProperties(declaredProject, projectInfo);
        // 查询出项目关联的应用信息
        List<ProjectApplication> applicationList = projectApplicationService.list(Wrappers.lambdaQuery(ProjectApplication.class)
                .eq(ProjectApplication::getProjectId, declaredProject.getId()));
        List<ProjectApplicationDTO> applicationDTOList = applicationList.stream().map(a -> {
            ProjectApplicationDTO applicationDTO = new ProjectApplicationDTO();
            BeanUtils.copyProperties(a, applicationDTO);
            return applicationDTO;
        }).collect(Collectors.toList());
        projectInfo.setApplicationList(applicationDTOList);
        HashMap<String, Object> dynamicMap = JSON.parseObject(declaredProject.getDynamicForm(), HashMap.class);
        projectInfo.setDynamicForm(dynamicMap);
        draftSaveDto.setProjectInfo(projectInfo);
        declaredProjectManage.saveToDraft(draftSaveDto);
    }

    /**
     * 删除项目的所有信息
     *
     * @param declaredProject
     */
    public void deleteProjectRelated(Project declaredProject) {
        //删除项目库中该项目信息
        projectService.removeById(declaredProject);
        //删除app信息
        projectApplicationService.remove(Wrappers.lambdaQuery(ProjectApplication.class)
                .eq(ProjectApplication::getProjectId, declaredProject.getId()));
    }

    /**
     * 退回审核后 所处理的逻辑
     *
     * @param declaredProject
     * @param instance
     */
    public void afterBackTodo(Project declaredProject, HistoricProcessInstance instance) {
        Long userId = LoginUserUtil.getUserId();
        // 获取当前流程实例类型
        String instanceId = declaredProject.getInstCode();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId));
        Integer instType = projectInst.getInstType();
        LambdaUpdateWrapper<Project> updateWrapper = Wrappers.lambdaUpdate();
        // 如果是预审审批或建设方案退回,需要重新盖章，原来盖过章的文件要清空
        if (InstTypeEnum.PRELIMINARY_PREVIEW.getCode().equals(instType)) {
            updateWrapper.set(Project::getPretrialFileId, null)
                    .set(Project::getPretrialFileName, null)
                    .eq(Project::getId, declaredProject.getId());
            projectService.update(updateWrapper);
        } else if (InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.getCode().equals(instType)) {
            updateWrapper.set(Project::getConstructFileId, null)
                    .set(Project::getConstructFileName, null)
                    .eq(Project::getId, declaredProject.getId());
            projectService.update(updateWrapper);
        }
        // 给项目创建人、流程发起人发送浙政钉工作通知：【项目名称】的【流程名称】被退回，请及时处理。
        //发送消息
        noticeManage.sendNotice(userId, declaredProject, instance.getProcessDefinitionName(), BACK_MSG_TEMPLATE, MsgTypeEnum.PROJECT_REVIEW_BACK);
    }


    /**
     * 当为通过操作时，更新项目表中项目状态
     *
     * @param userId  \
     * @param project \
     * @author CMM
     * @since 2023/02/08
     */
    public void updatePassProjectStatus(Long userId, Project project) {
        try {
            stateMachineUtil.pass(project);
            project.setUpdateOn(LocalDateTime.now());
            project.setUpdateBy(userId);
            projectService.updateById(project);
        } catch (Exception e) {
            log.error("状态机执行失败", e);
            throw new BizException("状态机执行失败!");
        }
    }

    /**
     * 当为驳回操作时，更新项目表中的项目状态
     *
     * @param userId   \
     * @param project  \
     * @param instType \
     * @author CMM
     * @since 2023/02/08
     */
    private void updateRejectProjectStatus(Long userId, Project project, Integer instType) {
        stateMachineUtil.reject(project);
        LambdaUpdateWrapper<Project> updateWrapper = Wrappers.lambdaUpdate();
        project.setUpdateOn(LocalDateTime.now());
        project.setUpdateBy(userId);
        projectService.updateById(project);
        // 如果是预审审批驳回,需要重新盖章，原来盖过章的文件要清空
        if (InstTypeEnum.PRELIMINARY_PREVIEW.eq(instType)) {
            updateWrapper.set(Project::getPretrialFileId, null)
                    .set(Project::getPretrialFileName, null)
                    .eq(Project::getId, project.getId());
        } else if (InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.eq(instType)) {
            updateWrapper.set(Project::getConstructFileId, null)
                    .set(Project::getConstructFileName, null)
                    .eq(Project::getId, project.getId());
        } else {
            return;
        }
        projectService.update(updateWrapper);
    }

    /**
     * 当为撤回操作时，更新项目表中的项目状态为前一个状态
     *
     * @param userId
     * @param project
     * @return void
     * @author CMM
     * @since 2023/02/08
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateWithdrawProjectStatus(Long userId, Project project) {
        // 删除关联表信息
        projectInstService.remove(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getProjectId, project.getId())
                .eq(ProjectInst::getInstCode, project.getInstCode()));
        //删除项目的实例信息
        historyService.deleteHistoricProcessInstance(project.getInstCode());

        stateMachineUtil.withDraw(project);
        project.setUpdateOn(LocalDateTime.now());
        project.setUpdateBy(userId);
        project.setInstCode(TodoCenterConst.Declared.NULL_INST_CODE);
        projectService.updateById(project);
    }

    public void deleteBackComments(List<HistoricVariableInstance> approves) {
        if (CollUtil.isNotEmpty(approves)) {
            for (HistoricVariableInstance approve : approves) {
                if (approve.getValue() instanceof ProcessHandlerEnum
                        && (ProcessHandlerEnum.BACK.equals(ProcessHandlerEnum.getEnumByName(String.valueOf(approve.getValue()))))) {
                    runtimeService.removeVariable(approve.getProcessInstanceId(), approve.getVariableName());

                    Comment comment;
                    Optional<Comment> first = taskService.getProcessInstanceComments(approve.getProcessInstanceId())
                            .stream()
                            .filter(c -> c.getTaskId().equals(approve.getVariableName().replace("approve_", StringUtils.EMPTY)))
                            .findFirst();
                    if (first.isPresent()) {
                        comment = first.get();
                        taskService.deleteComment(comment.getId());
                    }
                    break;

                }
            }
        }
    }

    /**
     * 判断是否包含 此任务
     *
     * @param progressNodes \
     * @param taskId        \
     * @return \
     */
    public static boolean checkIsContainsTask(List<ProgressNode> progressNodes, String taskId) {
        if (CollUtil.isEmpty(progressNodes)) {
            return Boolean.FALSE;
        }

        for (ProgressNode node : progressNodes) {
            if (node.getTaskId().equals(taskId)) {
                return Boolean.TRUE;
            }
            if (CollUtil.isNotEmpty(node.getChildren()) &&
                    (checkIsContainsTask(node.getChildren(), taskId))) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}
