package cn.reghao.tnb.search.app.es;

import cn.reghao.tnb.search.app.model.po.NginxLog;
import cn.reghao.tnb.search.app.model.po.VideoText;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.ShardStatistics;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.CountRequest;
import co.elastic.clients.elasticsearch.core.CountResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import co.elastic.clients.json.JsonData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2025-03-12 10:51:21
 */
@Slf4j
@Service
public class SearchService {
    private final ElasticsearchClient esClient;

    public SearchService(ElasticService elasticService) throws Exception {
        this.esClient = elasticService.getElasticsearchClient();
    }

    public void searchOne(String indexName, String searchText) throws IOException {
        SearchResponse<NginxLog> searchResponse = esClient.search(s -> s
                .index(indexName)
                // 搜索请求的查询部分（搜索请求也可以有其他组件，如聚合）
                .query(q -> q
                        // 在众多可用的查询变体中选择一个。我们在这里选择匹配查询（全文搜索）
                        .match(t -> t
                                .field("name")
                                .query(searchText))), NginxLog.class);
        TotalHits total = searchResponse.hits().total();
        boolean isExactResult = total != null && total.relation() == TotalHitsRelation.Eq;
        if (isExactResult) {
            log.info("search has: {} results", total.value());
        } else {
            log.info("search more than : {} results", total.value());
        }
        List<Hit<NginxLog>> hits = searchResponse.hits().hits();
        for (Hit<NginxLog> hit : hits) {
            NginxLog source = hit.source();
            log.info("Found result: {}", source);
        }
    }

    public List<NginxLog> searchByPage(String indexName, int pn, String searchField, String searchText) throws IOException {
        int ps = 100;
        String sortField = "timeIso8601";
        Query query = RangeQuery.of(r -> r.field("age").gte(JsonData.of(8)))._toQuery();
        Query query11 = getOrQuery();
        Query query1 = getAndQuery();
        Query query12 = getTermQuery();

        int start = (pn-1)*ps;
        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index(indexName)
                .query(query1)
                .from(start)
                .size(ps)
                // 按 id 字段降序排列
                .sort(f -> f.field(o -> o.field(sortField).order(SortOrder.Desc)))
        );

        SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
        /*List<Hit<NginxLog>> hits = searchResponse.hits().hits();
        for (Hit<NginxLog> hit : hits) {
            NginxLog product = hit.source();
            log.info("search page result: {}", product);
        }*/
        return searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
    }

    public void searchAll(String indexName) throws IOException {
        String fieldName1 = "url.raw";
        String fieldValue1 = "/";

        String fieldName2 = "requestMethod.raw";
        String fieldValue2 = "POST";

        String fieldName3 = "status";
        String fieldValue3 = "200";

        String fieldName4 = "vip";
        Boolean fieldValue4 = false;

        String keyword = "";
        QueryStringQuery stringQuery = new QueryStringQuery.Builder()
                // 查询的字段
                .fields("title")
                .query(keyword)
                .build();
        Query query = new Query.Builder()
                .bool(BoolQuery.of(b -> b
                        .must(m -> m.term(t -> t.field("vip").value(FieldValue.of(true))))
                        .must(m -> m.queryString(stringQuery))))
                .build();

        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q.bool(b -> b
                                .must(m -> m.term(t -> t.field(fieldName4).value(FieldValue.of(fieldValue4))))
                        //.must(m -> m.term(t -> t.field("name").value(FieldValue.of("test"))))
                ))
        );

        String keyword1 = "大奶";
        SearchRequest searchRequest1 = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q.bool(b -> b
                        .must(m -> m.term(t -> t.field(fieldName4).value(FieldValue.of(fieldValue4))))
                        .must(m -> m.queryString(t -> t.fields("title").query(keyword1)))
                ))
        );

        SearchRequest searchRequest2 = SearchRequest.of(s -> s
                .index(indexName)
        );

        //SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
        //List<NginxLog> list = searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
        SearchResponse<VideoText> searchResponse = esClient.search(searchRequest2, VideoText.class);
        List<VideoText> list = searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
        System.out.println();
    }

    public void searchByField(String indexName, String fieldName, String fieldValue) throws IOException {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(query -> query.match(match -> match.field(fieldName).query(fieldValue)))
        );

        SearchResponse<NginxLog> searchResponse = esClient.search(request, NginxLog.class);
        log.info("search result: {}", searchResponse);
    }

    public void search(String index) throws IOException {
        String fieldName = "url";
        String fieldValue = "/api";
        // 构造查询条件
        SearchRequest searchRequest = SearchRequest.of(s -> s.index(index)
                /*.sort(s1 -> s1.field(f -> f.field(fieldName).order(SortOrder.Asc)))
                .scroll(s2 -> s2.offset(0))*/
                .from(0)
                .size(500)
                .query(q -> q.match(m -> m.field(fieldName).query(FieldValue.of(fieldValue))))
        );

        SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
        HitsMetadata<NginxLog> hitsMetadata = searchResponse.hits();
        //得到总数
        TotalHits totalHits = hitsMetadata.total();
        Double maxScore = hitsMetadata.maxScore();
        //拿到匹配的数据
        List<Hit<NginxLog>> hits = hitsMetadata.hits();
        //拿到_source中的数据
        List<NginxLog> nginxLogs = hits.stream().map(Hit::source).collect(Collectors.toList());

        //最大分数
        System.out.println(searchResponse.maxScore());
        //分片数
        System.out.println(searchResponse.shards());
        //是否超时
        System.out.println(searchResponse.timedOut());
    }

    public void search() throws IOException {
        Query query = getExistsQuery();

        String index = "nginx_log";
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(index)
                .query(query)
                .build();
        SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
        TotalHits totalHits = searchResponse.hits().total();
        List<NginxLog> list = searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
    }

    public void count(String index) throws IOException {
        Query query1 = getMatchQuery();
        // remoteAddr 字段为空
        Query query2 = BoolQuery.of(b ->
                b.mustNot(m -> m.exists(t -> t.field("remoteAddr"))
                ))._toQuery();
        // remoteAddr 字段不为空
        Query query3 = BoolQuery.of(b ->
                b.must(m -> m.exists(t -> t.field("remoteAddr"))
                ))._toQuery();

        CountRequest countRequest = CountRequest.of(s -> s
                        .index(index)
                //.query(query3)
        );

        CountResponse countResponse = esClient.count(countRequest);
        long total = countResponse.count();
        ShardStatistics shardStatistics = countResponse.shards();
        System.out.println("total -> " + total);
    }

    /**
     * 等值查询
     *
     * @param
     * @return
     * @date 2025-03-12 13:45:17
     */
    public Query getTermQuery() {
        String fieldName1 = "url";
        String fieldValue1 = "/datareceive/ReceiveData/SendContentResult\"";

        String fieldName2 = "requestMethod";
        String fieldValue2 = "POST";

        Query query = TermQuery.of(t -> t
                .field(fieldName1).value(FieldValue.of(fieldValue1))
        )._toQuery();
        return query;
    }

    /**
     * MatchQuery 搜索时, 首先会解析查询字符串, 进行分词，然后查询
     * TermQuery 搜索时, 会根据输入的查询内容进行搜索, 并不会解析查询内容，对它分词
     *
     * @param
     * @return
     * @date 2025-03-12 15:04:22
     */
    public Query getMatchQuery() {
        String fieldName = "host";
        String searchText = "api.iquizoo.com";

        Query query = MatchQuery.of(m -> m.field(fieldName).query(searchText))._toQuery();
        return query;
    }

    /**
     * 多值查询, 相当于 SQL 语句中的 in 查询
     *
     * @param
     * @return
     * @date 2025-03-12 14:12:38
     */
    public Query getTermsQuery() {
        String fieldName = "status";
        List<FieldValue> fieldValueList = List.of(FieldValue.of("401"), FieldValue.of("500"));

        Query query = TermsQuery.of(t ->
                t.field(fieldName).terms(TermsQueryField.of(q -> q.value(fieldValueList)))
        )._toQuery();
        return query;
    }

    /**
     * 或查询, 相当于 SQL 查询
     * SELECT * FROM test1 where (uid = 1 or uid =2) and phone = 12345678919
     *
     * @param
     * @return
     * @date 2025-03-12 13:46:26
     */
    public Query getOrQuery() {
        Query query = BoolQuery.of(b ->
                b.should(m -> m.term(t -> t.field("status").value(FieldValue.of(401))))
                        .should(m -> m.term(t -> t.field("status").value(FieldValue.of(403))))
                        .must(m -> m.term(t -> t.field("host").value(FieldValue.of("api.iquizoo.com"))))
        )._toQuery();
        return query;
    }

    public Query getAndQuery() {
        String fieldName1 = "url.raw";
        String fieldValue1 = "/datareceive/ReceiveData/SendContentResult";

        String fieldName2 = "requestMethod";
        String fieldValue2 = "POST";

        Query query = BoolQuery.of(b -> b
                .must(m -> m.term(t -> t.field(fieldName1).value(FieldValue.of(fieldValue1))))
                .must(m -> m.term(t -> t.field(fieldName2).value(FieldValue.of(fieldValue2))))
        )._toQuery();
        return query;
    }

    /**
     * 模糊查询, 相当于 SQL 语句中的 like 查询
     *
     * @param
     * @return
     * @date 2025-03-12 14:11:45
     */
    public Query getWildcardQuery() {
        Query query = BoolQuery.of(b ->
                b.must(m -> m.wildcard(t -> t.field("url").value("*result*")))
        )._toQuery();
        return query;
    }

    /**
     * 存在查询, 相当于 SQL 语句中的 exist
     *
     * @param
     * @return
     * @date 2025-03-12 14:20:26
     */
    public Query getExistsQuery() {
        // 判断字段是否存在
        Query query = ExistsQuery.of(t -> t.field("host"))._toQuery();
        return query;
    }

    /**
     * 范围查询, 相当于 SQL 语句中的 > 和 <
     * gt 是大于，lt 是小于，gte 是大于等于，lte 是小于等于
     *
     * @param
     * @return
     * @date 2025-03-12 14:21:26
     */
    public Query getRangeQuery() {
        int status1 = 404;
        int status2 = 600;
        Query query = RangeQuery.of(t -> t.field("status").gte(JsonData.of(status1)).lte(JsonData.of(status2)))._toQuery();
        return query;
    }

    /**
     * 正则查询
     *
     * @param
     * @return
     * @date 2025-03-12 14:22:18
     */
    public Query getRegexpQuery() {
        Query query = RegexpQuery.of(t -> t.field("host").value("api.*"))._toQuery();
        return query;
    }

    public Query getQuery() {
        MatchAllQuery.of(m -> m.queryName("host"))._toQuery();
        // 一般情况下有一个单词错误的情况下，fuzzy 查询可以找到另一个近似的词来代替，主要有以下场景：
        //
        //修改一个单词，如：box--->fox。
        //移除一个单词，如：black-->lack。
        //插入一个单词，如：sic-->sick。
        //转换两个单词顺序，如：act-->cat。
        FuzzyQuery.of(f -> f.field("host").value("lonel"));
        // 通过指定字段的前缀进行查询
        Query query = PrefixQuery.of(p -> p.field("host").value("api"))._toQuery();
        return query;
    }

    /**
     * 聚合查询, 相当于 SQL 的 group by
     *
     * @param
     * @return
     * @date 2025-03-12 15:06:48
     */
    public void aggregate(String index) throws Exception {
        String aggField1 = "status";
        String aggField2 = "remoteAddr";
        String aggField3 = "httpUserAgent.raw";
        String aggField4 = "url.raw";
        String aggField5 = "timeIso8601";

        String fieldName1 = "url.raw";
        String fieldValue1 = "/base/Device/PageList";

        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(index)
                .query(q -> q.bool(b -> b.must(m -> m.term(t -> t.field(fieldName1).value(FieldValue.of(fieldValue1))))))
                .size(10000)
                .aggregations("first_agg", a->a.terms(t->t.field(aggField5).size(65535)))
                //.aggregations("agg1", a->a.histogram(t->t.field("httpUserAgent").interval(50.0)))
                //.aggregations("agg1", a->a.sum(t->t.field("httpUserAgent")))
                //.aggregations("second_agg", a->a.avg(t->t.field("status")))
                .build();

        SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
        TotalHits totalHits = searchResponse.hits().total();
        Map<String, Aggregate> resultMap = searchResponse.aggregations();

        List<Long> countList = new ArrayList<>();
        resultMap.forEach((k, v) -> {
            Object value = v._get();
            if (value instanceof StringTermsAggregate) {
                StringTermsAggregate terms = (StringTermsAggregate) value;
                List<StringTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    String groupKey = (String) bucket.key()._get();
                    long count = bucket.docCount();
                    countList.add(count);
                    //System.out.println(groupKey + " : " + IpTool.getLocation(groupKey) + " -> " + count);
                    System.out.println(groupKey + " : " + count);
                });
                System.out.println("bucket size = " + list.size());
            } else if (value instanceof LongTermsAggregate) {
                LongTermsAggregate terms = (LongTermsAggregate) value;
                List<LongTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    String groupKey = bucket.key();
                    String groupKeyStr = bucket.keyAsString();
                    long count = bucket.docCount();
                    countList.add(count);
                    System.out.println(groupKeyStr + " : " + count);
                });
            } else {
                System.out.println(value);
            }
        });

        System.out.println("total = " + countList.stream().mapToLong(Long::longValue).sum());
    }
}
