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

import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper;
import cn.axzo.workflow.core.service.CategoryService;
import com.alibaba.fastjson.JSON;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
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.api.Job;
import org.flowable.job.service.impl.persistence.entity.TimerJobEntity;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

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

import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_ERROR;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ACTIVITY_CANT_SET_ASSIGNEE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.PROCESS_CANT_SET_ASSIGNEE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.PROCESS_SET_ASSIGNEE_PARAM_ERROR;
import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.common.enums.WorkspaceType.PROJECT;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCategoryVersion;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount;

/**
 * 自定的业务指定审批人命令实现
 *
 * @author wangli
 * @since 2023/12/22 13:51
 */
public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand<Boolean> implements Serializable {
    private static final Logger log = LoggerFactory.getLogger(CustomBizSpecifyAssigneeToTaskCmd.class);
    private final String executionId;
    private final List<BpmnTaskDelegateAssigner> addedAssigners;

    public CustomBizSpecifyAssigneeToTaskCmd(String executionId, List<BpmnTaskDelegateAssigner> addedAssigners) {
        this.executionId = executionId;
        this.addedAssigners = addedAssigners;
    }

    @Override
    public String paramToJsonString() {
        Map<String, Object> params = new HashMap<>();
        params.put("executionId", executionId);
        params.put("addedAssigners", addedAssigners);
        return JSON.toJSONString(params);
    }

    public static Task getOperateTask(TaskService taskService, String executionId) {
        return taskService.createTaskQuery().executionId(executionId)
                .taskAssignee(NO_ASSIGNEE)
                .singleResult();
    }

    /**
     * 校验
     *
     * @param runtimeService
     * @param task
     * @param assigners
     */
    public static void validate(RuntimeService runtimeService, String executionId, Task task, List<BpmnTaskDelegateAssigner> assigners) {
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(task.getProcessDefinitionId());
        boolean present = assigners.stream().anyMatch(assigner -> !StringUtils.hasText(assigner.getNodeId()));
        if (present && getCategoryVersion(bpmnModel.getMainProcess()).orElse(0) > 0) {
            throw new WorkflowEngineException(ASSIGNEE_NODE_ID_NOT_EXISTS, "审批人");
        }
        validTask(task, executionId);
        //校验审批人数量是否超过限制
        validTaskAssignerCount(runtimeService, (TaskEntity) task, assigners);
    }

    @Override
    public Boolean execute(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        TaskService taskService = processEngineConfiguration.getTaskService();
        TaskEntity task = (TaskEntity) getOperateTask(taskService, executionId);
        //校验
        validate(processEngineConfiguration.getRuntimeService(), executionId, task, addedAssigners);

        // 校验节点真实的节点配置，还是否是指定人？
        if (!validateActivityConfig(task)) {
            log.info("业务节点设置审批人时，发现节点已经修改配置，无法继续设置审批人，processInstanceId:{}, taskDefinitionKey={}", task.getProcessInstanceId(), task.getTaskDefinitionKey());
            return true;
        }

        validProcessInstance(commandContext, task, addedAssigners);

        changeAssigneeSnapshot(commandContext, task);

        addAssignee(commandContext, taskService, task);

        clearAlterTimeJob(commandContext, task);

        return true;
    }

    public static boolean validateActivityConfig(TaskEntity task) {
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(task.getProcessDefinitionId());
        FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        ApprovalMethodEnum approvalMethodEnum = BpmnMetaParserHelper.getApprovalMethod(flowElement).orElse(ApprovalMethodEnum.unknown);
        return Objects.equals(approvalMethodEnum, ApprovalMethodEnum.bizSpecify);
    }

    /**
     * 清空告警的任务
     * <p>
     * 这个方法在最新的告警功能中，应该已经失效了，这里保留只是为了暂时兼容
     *
     * @param commandContext
     * @param task
     */
    @Deprecated
    private void clearAlterTimeJob(CommandContext commandContext, TaskEntity task) {
        ManagementService managementService = CommandContextUtil.getProcessEngineConfiguration(commandContext).getManagementService();
        Job timerJob = managementService.createTimerJobQuery().elementId(task.getTaskDefinitionKey()).processInstanceId(task.getProcessInstanceId()).singleResult();
        if (Objects.nonNull(timerJob)) {
            CommandContextUtil.getTimerJobService().deleteTimerJob((TimerJobEntity) timerJob);
        }

    }

    public static void validProcessInstance(CommandContext commandContext, Task task, List<BpmnTaskDelegateAssigner> addedAssigners) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoryService historyService = processEngineConfiguration.getHistoryService();
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, task.getProcessInstanceId());
        }
        if (!Objects.equals(PROCESSING.getStatus(), processInstance.getBusinessStatus())) {
            throw new WorkflowEngineException(PROCESS_CANT_SET_ASSIGNEE);
        }

        CategoryService categoryService = SpringContextUtils.getBean(CategoryService.class);
        categoryService.get(BPM_MODEL_CATEGORY, processInstance.getProcessDefinitionKey())
                .ifPresent(category -> {
                    if (category.getWorkspaceTypeCode().equals(String.valueOf(PROJECT.getCode())) && category.getVersion() > 0) {
                        for (BpmnTaskDelegateAssigner assigner : addedAssigners) {
                            if (!StringUtils.hasText(assigner.getNodeId())) {
                                throw new WorkflowEngineException(PROCESS_SET_ASSIGNEE_PARAM_ERROR);
                            }
                        }
                    }
                });
    }


    private void changeAssigneeSnapshot(CommandContext commandContext, Task task) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();

        @SuppressWarnings("unchecked")
        List<BpmnTaskDelegateAssigner> originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(),
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class);

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

    private void addAssignee(CommandContext commandContext, TaskService taskService, Task task) {
        if (CollectionUtils.isEmpty(addedAssigners)) {
            return;
        }

        addedAssigners.forEach(i -> CustomTaskHelper.addMultiTask(commandContext, (TaskEntity) task, i));

        taskService.setAssignee(task.getId(), HIDDEN_ASSIGNEE_ID);
        ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), DELETED.getStatus());
        CustomTaskHelper.deleteMultiTask(commandContext, task);
    }

    private static void validTask(Task task, String executionId) {
        if (Objects.isNull(task)) {
            throw new WorkflowEngineException(ACTIVITY_BIZ_SET_ASSIGNEE_ERROR, executionId);
        }
        if (!Objects.equals(task.getAssignee(), NO_ASSIGNEE)) {
            throw new WorkflowEngineException(ACTIVITY_CANT_SET_ASSIGNEE);
        }
    }

}
