package cn.axzo.workflow.es.service.impl;

import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.es.mapper.EsProcessInstanceMapper;
import cn.axzo.workflow.es.model.ProcessInstanceDocument;
import cn.axzo.workflow.es.model.ProcessTaskDocument;
import cn.axzo.workflow.es.service.EsProcessInstanceService;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.easyes.annotation.IndexName;
import org.dromara.easyes.annotation.rely.FieldType;
import org.dromara.easyes.core.biz.EsPageInfo;
import org.dromara.easyes.core.cache.GlobalConfigCache;
import org.dromara.easyes.core.conditions.index.LambdaEsIndexWrapper;
import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
import org.dromara.easyes.core.config.GlobalConfig;
import org.dromara.easyes.core.toolkit.FieldUtils;
import org.elasticsearch.ElasticsearchException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

import static cn.axzo.workflow.common.constant.BpmnConstants.ES_FIXED_ROUTING;
import static cn.axzo.workflow.es.common.code.ElasticSearchRespCode.ES_EXPENSIVE_QUERIES_NOT_SUPPORTED;
import static cn.axzo.workflow.es.common.code.ElasticSearchRespCode.ES_SEARCH_ERROR;

/**
 * 操作流程实例的 ES Service 实现
 *
 * @author wangli
 * @since 2024-09-27 11:09
 */
@Service
@AllArgsConstructor
@Slf4j
public class EsProcessInstanceServiceImpl implements EsProcessInstanceService {

    private final EsProcessInstanceMapper esProcessInstanceMapper;

    /**
     * 创建索引
     * <p>
     * 由于采用的是父子文档, 所以该接口会一并创建
     *
     * @return
     */
    @Override
    public Boolean createIndex() {
        IndexName annotation = AnnotationUtils.findAnnotation(ProcessInstanceDocument.class, IndexName.class);
        if (esProcessInstanceMapper.existsIndex(annotation.value())) {
            return false;
        }
        return esProcessInstanceMapper.createIndex();
    }

    /**
     * 删除流程实例索引
     *
     * @return
     */
    @Override
    public Boolean deleteIndex() {
        GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig();
        IndexName annotation = AnnotationUtils.findAnnotation(ProcessInstanceDocument.class, IndexName.class);
        return esProcessInstanceMapper.deleteIndex(globalConfig.getDbConfig().getIndexPrefix() + annotation.value());
    }

    @Override
    public Boolean updateIndex() {
        GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig();

        LambdaEsIndexWrapper<ProcessInstanceDocument> wrapper = new LambdaEsIndexWrapper<>();
        // 指定要更新哪个索引
        IndexName annotation = AnnotationUtils.findAnnotation(ProcessInstanceDocument.class, IndexName.class);
        wrapper.indexName(globalConfig.getDbConfig().getIndexPrefix() + annotation.value());
        wrapper.mapping("initiator_json_str", FieldType.TEXT);
        esProcessInstanceMapper.updateIndex(wrapper);
        return null;
    }

    /**
     * 新增流程实例文档文档
     *
     * @param processInstanceDocument
     * @return 成功的条数
     */
    @Override
    public Integer insert(String routing, ProcessInstanceDocument processInstanceDocument) {
        log.info("insert es document: {}", JSON.toJSONString(processInstanceDocument));
        return esProcessInstanceMapper.insert(routing, processInstanceDocument);
    }

    @Override
    public Integer insertBatch(String routing, Collection<ProcessInstanceDocument> processInstanceDocuments) {
        return esProcessInstanceMapper.insertBatch(routing, processInstanceDocuments);
    }

    /**
     * 更新流程实例文档
     *
     * @param routing
     * @param processInstanceDocument
     */
    @Override
    public Integer update(String routing, ProcessInstanceDocument processInstanceDocument) {
        return esProcessInstanceMapper.updateById(ES_FIXED_ROUTING, processInstanceDocument);
    }

    @Override
    public Integer delete(String processInstanceId) {
        return esProcessInstanceMapper.deleteById(ES_FIXED_ROUTING, processInstanceId);
    }

