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

import cn.axzo.basics.common.BeanMapper;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO;
import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO;
import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc;
import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper;
import cn.axzo.workflow.core.service.ExtAxReModelService;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_QUERY_ERROR;
import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_ILLEGAL;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.service.impl.ExtAxModelDocServiceImpl.buildQueryWrapper;


/**
 * 自定义的获取指定流程实例或指定流程定义版本对应的模型中关联的文档元数据
 *
 * @author wangli
 * @since 2025-04-02 10:25
 */
public class CustomGetModelDocsCmd implements Command<List<DocBaseVO>> {

    private String processInstanceId;
    /**
     * eg. we1:1:202504021034000000001 or we1
     */
    private String processDefinitionId;
    private List<String> getProcessDefinitionIds;
    private String tenantId;
    private Boolean filterEnable = true;
    /**
     * 该属性，只对流程实例入参生效，过滤出发起流程时，勾选的文档
     */
    private Boolean filterSelect = false;
    private final ExtAxModelDocMapper extAxModelDocMapper;
    private final ExtAxReModelService extAxReModelService;

    public CustomGetModelDocsCmd(String processInstanceId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this(processInstanceId, false, extAxModelDocMapper, extAxReModelService);
    }

    public CustomGetModelDocsCmd(String processInstanceId, Boolean filterSelect, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this(processInstanceId, filterSelect, true, extAxModelDocMapper, extAxReModelService);
    }

    public CustomGetModelDocsCmd(String processInstanceId, Boolean filterSelect, Boolean filterEnable, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this.processInstanceId = processInstanceId;
        this.filterSelect = filterSelect;
        this.extAxModelDocMapper = extAxModelDocMapper;
        this.filterEnable = filterEnable;
        this.extAxReModelService = extAxReModelService;
    }

    public CustomGetModelDocsCmd(String processDefinitionId, String tenantId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this.processDefinitionId = processDefinitionId;
        this.tenantId = StringUtils.hasText(tenantId) ? tenantId : NO_TENANT_ID;
        this.extAxModelDocMapper = extAxModelDocMapper;
        this.extAxReModelService = extAxReModelService;
    }

    public CustomGetModelDocsCmd(String processInstanceId, String processDefinitionId, List<String> processDefinitionIds, String tenantId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this.processInstanceId = processInstanceId;
        this.processDefinitionId = processDefinitionId;
        this.getProcessDefinitionIds = processDefinitionIds;
        this.tenantId = tenantId;
        this.extAxModelDocMapper = extAxModelDocMapper;
        this.extAxReModelService = extAxReModelService;
    }

    public CustomGetModelDocsCmd(String processInstanceId, String processDefinitionId, String tenantId, Boolean filterSelect, Boolean filterEnable, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) {
        this.processInstanceId = processInstanceId;
        this.processDefinitionId = processDefinitionId;
        this.tenantId = tenantId;
        this.filterSelect = filterSelect;
        this.filterEnable = filterEnable;
        this.extAxModelDocMapper = extAxModelDocMapper;
        this.extAxReModelService = extAxReModelService;
    }

