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

import cn.axzo.workflow.common.enums.ProcessTaskEventEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.identity.Authentication;
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.engine.impl.util.ProcessDefinitionUtil;
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.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TRANSFER_TO_SELF;
import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS;
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.constant.BpmnConstants.TRANSFER_TO;
import static cn.axzo.workflow.common.constant.BpmnConstants.TRANSFER_TO_ADVICE;
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.utils.BpmnMetaParserHelper.getCategoryVersion;
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.deleteMultiTasks;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getDuplicatePendingTasks;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;

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

    private static final long serialVersionUID = -8011156125774066571L;
    private final String originTaskId;
    private final BpmnTaskDelegateAssigner originTaskAssignee;
    private final String advice;
    private final List<AttachmentDTO> attachmentList;
    private final BpmnTaskDelegateAssigner targetTaskAssignee;
    private final String additionalOpeDesc;

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

    @Override
    public String paramToJsonString() {
        Map<String, Object> params = new HashMap<>();
        params.put("originTaskId", originTaskId);
        params.put("originTaskAssignee", originTaskAssignee);
        params.put("advice", advice);
        params.put("attachmentList", attachmentList);
        params.put("targetTaskAssignee", targetTaskAssignee);
        return JSON.toJSONString(params);
    }

    @Override
    public Void execute(CommandContext commandContext) {
        //不能转交给自己
        if (targetTaskAssignee.buildAssigneeId().equals(originTaskAssignee.buildAssigneeId())) {
            throw new WorkflowEngineException(TRANSFER_TO_SELF);
        }
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoricTaskInstanceQuery taskQuery =
                processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
        HistoricTaskInstance historicTaskInstance = taskQuery.taskId(originTaskId).singleResult();

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

        validTargetAssigneeNodeId(task.getProcessDefinitionId());

        validTask(historicTaskInstance, task, originTaskAssignee, null);


        // 修改节点对应的审批人集合快照信息
        processAssignee(processEngineConfiguration, task);

        // 对被转交的任务进行建议和附件的处理
        resolveOriginTask(commandContext, taskService, task);
        batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList,
                originTaskAssignee);

        List<Task> duplicatePendingTasks = getDuplicatePendingTasks(commandContext, task, Lists.newArrayList(targetTaskAssignee));
        //转交目标人不存在任务，再生成转交任务
        if (CollectionUtils.isEmpty(duplicatePendingTasks)) {
            addMultiTask(commandContext, task, targetTaskAssignee);
        }
        task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), TRANSFER.getStatus());
        // 结束被转交任务
        deleteMultiTasks(commandContext, Collections.singletonList(task));

        task.setTransientVariable(TRANSFER_TO, targetTaskAssignee);
        task.setTransientVariable(TRANSFER_TO_ADVICE, advice);
        processEngineConfiguration.getListenerNotificationHelper().executeTaskListeners(task, ProcessTaskEventEnum.PROCESS_TASK_TRANSFER.getTag());
        return null;
    }

    private void validTargetAssigneeNodeId(String processDefinitionId) {
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId);
        Integer categoryVersion = getCategoryVersion(bpmnModel.getMainProcess()).orElse(0);
        if (categoryVersion > 0 && StringUtils.isBlank(targetTaskAssignee.getNodeId())) {
            throw new WorkflowEngineException(ASSIGNEE_NODE_ID_NOT_EXISTS, "审批人");
        }
    }

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

        Authentication.setAuthenticatedUserId(originTaskAssignee.buildAssigneeId());
        addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "转交给" + targetTaskAssignee.getAssignerName() + (StringUtils.isNotBlank(additionalOpeDesc) ? additionalOpeDesc : ""));
        addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice);
        Authentication.setAuthenticatedUserId(null);

        taskService.saveTask(task);
    }

    public void processAssignee(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) {
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        @SuppressWarnings("unchecked")
        List<BpmnTaskDelegateAssigner> originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(),
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class);
        List<BpmnTaskDelegateAssigner> resultList = new ArrayList<>();
        boolean containsTargetAssignee = false;
        for (BpmnTaskDelegateAssigner assigner : originAssingeeList) {
            if (!Objects.equals(assigner.buildAssigneeId(), originTaskAssignee.buildAssigneeId())) {
                resultList.add(assigner);
            }
            if (Objects.equals(assigner.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())) {
                containsTargetAssignee = true;
            }
        }
        if (!containsTargetAssignee) {
            resultList.add(targetTaskAssignee);
        }
        runtimeService.setVariable(task.getProcessInstanceId(),
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(),
                resultList);
    }

}