    @Override
    public BpmPageResult<ProcessInstanceDocument> search(InstanceSearchReqDTO dto) {
        if (!CollectionUtils.isEmpty(dto.getProcessInstanceIds())) {
            List<ProcessInstanceDocument> processInstanceDocuments = esProcessInstanceMapper
                    .selectList(new LambdaEsQueryWrapper<ProcessInstanceDocument>()
                            .in(FieldUtils.val(ProcessInstanceDocument::getId), dto.getProcessInstanceIds()));
            return new BpmPageResult<>(processInstanceDocuments, (long) processInstanceDocuments.size());
        }

        LambdaEsQueryWrapper<ProcessInstanceDocument> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.hasChild("process_task_document",
                        w -> w.matchPhrase(StringUtils.hasText(dto.getAssigneeName()), FieldUtils.val(ProcessTaskDocument::getAssigneeName), dto.getAssigneeName())
                                .eq(StringUtils.hasText(dto.getTenantId()), FieldUtils.val(ProcessTaskDocument::getAssigneeTenantId), dto.getTenantId())
                                .eq(StringUtils.hasText(dto.getOuId()), FieldUtils.val(ProcessTaskDocument::getAssigneeOuId), dto.getOuId())
                                .eq(StringUtils.hasText(dto.getPersonId()), FieldUtils.val(ProcessTaskDocument::getAssigneePersonId), dto.getPersonId())
                )
                .eq(StringUtils.hasText(dto.getBusinessStatus()), FieldUtils.val(ProcessInstanceDocument::getBusinessStatus), dto.getBusinessStatus())
                .and(StringUtils.hasText(dto.getProcessInstanceName()),
                        w -> w.or(i -> i.eq(FieldUtils.val(ProcessInstanceDocument::getProcessInstanceName), dto.getProcessInstanceName(), 1.2F))
                                .or(j -> j.match(FieldUtils.val(ProcessInstanceDocument::getProcessInstanceName), dto.getProcessInstanceName()))
                )
                .in(CollectionUtils.isEmpty(dto.getProcessInstanceIds()), FieldUtils.val(ProcessInstanceDocument::getId), dto.getProcessInstanceIds())
                .ge(Objects.nonNull(dto.getBeginStartTime()), ProcessInstanceDocument::getInstanceStartTime, dto.getBeginStartTime())
                .le(Objects.nonNull(dto.getOverStartTime()), ProcessInstanceDocument::getInstanceStartTime, dto.getOverStartTime())
                .ge(Objects.nonNull(dto.getBeginEndTime()), ProcessInstanceDocument::getInstanceEndTime, dto.getBeginEndTime())
                .le(Objects.nonNull(dto.getOverEndTime()), ProcessInstanceDocument::getInstanceEndTime, dto.getOverEndTime())
                .sortByScore()
                .orderByDesc(FieldUtils.val(ProcessInstanceDocument::getInstanceStartTime))
                .trackScores()
                .routing(ES_FIXED_ROUTING)
        ;
        EsPageInfo<ProcessInstanceDocument> pageInfo;
        try {
            pageInfo = esProcessInstanceMapper.pageQuery(wrapper, dto.getPageNo(), dto.getPageSize());
        } catch (Exception e) {
            Throwable rootCause = getRootCause(e);
            if (rootCause instanceof ElasticsearchException && rootCause.getMessage().contains("search.allow_expensive_queries")) {
                // 该问题涉及到集群参数调整，调整方法参考异常 CODE 中的说明
                throw new WorkflowEngineException(ES_EXPENSIVE_QUERIES_NOT_SUPPORTED);
            }
            log.warn("ES 搜索时发生异常：{}", e.getMessage(), e);
            throw new WorkflowEngineException(ES_SEARCH_ERROR, e.getMessage());
        }
        return new BpmPageResult<>(pageInfo.getList(), pageInfo.getTotal());
    }

    private Throwable getRootCause(Throwable throwable) {
        if (Objects.nonNull(throwable) && Objects.nonNull(throwable.getCause())) {
            return getRootCause(throwable.getCause());
        }
        return throwable;
    }
}
