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

import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.dto.SignatureDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.ListUtils;
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

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

import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER;
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_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;

/**
 * 自定义(同步)审批通过任务的命令器实现
 *
 * @author wangli
 * @since 2024/1/4 15:50
 */
public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Serializable {

    private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskCmd.class);
    private final String taskId;
    /**
     * advice 统一均为审批节点的审批意见，一般为用户在同意、驳回时填写的内容
     * 有值时，在日志中一般出现在 operationDesc 的下方
     */
    private final String advice;
    /**
     * operationDesc 统一为审批节点的动作描述，例如：某某转交、加签某某等
     * 在日志中一般出现在节点名称的下方
     * 不传,默认"已通过"
     */
    private String operationDesc;
    /**
     * 附件, 可为空
     */
    private final List<AttachmentDTO> attachmentList;
    private final BpmnTaskDelegateAssigner approver;
    /**
     * 下级节点的审批,可为空
     */
    private final BpmnTaskDelegateAssigner nextApprover;

    /**
     * 指定节点类型
     */
    private List<BpmnFlowNodeType> nodeTypes;

    @Override
    public String paramToJsonString() {
        Map<String, Object> params = new HashMap<>();
        params.put("taskId", taskId);
        params.put("advice", advice);
        params.put("operationDesc", operationDesc);
        params.put("attachmentList", JSON.toJSONString(attachmentList));
        params.put("approver", JSON.toJSONString(approver));
        params.put("nextApprover", JSON.toJSONString(nextApprover));
        params.put("nodeTypes", JSON.toJSONString(nodeTypes));
        return JSON.toJSONString(params);
    }

    public CustomApproveTaskCmd(BpmnTaskAuditDTO dto) {
        this(dto, null);
        if (Objects.nonNull(dto.getOperationDesc())) {
            this.operationDesc = dto.getOperationDesc();
        }
    }

    public CustomApproveTaskCmd(BpmnTaskAuditDTO dto, String operationDesc) {
        this.taskId = dto.getTaskId();
        this.advice = dto.getAdvice();
        this.attachmentList = dto.getAttachmentList();
        this.approver = dto.getApprover();
        this.nextApprover = dto.getNextApprover();
        this.nodeTypes = dto.getNodeTypes();
        // 这里的不能直接使用字符串的比较，因为外部可能传入空字符串，比如发起人的通过时，就是传入的空字符串
        if (Objects.nonNull(operationDesc)) {
            this.operationDesc = operationDesc;
        } else {
            this.operationDesc = "已同意";
        }
    }

    @Override
    /**
     * TODO 重要说明，该类与 CustomApproveTaskWithFormCmd 作用等同，当逻辑有调整时，需同步调整
     */
    public Void execute(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoricTaskInstanceQuery taskQuery =
                processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
        TaskService taskService = processEngineConfiguration.getTaskService();

        HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult();

        TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult();
        validTask(historicTaskInstance, task, approver, nodeTypes);

        // TODO 所有的跟 Task 相关的动作都可以在这里进行扩展，用于扩展八大按钮标准动作以外的一些逻辑，但这里需要结合 Spring 能力，需设计好扩展点，否则无法进行扩展
        // 其他动态也应该在类似的地方预留扩展点

        if (StringUtils.hasLength(advice)) {
            Authentication.setAuthenticatedUserId(approver.buildAssigneeId());
            addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice);
            Authentication.setAuthenticatedUserId(null);
        }

        batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, approver);

        Authentication.setAuthenticatedUserId(Objects.nonNull(approver) ? approver.buildAssigneeId() : null);
        addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc);
        Authentication.setAuthenticatedUserId(null);

        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        if (Objects.nonNull(nextApprover)) {
            // 主动设置下级审批人
            runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_SPECIFY_NEXT_APPROVER,
                    nextApprover);
        }
        task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus());

        // 记录电子签名的图片
        recordSignature(task, runtimeService);

        resetApproverNode(task.getProcessInstanceId());

        if (Objects.nonNull(approver)) {
            runtimeService.setVariable(task.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, approver);
        }

        executeSynchronous(task, taskService, runtimeService);
        return null;
    }

    private void resetApproverNode(String processInstanceId) {
        ExtAxProcessLogService logService = SpringContextUtils.getBean(ExtAxProcessLogService.class);
        ExtAxProcessLog logQuery = new ExtAxProcessLog();
        logQuery.setProcessInstanceId(processInstanceId);
        logQuery.setTaskId(taskId);
        logQuery.setStatus(PROCESSING.getStatus());
        List<ExtAxProcessLog> logs = logService.genericQuery(logQuery);
        if (CollectionUtils.isEmpty(logs)) {
            return;
        }
        if (CollectionUtils.isEmpty(logs.get(0).getAssigneeFull())) {
            return;
        }
        approver.setNodeId(logs.get(0).getAssigneeFull().get(0).getNodeId());
    }

    private void recordSignature(TaskEntity task, RuntimeService runtimeService) {
        List<SignatureDTO> signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class);
        if (Objects.isNull(signatures)) {
            signatures = new ArrayList<>();
        }
        Optional<SignatureDTO> any = signatures.stream()
                .filter(i -> Objects.equals(i.getActivityId(), task.getTaskDefinitionKey())).findAny();
        SignatureDTO dto = any.orElse(new SignatureDTO()
                .setActivityId(task.getTaskDefinitionKey())
                .setActivityName(task.getName())
                .setSignatures(new ArrayList<>()));
        ListUtils.emptyIfNull(attachmentList).stream()
                .filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature))
                .findFirst()
                .ifPresent(attachment -> dto.getSignatures().add(0,
                        new SignatureDTO.SignDetail()
                                .setSignature(attachment.getUrl())
                                .setAdvice(advice)));
        if (!any.isPresent()) {
            signatures.add(dto);
        }

        if (!CollectionUtils.isEmpty(signatures)) {
            runtimeService.setVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, signatures);
        }
    }

    private void executeSynchronous(Task task, TaskService taskService, RuntimeService runtimeService) {
        if (StringUtils.hasText(task.getExecutionId())) {
            // 正常完成流程任务，审批通过
            taskService.complete(task.getId(), runtimeService.getVariables(task.getExecutionId()));
        } else {
            // 特殊的完成单独创建的任务
            taskService.complete(task.getId());
        }
    }

}
