package com.njcn.bpm.service.task.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.bpm.constant.BpmConstants;
import com.njcn.bpm.enums.BpmDeleteReasonEnum;
import com.njcn.bpm.enums.BpmProcessInstanceStatusEnum;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.enums.BpmTaskStatusEnum;
import com.njcn.bpm.event.BpmProcessInstanceEventPublisher;
import com.njcn.bpm.pojo.dto.BpmProcessInstanceCreateReqDTO;
import com.njcn.bpm.pojo.dto.PageResult;
import com.njcn.bpm.pojo.param.instance.BpmProcessInstanceCancelParam;
import com.njcn.bpm.pojo.param.instance.BpmProcessInstancePageParam;
import com.njcn.bpm.pojo.param.task.BpmTaskRejectParam;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.service.task.IBpmProcessInstanceService;
import com.njcn.bpm.strategy.BpmTaskCandidateStartUserSelectStrategy;
import com.njcn.bpm.utils.BpmProcessInstanceConvert;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.bpm.utils.DateUtils;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.utils.PubUtils;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.pojo.vo.UserVO;
import com.njcn.web.factory.PageFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.njcn.bpm.utils.FlowableUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;


/**
 * 流程实例 Service 实现类
 * <p>
 * ProcessDefinition & ProcessInstance & Execution & Task 的关系：
 * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />
 * <p>
 * HistoricProcessInstance & ProcessInstance 的关系：
 * 1. <a href=" https://my.oschina.net/843294669/blog/71902" />
 * <p>
 * 简单来说，前者 = 历史 + 运行中的流程实例，后者仅是运行中的流程实例
 *
 * @author hongawen
 */
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements IBpmProcessInstanceService {

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private HistoryService historyService;

    @Resource
    private IBpmProcessDefinitionService processDefinitionService;

    @Resource
    private UserFeignClient userFeignClient;

    @Resource
    private BpmProcessInstanceEventPublisher processInstanceEventPublisher;

    @Override
    public ProcessInstance getProcessInstance(String id) {
        return runtimeService.createProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceId(id)
                .singleResult();
    }


    @Override
    public List<ProcessInstance> getProcessInstances(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String id) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();
    }

    @Override
    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public PageResult<HistoricProcessInstance> getProcessInstancePage(String userId, BpmProcessInstancePageParam bpmProcessInstancePageParam) {
        if (StrUtil.isNotBlank(bpmProcessInstancePageParam.getSearchValue()) && bpmProcessInstancePageParam.getSearchValue().contains("null")) {
            throw new BusinessException("搜索值中不能包含null");
        }
        // 通过 BpmProcessInstanceExtDO 表，先查询到对应的分页
        HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceTenantId(FlowableUtils.getTenantId())
                .orderByProcessInstanceStartTime().desc();
        // 【我的流程】菜单时，需要传递该字段
        if (userId != null) {
            processInstanceQuery.startedBy(userId);
            // 【管理流程】菜单时，才会传递该字段
        } else if (bpmProcessInstancePageParam.getStartUserId() != null) {
            processInstanceQuery.startedBy(bpmProcessInstancePageParam.getStartUserId());
        }
        if (StrUtil.isNotEmpty(bpmProcessInstancePageParam.getName())) {
            processInstanceQuery.processInstanceNameLike("%" + bpmProcessInstancePageParam.getName() + "%");
        }
        if (StrUtil.isNotEmpty(bpmProcessInstancePageParam.getSearchValue())) {
            processInstanceQuery.processInstanceNameLike("%" + bpmProcessInstancePageParam.getSearchValue() + "%");
        }
        if (StrUtil.isNotEmpty(bpmProcessInstancePageParam.getProcessDefinitionId())) {
            processInstanceQuery.processDefinitionId("%" + bpmProcessInstancePageParam.getProcessDefinitionId() + "%");
        }
        if (StrUtil.isNotEmpty(bpmProcessInstancePageParam.getCategory())) {
            processInstanceQuery.processDefinitionCategory(bpmProcessInstancePageParam.getCategory());
        }

        if (bpmProcessInstancePageParam.getStatus() != null) {
            processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, bpmProcessInstancePageParam.getStatus());
        }
        //流程状态排除不通过的，不通过会放在待办中，一直到该流程通过
        processInstanceQuery.variableValueNotEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus());

        if (Objects.nonNull(bpmProcessInstancePageParam.getSearchBeginTime()) && Objects.nonNull(bpmProcessInstancePageParam.getSearchEndTime())) {
            String beginTimeStr = bpmProcessInstancePageParam.getSearchBeginTime();
            String endTimeStr = bpmProcessInstancePageParam.getSearchEndTime();
            LocalDateTime beginTime = LocalDateTimeUtil.parse(beginTimeStr, DatePattern.NORM_DATE_PATTERN);
            LocalDateTime endTime = PubUtils.endTimeToLocalDateTime(endTimeStr);
            processInstanceQuery.startedBefore(DateUtils.of(endTime));
            processInstanceQuery.startedAfter(DateUtils.of(beginTime));
        }

        // 查询数量
        long processInstanceCount = processInstanceQuery.count();
        if (processInstanceCount == 0) {
            return PageResult.empty(processInstanceCount);
        }
        // 查询列表
        int offset = PageFactory.getPageSize(bpmProcessInstancePageParam) * (PageFactory.getPageNum(bpmProcessInstancePageParam) - 1);
        List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(offset, PageFactory.getPageSize(bpmProcessInstancePageParam));
        return new PageResult<>(processInstanceList, processInstanceCount);
    }

    @Override
    public List<HistoricProcessInstance> getRejectProcessInstanceList(String userId, Set<String> instanceIdSet) {
        HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                // 只查询当前用户的流程实例
                .startedBy(userId)
                .includeProcessVariables()
                // 只查询已驳回的流程实例
                .variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus())
                //加上从业务表中产寻到的不通过的实例id
                .processInstanceIds(instanceIdSet)
                .processInstanceTenantId(FlowableUtils.getTenantId())
                .orderByProcessInstanceStartTime().desc();
        return processInstanceQuery.list();
    }

