
package com.tmerclub.cloud.search.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.product.bo.EsProductBO;
import com.tmerclub.cloud.common.product.vo.search.SpuSearchUpdateVO;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.search.constant.EsConstant;
import com.tmerclub.cloud.search.constant.EsIndexEnum;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.*;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.TopHitsAggregationBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.*;

/**
 * @author YXF
 * @date 2021/07/29
 */
@Component
public class EsSearchUtil {

    private static final Logger log = LoggerFactory.getLogger(EsSearchUtil.class);
    private static RestHighLevelClient restHighLevelClient;

    /**
     * 正常来说普通方法才是属于实体对象（也就是New出来的对象）的，spring注入是在容器中实例化对象，静态变量是无法注入的
     * 所以需要通过构造方法的方式来进行注入，或者使用@PostConstruct注解
     */
    @Autowired
    private EsSearchUtil(RestHighLevelClient restHighLevelClient) {
        EsSearchUtil.restHighLevelClient = restHighLevelClient;
    }

    private EsSearchUtil() {
    }


    public static void esSave(Long id, EsIndexEnum esIndexEnum, Object data) {
        IndexRequest request = new IndexRequest(esIndexEnum.value());
        request.id(String.valueOf(id));
        request.source(Json.toJsonString(data), XContentType.JSON);
        try {
            IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            log.info("es插入结果， {}", indexResponse.toString());
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error(e.toString());
            throw new LuckException("保存es信息异常", e);
        }
    }


