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

import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.web.log.NginxLog;
import cn.reghao.tnb.search.esapp.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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2025-03-12 10:51:21
 */
@Slf4j
@Service
public class SearchService {
    private final String timeZone = "+08:00";
    private final ElasticsearchClient esClient;

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

    public long count(String index, Query query) {
        Query combinedQuery = Query.of(q -> q.matchAll(m -> m));
        Query query1 = EsQuery.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();
        String fieldName  = "requestTimestamp";
        Query rangeQuery = EsQuery.getRangeQuery(fieldName);

        CountRequest countRequest = CountRequest.of(s -> s.index(index).query(query));
        long total = -1;
        try {
            CountResponse countResponse = esClient.count(countRequest);
            total = countResponse.count();
            ShardStatistics shardStatistics = countResponse.shards();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return total;
    }

    public void searchByText(String indexName, String fieldName, String fieldValue) throws IOException {
        SearchResponse<NginxLog> searchResponse = esClient.search(s -> s
                .index(indexName)
                /*.sort(s1 -> s1.field(f -> f.field(fieldName).order(SortOrder.Asc)))
                .scroll(s2 -> s2.offset(0))*/
                .from(0)
                .size(100)
                // 搜索请求的查询部分（搜索请求也可以有其他组件，如聚合）
                .query(q -> q
                        // 在众多可用的查询变体中选择一个。我们在这里选择匹配查询（全文搜索）
                        .match(t -> t.field(fieldName).query(fieldValue))), 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);
        }
    }

