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

import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import com.google.common.collect.Lists;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
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.impl.persistence.entity.TaskEntity;
import org.springframework.util.StringUtils;

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

import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.TRANSFER;
import static cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner.buildDummyAssigner;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addMultiTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.deleteMultiTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerDuplicated;

/**
 * 转交任务的命令实现
 *
 * @author wangli
 * @since 2023/12/22 10:22
 */
public class CustomTransferUserTaskCmd implements Command<Void>, Serializable {

    private final String originTaskId;
    private final BpmnTaskDelegateAssigner originTaskAssignee;
    private final String advice;
    private final List<AttachmentDTO> attachmentList;
    private final BpmnTaskDelegateAssigner targetTaskAssignee;

    public CustomTransferUserTaskCmd(String originTaskId, BpmnTaskDelegateAssigner originTaskAssignee, String advice,
                                     List<AttachmentDTO> attachmentList, BpmnTaskDelegateAssigner targetTaskAssignee) {
        this.originTaskId = originTaskId;
        this.originTaskAssignee = originTaskAssignee;
        this.advice = advice;
        this.attachmentList = attachmentList;
        this.targetTaskAssignee = targetTaskAssignee;
    }


    @Override
    public Void execute(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoricTaskInstanceQuery taskQuery =
                processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
        HistoricTaskInstance historicTaskInstance = taskQuery.taskId(originTaskId).singleResult();

        TaskService taskService = processEngineConfiguration.getTaskService();
        Task task = taskService.createTaskQuery().taskId(originTaskId).singleResult();

        validTask(historicTaskInstance, (TaskEntity) task, originTaskAssignee);

        validTaskAssignerDuplicated(commandContext, (TaskEntity) task, Lists.newArrayList(targetTaskAssignee));

        processAssignee(processEngineConfiguration, task);

        resolveOriginTask(commandContext, taskService, task);

        batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList,
                    originTaskAssignee);

        addMultiTask(commandContext, (TaskEntity) task, targetTaskAssignee);
        ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), TRANSFER.getStatus());
        deleteMultiTask(commandContext, (TaskEntity) task);

        return null;
    }

    private void resolveOriginTask(CommandContext commandContext, TaskService taskService, Task task) {
        BpmnTaskDelegateAssigner assigner = buildDummyAssigner("transfer", TASK_ASSIGNEE_SKIP_FLAT, "dummyApprover");
        task.setAssignee(assigner.buildAssigneeId());
        ((TaskEntity) task).setScopeType("TRANSFER");
        taskService.saveTask(task);

        Authentication.setAuthenticatedUserId(originTaskAssignee.buildAssigneeId());
        addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "转交给" + targetTaskAssignee.getAssignerName());
        if (StringUtils.hasLength(advice)) {
            addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice);
        }
        Authentication.setAuthenticatedUserId(null);
    }

    public void processAssignee(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) {
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        List<BpmnTaskDelegateAssigner> originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(),
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class);
        Optional<BpmnTaskDelegateAssigner> exists = originAssingeeList.stream()
                .filter(i -> Objects.equals(i.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())).findAny();
        if (exists.isPresent()) {
            throw new WorkflowEngineException(ASSIGNEE_HAS_BEEN_EXISTS);
        }

        for (BpmnTaskDelegateAssigner assigner : originAssingeeList) {
            if (Objects.equals(assigner.buildAssigneeId(), originTaskAssignee.buildAssigneeId())) {
                originAssingeeList.remove(assigner);
                break;
            }
        }
        originAssingeeList.add(targetTaskAssignee);
        runtimeService.setVariable(task.getProcessInstanceId(),
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(),
                originAssingeeList);
    }
}
