package cn.axzo.workflow.core.service.impl;

import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd;
import cn.axzo.workflow.core.engine.job.AsyncBpmnProcessActivityJobHandler;
import cn.axzo.workflow.core.service.BpmnProcessActivityService;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.job.service.JobService;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;

import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.PROCESS_INSTANCE_IS_NOT_EXIST;


@Service
@Slf4j
public class BpmnProcessActivityServiceImpl implements BpmnProcessActivityService {

    @Resource
    @Lazy
    private RuntimeService runtimeService;
    @Resource
    @Lazy
    private SpringProcessEngineConfiguration processEngineConfiguration;

    private static final String JOB_TRIGGER_ASYNC_NAME = "asyncTrigger";
    private static final String JOB_ASSIGNEE_ASYNC_NAME = "asyncSetAssignee";

    public void executeAsyncJob(JobEntity job) {
        if (job == null) {
            log.error("job is null");
            throw new NullPointerException("job is null");
        }
        String type = job.getElementName();
        String customValues = job.getCustomValues();
        switch (type) {
            case JOB_TRIGGER_ASYNC_NAME:
                trigger(customValues);
                break;
            case JOB_ASSIGNEE_ASYNC_NAME:
                BpmnActivitySetAssigneeDTO dto = JSONUtil.toBean(customValues, BpmnActivitySetAssigneeDTO.class);
                setAssignee(dto);
                break;
            default:
                throw new UnsupportedOperationException(String.format("不支持'%s'类型操作", type));
        }
    }

    @Override
    public void trigger(String executionId) {
        Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
        if (Objects.isNull(execution)) {
            throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, executionId);
        }
        runtimeService.trigger(executionId);
    }

    @Override
    public void triggerAsync(String executionId) {
        validateAndStartAsyncJob(executionId, executionId, JOB_TRIGGER_ASYNC_NAME);
    }

    @Override
    public void setAssignee(BpmnActivitySetAssigneeDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        commandExecutor.execute(new CustomBizSpecifyAssigneeToTaskCmd(dto.getTriggerId(), dto.getAssigners()));
    }

    @Override
    public void setAssigneeAsync(BpmnActivitySetAssigneeDTO dto) {
        validateAndStartAsyncJob(dto.getTriggerId(), dto, JOB_ASSIGNEE_ASYNC_NAME);
    }

    private void validateAndStartAsyncJob(String executionId, Object customValue, String actionName) {
        Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
        if (Objects.isNull(execution)) {
            throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, executionId);
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult();
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_IS_NOT_EXIST, execution.getId());
        }
        JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService();
        JobEntity job = jobService.createJob();
        // 这里的 executionId 可为 null
        job.setExecutionId(executionId);
        job.setProcessInstanceId(execution.getProcessInstanceId());
        job.setProcessDefinitionId(processInstance.getProcessDefinitionId());
        job.setElementId(AsyncBpmnProcessActivityJobHandler.TYPE);
        job.setElementName(actionName);
        job.setJobHandlerType(AsyncBpmnProcessActivityJobHandler.TYPE);
        job.setTenantId(execution.getTenantId());
        job.setCustomValues(JSONUtil.toJsonStr(customValue));
        jobService.createAsyncJob(job, false);
        jobService.scheduleAsyncJob(job);
    }

}