    /**
     * 分页查询
     *
     * @param
     * @return
     * @date 2025-12-25 15:54:34
     */
    public List<NginxLog> searchByPage(String indexName, int pageSize, int pageNumber,
                                       Query query, String sortField, SortOrder sortOrder) {
        int start = (pageNumber-1)*pageSize;
        SearchRequest searchRequest = SearchRequest.of(s -> s
                .index(indexName)
                .query(query)
                .from(start)
                .size(pageSize)
                // 按指定字段升序或降序排列
                .sort(f -> f.field(o -> o.field(sortField).order(sortOrder)))
        );

        try {
            SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
            HitsMetadata<NginxLog> hitsMetadata = searchResponse.hits();
            //得到总数
            TotalHits totalHits = hitsMetadata.total();
            long total = totalHits.value();
            Double maxScore = hitsMetadata.maxScore();
            //拿到匹配的数据
            List<Hit<NginxLog>> hits = hitsMetadata.hits();
            //拿到_source中的数据
            List<NginxLog> nginxLogs = hits.stream().map(Hit::source).collect(Collectors.toList());
            return nginxLogs;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    public void searchAll(String indexName) throws IOException {
        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();
    }

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

        String fieldName = "status";
        String fieldValue = "200";
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(index)
                .size(0)
                .query(q -> q.bool(b -> b.must(m -> m.term(t -> t.field(fieldName).value(FieldValue.of(fieldValue))))))
                //.aggregations("first_agg", a->a.terms(t->t.field(aggregateField).size(65535)))
                /*.aggregations("agg1", a -> a.dateRange(t -> t.field(aggField5).format("yyyy-MM-dd HH:mm:ss")
                        .ranges(r -> r.from(new Function<>() {
                            @Override
                            public ObjectBuilder<FieldDateMath> apply(FieldDateMath.Builder builder) {
                                return builder.expr("2023-11-20 00:00:00");
                            }
                        }).to(new Function<>() {
                            @Override
                            public ObjectBuilder<FieldDateMath> apply(FieldDateMath.Builder builder) {
                                return builder.expr("2023-11-20 23:59:59");
                            }
                        }))))*/
                //
                //.size(0)
                // 按天进行聚合
                .aggregations("agg1", a->a.dateHistogram(t->t.field(aggregateField).calendarInterval(CalendarInterval.Day).minDocCount(0)))
                //.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();
        long total = totalHits.value();
        Map<String, Aggregate> resultMap = searchResponse.aggregations();

        Map<Long, Long> map = new TreeMap<>();
        resultMap.forEach((k, v) -> {
            Object value = v._get();
            if (value instanceof StringTermsAggregate terms) {
                List<StringTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    String groupKey = (String) bucket.key()._get();
                    long count = bucket.docCount();
                    //System.out.println(groupKey + " : " + GeoIpTool.getLocation(groupKey) + " -> " + count);
                    System.out.printf("%s -> %s\n", groupKey, count);
                });
                System.out.println("bucket size = " + list.size());
            } else if (value instanceof LongTermsAggregate terms) {
                List<LongTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    String groupKey = bucket.key();
                    Long secondTimestamp = Long.parseLong(groupKey)/1000;
                    //String groupKeyStr = bucket.keyAsString();
                    String dateTimeStr = DateTimeConverter.format(Long.parseLong(groupKey));
                    long count = bucket.docCount();
                    map.put(secondTimestamp, count);
                });
            } else if (value instanceof DateRangeAggregate aggregate) {
                List<RangeBucket> list = aggregate.buckets().array();
                list.forEach(rangeBucket -> {
                    String key = rangeBucket.key();
                    System.out.println(rangeBucket);
                });
            } else if (value instanceof DateHistogramAggregate aggregate) {
                List<DateHistogramBucket> list = aggregate.buckets().array();
                list.forEach(rangeBucket -> {
                    long msTimestamp = Long.parseLong(rangeBucket.key());
                    String dateStr = DateTimeConverter.localDateTime(msTimestamp).toLocalDate().toString();
                    long count = rangeBucket.docCount();
                });
            } else {
                System.out.println(value);
            }
        });
    }

    /**
     * 根据天进行聚合
     *
     * @param
     * @return 日期 -> 每日访问量
     * @date 2025-12-25 17:23:04
     */
    public Map<String, Long> aggregateByDay(String index, String aggregateField, Query query) {
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(index)
                .size(0)
                .query(query)
                // 按天进行聚合
                .aggregations("agg1", a->a.dateHistogram(t -> t.field(aggregateField)
                        .calendarInterval(CalendarInterval.Day).timeZone(timeZone).minDocCount(0)))
                .build();
        try {
            SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
            TotalHits totalHits = searchResponse.hits().total();
            long total = totalHits.value();
            Map<String, Aggregate> resultMap = searchResponse.aggregations();
            Map<String, Long> map = getAggregateMap(aggregateField, resultMap);
            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return Collections.emptyMap();
    }

    /**
     * 在指定日期时间范围内对某个字段进行聚合
     *
     * @param
     * @return url -> 每日访问量
     * @date 2025-12-25 17:31:56
     */
    public Map<String, Long> aggregateByQuery(String index, String aggregateField, Query query) {
        int aggregateSize = 65535;
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(index)
                .size(0)
                .query(query)
                .aggregations("first_agg", a->a.terms(t->t.field(aggregateField).size(aggregateSize)))
                .build();
        try {
            SearchResponse<NginxLog> searchResponse = esClient.search(searchRequest, NginxLog.class);
            TotalHits totalHits = searchResponse.hits().total();
            long total = totalHits.value();
            Map<String, Aggregate> resultMap = searchResponse.aggregations();
            Map<String, Long> map = getAggregateMap(aggregateField, resultMap);
            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return Collections.emptyMap();
    }

    private Map<String, Long> getAggregateMap(String aggregateField, Map<String, Aggregate> resultMap) {
        Map<String, Long> map = new TreeMap<>();
        resultMap.forEach((k, v) -> {
            Object value = v._get();
            if (value instanceof StringTermsAggregate terms) {
                List<StringTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    String groupKey = (String) bucket.key()._get();
                    long count = bucket.docCount();
                    map.put(groupKey, count);
                });
            } else if (value instanceof LongTermsAggregate terms) {
                List<LongTermsBucket> list = terms.buckets().array();
                list.forEach(bucket -> {
                    long count = bucket.docCount();
                    String groupKey = bucket.key();
                    if ("timeIso8601".equals(aggregateField)) {
                        String dateTimeStr = DateTimeConverter.format(Long.parseLong(groupKey));
                        map.put(dateTimeStr, count);
                    } else {
                        map.put(groupKey, count);
                    }
                });
            } else if (value instanceof DateHistogramAggregate aggregate) {
                List<DateHistogramBucket> list = aggregate.buckets().array();
                list.forEach(rangeBucket -> {
                    long msTimestamp = Long.parseLong(rangeBucket.key());
                    String dateStr = DateTimeConverter.localDateTime(msTimestamp).toLocalDate().toString();
                    long count = rangeBucket.docCount();
                    map.put(dateStr, count);
                });
            } else {
                System.out.println("unknown aggregate type: " + value.getClass().getName());
            }
        });

        return map;
    }
}