//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
//        // 获得流程定义
//        ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
//        // 发起流程
//        return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
//                createReqVO.getStartUserSelectAssignees());
//    }

    @Override
    public String createProcessInstance(String userId, BpmProcessInstanceCreateReqDTO createReqDTO) {
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
        // 发起流程
        return createProcessInstance(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),
                createReqDTO.getStartUserSelectAssignees());
    }

    private String createProcessInstance(String userId, ProcessDefinition definition,
                                         Map<String, Object> variables, String businessKey,
                                         Map<String, List<String>> startUserSelectAssignees) {
        // 1.1 校验流程定义
        if (definition == null) {
            throw new BusinessException(BpmResponseEnum.PROCESS_DEFINITION_NOT_EXISTS);
        }
        if (definition.isSuspended()) {
            throw new BusinessException(BpmResponseEnum.PROCESS_DEFINITION_IS_SUSPENDED);
        }

        // 1.2 校验发起人自选审批人
        validateStartUserSelectAssignees(definition, startUserSelectAssignees);

        // 2. 创建流程实例
        if (variables == null) {
            variables = new HashMap<>(32);
        }
        // 过滤一下，避免 ProcessInstance 系统级的变量被占用
        FlowableUtils.filterProcessInstanceFormVariable(variables);
        // 流程实例状态：审批中
        variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                BpmProcessInstanceStatusEnum.RUNNING.getStatus());
        if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
            variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
        }
        Authentication.setAuthenticatedUserId(userId);
        ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
                .processDefinitionId(definition.getId())
                .businessKey(businessKey)
                .name(definition.getName().trim())
                .variables(variables)
                .start();
        return instance.getId();
    }

    private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<String>> startUserSelectAssignees) {
        // 1. 获得发起人自选审批人的 UserTask 列表
        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
        List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
        if (CollUtil.isEmpty(userTaskList)) {
            return;
        }

        // 2. 校验发起人自选审批人的 UserTask 是否都配置了
        userTaskList.forEach(userTask -> {
            List<String> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null;
            if (CollUtil.isEmpty(assignees)) {
                throw new BusinessException("审批任务(" + userTask.getName() + ")的审批人未配置");
            }
            List<UserVO> userList = userFeignClient.getUserVOByIdList(assignees).getData();
            Map<String, UserVO> userMap = CollectionUtils.convertMap(userList, UserVO::getId);
            assignees.forEach(assignee -> {
                if (userMap.get(assignee) == null) {
                    throw new BusinessException("审批任务(" + userTask.getName() + ")的审批人(" + assignee + ")不存在");
                }
            });
        });
    }

    @Override
    public void cancelProcessInstanceByStartUser(String userId, BpmProcessInstanceCancelParam cancelReqVO) {
        // 1.1 校验流程实例存在
        ProcessInstance instance = getProcessInstance(cancelReqVO.getProcessInstanceId());
        if (instance == null) {
            throw new BusinessException(BpmResponseEnum.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
        }
        // 1.2 只能取消自己的
        if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
            throw new BusinessException(BpmResponseEnum.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
        }

        // 2. 通过删除流程实例，实现流程实例的取消,
        // 删除流程实例，正则执行任务 ACT_RU_TASK. 任务会被删除。
        deleteProcessInstance(cancelReqVO.getProcessInstanceId(),
                BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));

        // 3. 进一步的处理，交给 updateProcessInstanceCancel 方法
    }

    @Override
    public void cancelProcessInstanceByAdmin(String userId, BpmProcessInstanceCancelParam cancelReqVO) {
        // 1.1 校验流程实例存在
        ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
        if (instance == null) {
            throw new BusinessException(BpmResponseEnum.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
        }
        // 1.2 管理员取消，不用校验是否为自己的
        UserVO user = userFeignClient.getUserById(userId).getData();

        // 2. 通过删除流程实例，实现流程实例的取消,
        // 删除流程实例，正则执行任务 ACT_RU_TASK. 任务会被删除。
        deleteProcessInstance(cancelReqVO.getId(),
                BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getName(), cancelReqVO.getReason()));

        // 3. 进一步的处理，交给 updateProcessInstanceCancel 方法
    }

    @Override
    public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) {
        // 1. 判断是否为 Reject 不通过。如果是，则不进行更新.
        // 因为，updateProcessInstanceReject 方法（审批不通过），已经进行更新了
        if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) {
            return;
        }

        // 2. 更新流程实例 status
        runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                BpmProcessInstanceStatusEnum.CANCEL.getStatus());

        // 3. 发送流程实例的状态事件
        // 注意：此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
        // 发送流程实例的状态事件
        processInstanceEventPublisher.sendProcessInstanceResultEvent(
                BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus()));
    }

    @Override
    public void updateProcessInstanceWhenApprove(ProcessInstance instance) {
        //判断传入参数是否是，不通过信息不通过则需要更新流程信息为不通过
        Integer processInstanceStatus = BpmProcessInstanceStatusEnum.APPROVE.getStatus();
        Map<String, Object> processVariables = instance.getProcessVariables();
        if (processVariables.containsKey(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS)) {
            Integer stata = Integer.valueOf(processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS).toString());
            if (Objects.equals(BpmProcessInstanceStatusEnum.REJECT.getStatus(), stata)) {
                processInstanceStatus = BpmProcessInstanceStatusEnum.REJECT.getStatus();
            }
        }
        // 1. 更新流程实例 status
        runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                processInstanceStatus);

        // 2. 发送流程被【通过】的消息