    public static void esUpdateSpuBySpuIds(List<Long> spuIds, EsProductBO esProductBO) {
        if (CollUtil.isEmpty(spuIds)) {
            return;
        }
        String source = Json.toJsonString(esProductBO);
        try {
            BulkRequest request = new BulkRequest();
            // 准备更新的数据
            for (Long spuId : spuIds) {
                request.add(new UpdateRequest(EsIndexEnum.PRODUCT.value(), String.valueOf(spuId)).doc(source, XContentType.JSON));
            }
            //更新
            BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            if (bulkResponse.hasFailures()) {
                throw new LuckException(bulkResponse.buildFailureMessage());
            }
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (Exception e) {
            throw new LuckException(e.getMessage());
        }
    }


    /**
     *
     */
    public static SearchResponse search(SearchRequest searchRequest) {
        SearchResponse response;
        try {
            //2、执行检索请求
            response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            log.info("搜索返回结果：{}", response.toString());
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error(e.toString());
            throw new LuckException("搜索服务出了小差，请稍后再试", e);
        }
        return response;
    }


    /**
     *
     */
    public static QueryBuilder termsQueryByArray(String name, String data) {
        List<String> list = StrUtil.split(data, Constant.COMMA);
        return QueryBuilders.termsQuery(name, list);
    }


    /**
     *
     */
    public static QueryBuilder nestedQuery(String nestedName, String name, Long data) {
        return nestedQuery(nestedName, name, data.toString());
    }

    /**
     *
     */
    public static QueryBuilder nestedQuery(String nestedName, String name, String data) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.termsQuery(name, data));
        return QueryBuilders.nestedQuery(nestedName, boolQuery, ScoreMode.None);
    }

    @SuppressWarnings("rawtypes")
    public static QueryBuilder nestedQuery(String nestedName, String name, List list) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.termsQuery(name, list));
        return QueryBuilders.nestedQuery(nestedName, boolQuery, ScoreMode.None);
    }

    /**
     *
     */
    public static QueryBuilder nestedQueryByArray(String nestedName, String name, String data) {
        String[] ids = data.split(Constant.COMMA);
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        for (String brandId : ids) {
            boolQuery.should(QueryBuilders.termsQuery(name, brandId));
        }
        return QueryBuilders.nestedQuery(nestedName, boolQuery, ScoreMode.None);
    }

    /**
     *
     */
    public static RangeQueryBuilder rangeQuery(String name, Long minValue, Long maxValue) {
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(name);
        if (Objects.nonNull(minValue)) {
            rangeQueryBuilder.gte(minValue);
        }
        if (Objects.nonNull(maxValue)) {
            rangeQueryBuilder.lte(maxValue);
        }
        return rangeQueryBuilder;
    }

    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name) {
        return nestedAggregation(nestedName, fieldName, name, null, null);
    }

    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, String[] fetchSource) {
        return nestedAggregation(nestedName, fieldName, name, fetchSource, null);
    }

    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, Integer size) {
        return nestedAggregation(nestedName, fieldName, name, null, size);
    }

    /**
     *
     */
    public static NestedAggregationBuilder nestedAggregation(String nestedName, String fieldName, String name, String[] fetchSource, Integer size) {
        if (Objects.isNull(size)) {
            size = 1;
        }
        NestedAggregationBuilder nested = AggregationBuilders.nested(nestedName, nestedName);
        TermsAggregationBuilder terms = AggregationBuilders.terms(name).field(fieldName).size(10);
        TopHitsAggregationBuilder topHits = AggregationBuilders
                .topHits(EsConstant.TOP_HITS_DATA)
                .size(size);
        // 指定响应的数据字段
        if (ArrayUtil.isNotEmpty(fetchSource)) {
            topHits.fetchSource(fetchSource, null);
        }
        terms.subAggregation(topHits);
        nested.subAggregation(terms);
        return nested;
    }

    public static void esUpdate(Long id, EsIndexEnum esIndexEnum, Object data) {
        try {
            UpdateRequest request = new UpdateRequest(esIndexEnum.value(), String.valueOf(id)).retryOnConflict(3);
            // 存在就更新，不存就保存
            request.docAsUpsert(true);
            request.doc(Json.toJsonString(data), XContentType.JSON);
            UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
            log.info("es更新结果， {}", updateResponse.toString());
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error(e.toString());
            throw new LuckException("更新es信息异常", e);
        }
    }

    /**
     * 滚动检索
     */
    public static SearchResponse scroll(SearchScrollRequest searchScrollRequest) {
        SearchResponse response;
        try {
            //2、执行检索请求
            response = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);

            log.debug("滚动搜索返回结果：{}", response.toString());
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error(e.toString());
            throw new LuckException("滚动搜索服务出了点小差，请稍后再试", e);
        }
        return response;
    }

    /**
     * 删除滚动
     */
    public static void clearScroll(String scrollId) {

        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(scrollId);
        ClearScrollResponse clearScrollResponse;
        try {
            //2、执行检索请求
            clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error(e.toString());
            throw new LuckException("删除滚动服务出了点小差，请稍后再试", e);
        }
        if (!clearScrollResponse.isSucceeded()) {
            log.error("删除滚动返回结果：{}", clearScrollResponse);
            throw new LuckException("");
        }
        clearScrollResponse.isSucceeded();
    }

    /**
     * 批量更新商品库存
     */
    public static void changeSpuStockBatch(List<SpuSearchUpdateVO> esSpuList) {
        if (CollUtil.isEmpty(esSpuList)) {
            return;
        }
        Map<Long, Script> map = new HashMap<>(esSpuList.size());
        for (SpuSearchUpdateVO esSpuUpdate : esSpuList) {
            String code = "= ";
            Script script = new Script(ScriptType.INLINE,
                    "painless",
                    "ctx._source.stock " + code + esSpuUpdate.getCount(),
                    Collections.emptyMap());
            map.put(esSpuUpdate.getSpuId(), script);
        }
        updateStockByPainless(map);
    }

    public static void updateStockByPainless(Map<Long, Script> spuMap) {
        BulkRequest request = new BulkRequest();
        spuMap.forEach((spuId, script) -> {
            UpdateRequest updateRequest = new UpdateRequest(EsIndexEnum.PRODUCT.value(), String.valueOf(spuId));
            updateRequest.script(script);
            request.add(updateRequest);
        });
        try {
            BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            log.info("painless脚本更新商品{}", Json.toJsonString(bulkResponse));
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error("更新es商品库存信息异常信息：{}，商品信息：{}", e, spuMap);
            throw new LuckException("更新es商品库存信息异常信息 " + e);
        }
    }

    public static void updateByPainless(Map<Long, Script> map, String indexName) {
        BulkRequest request = new BulkRequest();
        map.forEach((id, script) -> {
            UpdateRequest updateRequest = new UpdateRequest(indexName, String.valueOf(id));
            updateRequest.script(script);
            request.add(updateRequest);
        });
        try {
            BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
            log.info("painless脚本执行的索引：{}， 数据：{}", indexName, bulkResponse);
        } catch (SocketTimeoutException e) {
            log.error("es连接超时", e);
            throw new LuckException("当前请求人数太多，请稍后重试");
        } catch (IOException e) {
            log.error("更新es索引：{}， 异常信息：{}，商品信息：{}", indexName, e, map);
            throw new LuckException("更新es商品库存信息异常信息 " + e);
        }
    }
}
