package cn.fateverse.workflow.process.listener;

import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.workflow.constant.ProcessConstant;
import cn.fateverse.workflow.service.NotifyService;
import cn.fateverse.workflow.entity.HistoricalOperation;
import cn.fateverse.workflow.entity.ProcessData;
import cn.fateverse.workflow.entity.bpmn.CommentInfo;
import cn.fateverse.workflow.entity.bpmn.ProcessNode;
import cn.fateverse.workflow.entity.bpmn.SettingsInfo;
import cn.fateverse.workflow.entity.bpmn.UserInfo;
import cn.fateverse.workflow.enums.OperationStateEnums;
import cn.fateverse.workflow.mapper.HistoricalOperationMapper;
import cn.fateverse.workflow.mapper.ProcessDataMapper;
import cn.fateverse.workflow.mapper.UserInstanceMapper;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.task.api.Task;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 用户定时边界事件监听器
 *
 * @author Clay
 * @date 2023/1/4
 */
@Slf4j
@Component
public class BoundaryEvenListener implements ExecutionListener {

    private final TaskService taskService;

    private final RuntimeService runtimeService;

    private final NotifyService notifyService;

    private final ProcessDataMapper processDataMapper;

    private final UserInstanceMapper userInstanceMapper;

    private final HistoricalOperationMapper operationMapper;

    public BoundaryEvenListener(TaskService taskService,
                                RuntimeService runtimeService,
                                NotifyService notifyService,
                                ProcessDataMapper processDataMapper,
                                UserInstanceMapper userInstanceMapper, HistoricalOperationMapper operationMapper) {
        this.taskService = taskService;
        this.runtimeService = runtimeService;
        this.notifyService = notifyService;
        this.processDataMapper = processDataMapper;
        this.userInstanceMapper = userInstanceMapper;
        this.operationMapper = operationMapper;
    }


    @Override
    public void notify(DelegateExecution execution) {
        ExecutionEntityImpl entity = (ExecutionEntityImpl) execution;
        String processDefinitionKey = entity.getProcessDefinitionKey();
        ProcessData processData = processDataMapper.selectByProcessDefinitionKey(processDefinitionKey);
        Optional<ProcessNode> nodeOptional = JSON.parseArray(processData.getProcessStr(), ProcessNode.class).stream().filter(processNode -> {
            String activityId = execution.getCurrentActivityId().replace("boundary_" , "node_");
            return processNode.getId().equals(activityId);
        }).findAny();
        if (nodeOptional.isEmpty()) {
            return;
        }
        ProcessNode processNode = nodeOptional.get();
        JSONObject timeLimit = processNode.getProps().getTimeLimit();
        String type = timeLimit.getJSONObject("handler").getString("type");
        List<Task> list = taskService.createTaskQuery()
                .processInstanceId(execution.getProcessInstanceId())
                .includeProcessVariables()
                .taskDefinitionKey(processNode.getId())
                .list();
        switch (type) {
            case "PASS":
                passExecution(list);
                break;
            case "REFUSE":
                refuseExecution(list);
                break;
            case "NOTIFY":
                String deploymentName = processData.getDeploymentName();
                notifyService.notifyTaskList(deploymentName, list, JSON.parseObject(processData.getSettings(), SettingsInfo.class));
                break;
            default:
                throw new CustomException("事件还未实现");
        }
    }

    /**
     * 自动通过
     *
     * @param list 任务对象列表
     */
    private void passExecution(List<Task> list) {
        list.forEach(task -> {
            changeOperation(task, OperationStateEnums.AUTO_PASS, "任务超时,自动通过");
            taskService.complete(task.getId());
        });
    }

    /**
     * 自动驳回
     *
     * @param list 任务对象列表
     */
    private void refuseExecution(List<Task> list) {
        Set<String> processInstanceIdSet = new HashSet<>(list.size());
        list.forEach(task -> {
            changeOperation(task, OperationStateEnums.AUTO_REFUSE, "任务超时,自动驳回");
            changVariables(task, ProcessConstant.BUSINESS_STATUS_REJECT);
            runtimeService.deleteProcessInstance(task.getProcessInstanceId(), "任务超时,自动驳回");
            processInstanceIdSet.add(task.getProcessInstanceId());
        });
        userInstanceMapper.updateStateByProcessInstanceIdList(new ArrayList<>(processInstanceIdSet), ProcessConstant.BUSINESS_STATUS_REJECT);
    }

    /**
     * 改变运行装填
     *
     * @param task  任务节点
     * @param state 需要修改的状态
     */
    private void changVariables(Task task, String state) {
        //获取流程数据,并设置为拒绝状态
        Map<String, Object> variables = task.getProcessVariables();
        variables.put(ProcessConstant.PROCESS_STATUS, state);
        //将表单数据设置到流程中
        runtimeService.setVariables(task.getProcessInstanceId(), variables);
    }

    /**
     * 改变操作历史
     *
     * @param task  task任务节点信息
     * @param state 需要修改的状态
     * @param msg   需要添加的提示信息
     */
    private void changeOperation(Task task, OperationStateEnums state, String msg) {
        //获取到操作历史
        HistoricalOperation operation = operationMapper.selectByProcessInstanceIdAndNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());
        //设置状态
        operation.setState(state);
        CommentInfo commentInfo = new CommentInfo();
        commentInfo.setContext(msg);
        operation.setMessage(JSON.toJSONString(commentInfo));
        List<UserInfo> userInfos = JSON.parseArray(operation.getUserInfo(), UserInfo.class);
        if (null != userInfos) {
            for (UserInfo userInfo : userInfos) {
                userInfo.setState(state);
            }
            operation.setUserInfo(JSON.toJSONString(userInfos));
        }
        operationMapper.update(operation);
    }


}