//        messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));

        // 3. 发送流程实例的状态事件
        // 注意：此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        // 在审核不通过时，会触该信息，设置在审核通过时，才发送
        if (Objects.equals(BpmProcessInstanceStatusEnum.APPROVE.getStatus(), processInstanceStatus)) {
            HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
            processInstanceEventPublisher.sendProcessInstanceResultEvent(
                    BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, processInstanceStatus));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessInstanceReject(String id, String reason) {
        // 1. 更新流程实例 status
        runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());

        // 2. 删除流程实例，以实现驳回任务时，取消整个审批流程
        ProcessInstance processInstance = getProcessInstance(id);
        deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));

        // 3. 发送流程被【不通过】的消息
//        messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason));

        // 4. 发送流程实例的状态事件
        processInstanceEventPublisher.sendProcessInstanceResultEvent(
                BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, reason, BpmProcessInstanceStatusEnum.REJECT.getStatus()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessInstanceFromReject(String id, String reason, TaskService taskService, BpmTaskRejectParam bpmTaskRejectParam) {
        // 1. 更新流程实例 status
        runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());

        // 2. 删除流程实例，以实现驳回任务时，取消整个审批流程
        ProcessInstance processInstance = getProcessInstance(id);
        // 3.3 调用 BPM complete 去完成任务
        // 其中，variables 是存储动态表单到 local 任务级别。过滤一下，避免 ProcessInstance 系统级的变量被占用
        if (CollectionUtil.isNotEmpty(bpmTaskRejectParam.getVariables())) {
            Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(bpmTaskRejectParam.getVariables());
            taskService.complete(bpmTaskRejectParam.getId(), variables, true);
        } else {
            taskService.complete(bpmTaskRejectParam.getId());
        }
        //这是删除所有信息，
//        deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
        // 4. 发送流程实例的状态事件
        processInstanceEventPublisher.sendProcessInstanceResultEvent(
                BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, reason, BpmProcessInstanceStatusEnum.REJECT.getStatus()));

    }

    public void deleteProcessInstance(String id, String reason) {
        runtimeService.deleteProcessInstance(id, reason);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String instanceId, String deleteReason) {

        // 查询历史数据
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId);
        if (historicProcessInstance.getEndTime() != null) {
            historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
            return;
        }
        // 删除流程实例
        runtimeService.deleteProcessInstance(instanceId, deleteReason);
        // 删除历史流程实例
        historyService.deleteHistoricProcessInstance(instanceId);
    }


    public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new BusinessException("流程实例不存在: " + processInstanceId);
        }
        return historicProcessInstance;
    }

}