    @Override
    public List<DocBaseVO> execute(CommandContext commandContext) {
        check();
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        RepositoryService repositoryService = processEngineConfiguration.getRepositoryService();

        List<ProcessDefinition> processDefinitions = new ArrayList<>();
        List<Long> enableDocIds = new ArrayList<>();
        if (StringUtils.hasText(processInstanceId)) {
            HistoryService historyService = processEngineConfiguration.getHistoryService();
            HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (filterSelect) {
                if (Objects.isNull(instance)) {
                    return Collections.emptyList();
                }
                if (Objects.equals(PROCESSING.getStatus(), instance.getBusinessStatus())) {
                    RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
                    List<Long> docIds = (List<Long>) runtimeService.getVariable(processInstanceId, SIGN_PROCESS_ENABLE_DOC_IDS, List.class);
                    if (!CollectionUtils.isEmpty(docIds)) {
                        enableDocIds.addAll(docIds);
                    }
                } else {
                    HistoricVariableInstance variableInstance = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_PROCESS_ENABLE_DOC_IDS).singleResult();
                    if (Objects.nonNull(variableInstance)) {
                        enableDocIds.addAll((List<Long>) variableInstance.getValue());
                    }
                }
            }
            processDefinitions.add(repositoryService.createProcessDefinitionQuery().processDefinitionId(instance.getProcessDefinitionId()).singleResult());
        } else {
            List<ProcessDefinition> definitions = new ArrayList<>();
            if (StringUtils.hasText(processDefinitionId)) {
                definitions.addAll(repositoryService.createProcessDefinitionQuery()
                        .processDefinitionKey(parseProcessDefinitionKey(processDefinitionId))
                        .list());
            } else {
                List<String> definitionIds = getProcessDefinitionIds.stream().filter(StringUtils::hasText).map(this::parseProcessDefinitionKey).collect(Collectors.toList());
                definitions.addAll(repositoryService.createNativeProcessDefinitionQuery()
                        .sql("SELECT * FROM ACT_RE_PROCDEF WHERE KEY_ IN " + definitionIds.stream().map(i -> "'" + i + "'").collect(Collectors.joining(",", "(", ")")))
                        .list());
            }


            if (CollectionUtils.isEmpty(definitions)) {
                return Collections.emptyList();
            }
            // 按 key 分组，每组取 version 最大的 ProcessDefinition
            Map<String, List<ProcessDefinition>> maxVersionMap = definitions.stream()
//                    .filter(i -> i.getTenantId().equals(tenantId))
                    .collect(Collectors.groupingBy(
                            ProcessDefinition::getKey,
                            Collectors.toList()));

            maxVersionMap.forEach((k, definitionList) -> {
                Optional<ProcessDefinition> first = definitionList.stream()
                        .filter(i -> Objects.equals(i.getKey(), k))
                        .filter(i -> i.getTenantId().equals(tenantId))
                        .max(Comparator.comparing(ProcessDefinition::getVersion));
                if (first.isPresent()) {
                    ProcessDefinition processDefinition = first.get();
                    CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
                    Model model = commandExecutor.execute(new CustomGetModelByDefinitionIdCmd(processDefinition.getId()));
                    BpmnModelExtVO modelStatus = extAxReModelService.getStatusByModelId(model.getId());
                    if (Objects.equals(0, modelStatus.getStatus())) {
                        processDefinitions.add(definitions.stream()
                                .filter(i -> Objects.equals(i.getKey(), k))
                                .filter(i -> i.getTenantId().equals(NO_TENANT_ID))
                                .max(Comparator.comparing(ProcessDefinition::getVersion)).get());
                    } else {
                        processDefinitions.add(processDefinition);
                    }
                } else {
                    processDefinitions.add(first.orElseGet(() -> definitions.stream()
                            .filter(i -> Objects.equals(i.getKey(), k))
                            .filter(i -> i.getTenantId().equals(NO_TENANT_ID))
                            .max(Comparator.comparing(ProcessDefinition::getVersion)).get()));
                }
            });
        }

        List<ExtAxModelDoc> docs = new ArrayList<>();
        if (!CollectionUtils.isEmpty(processDefinitions)) {
            for (ProcessDefinition processDefinition : processDefinitions) {
                ExtAxModelDoc query = new ExtAxModelDoc();
                query.setModelKey(processDefinition.getKey());
                if (Objects.equals(Boolean.TRUE, filterEnable)) {
                    query.setStatus(filterEnable);
                }
                query.setTenantId(processDefinition.getTenantId());
                query.setTempFile(false);
                docs.addAll(extAxModelDocMapper.selectList(buildQueryWrapper(query)));
            }
        }

        if (filterSelect) {
            docs = docs.stream().filter(i -> enableDocIds.contains(i.getId())).collect(Collectors.toList());
        }
        return BeanMapper.copyList(docs, DocBaseVO.class, (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType())));
    }

    private String parseProcessDefinitionKey(String processDefinitionId) {
        if (StringUtils.hasText(processDefinitionId)) {
            return processDefinitionId.split(":")[0];
        }
        throw new WorkflowEngineException(PROCESS_DEFINITION_ID_ILLEGAL);
    }

    private void check() {
        if (!StringUtils.hasText(processInstanceId) && (!StringUtils.hasText(processDefinitionId)) && CollectionUtils.isEmpty(getProcessDefinitionIds)) {
            throw new WorkflowEngineException(MODEL_FILE_QUERY_ERROR);
        }
    }
}
