package cn.axzo.workflow.core.engine.cmd;

import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnModelUtils;
import cn.axzo.workflow.core.engine.job.AsyncBackTaskJobHandler;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.job.service.JobService;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;

import java.io.Serializable;
import java.util.Objects;

import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;

@Slf4j
public class CustomBackTaskAsyncCmd extends AbstractCommand<String> implements Serializable {

    private static final long serialVersionUID = 1773108485033787095L;

    private final BpmnTaskBackAuditDTO dto;

    public CustomBackTaskAsyncCmd(BpmnTaskBackAuditDTO dto) {
        this.dto = dto;
    }

    @Override
    public String executeInternal(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
            CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoricTaskInstanceQuery taskQuery =
            processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
        TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService();
        HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult();

        TaskEntity taskEntity = taskService.getTask(dto.getTaskId());
        validTask(historicTaskInstance, taskEntity, dto.getApprover(), dto.getNodeTypes());

        Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
        FlowElement targetFlowElement = process.getFlowElement(dto.getToActivityId(), true);
        if (Objects.isNull(targetFlowElement)) {
            throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, dto.getToActivityId());
        }
        FlowElement sourceFlowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey(), true);
        // 退回节点到当前节点不可达到，不允许退回
        if (!BpmnModelUtils.isReachable(process, (FlowNode) targetFlowElement, (FlowNode) sourceFlowElement)) {
            throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
        }

        return startAsync(processEngineConfiguration, taskEntity);
    }

    private String startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) {
        JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService();

        JobEntity job = jobService.createJob();
        // 这里的 executionId 可为 null
        job.setExecutionId(task.getExecutionId());
        job.setProcessInstanceId(task.getProcessInstanceId());
        job.setProcessDefinitionId(task.getProcessDefinitionId());
        job.setElementId(task.getTaskDefinitionKey());
        job.setElementName(task.getName());
        job.setJobHandlerType(AsyncBackTaskJobHandler.TYPE);
        job.setTenantId(task.getTenantId());

        // 携带自定义的数据
        job.setCustomValues(JSONUtil.toJsonStr(dto));

        // 创建异步任务并调度
        jobService.createAsyncJob(job, false);
        jobService.scheduleAsyncJob(job);
        return job.getId();
    }

    @Override
    public String paramToJsonString() {
        return JSON.toJSONString(dto);
    }
}
