package com.webchat.service.elastic;


import com.google.common.collect.Lists;
import com.webchat.common.bean.APIPageResponseBean;
import com.webchat.common.util.JsonUtil;
import com.webchat.domain.vo.dto.AbstractBaseEsDTO;
import com.webchat.domain.vo.dto.AbstractESMessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;

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

/**
 * webchat 抽象ES操作服务，不同搜索业务场景自己继承实现
 */
@Slf4j
public abstract class AbstractWebChatElasticSearchClient<T extends AbstractBaseEsDTO> {

    @Autowired
    private RestHighLevelClient client;


    private static final int BULK_SAVE_SIZE = 10;

    /**
     * 实现类中配置不同的索引
     *
     * @return
     */
    protected abstract String getIndexName();

    /**
     * 搜索json结果反序列化
     *
     * @param dataJson
     * @return
     */
    protected abstract T convert(String dataJson);

    /**
     * 填充扩展信息
     *
     * @param dataList
     */
    protected abstract void setSearchResultExtInfo(List<T> dataList);

    protected Integer getShards() {
        return 1;
    }

    protected Integer getReplicas() {
        return 1;
    }

    /**
     * 具体搜索服务自己实现
     * @return
     */
    protected Settings getSettings() {
        return Settings.builder()
                .put("number_of_shards", this.getShards())
                .put("number_of_replicas", this.getReplicas())
                .build();
    }

    /**
     * 具体搜索服务自己实现
     * @return
     */
    protected abstract XContentBuilder getXContentBuilder() throws IOException;


    /**
     * ES 搜索
     *
     * @param type   搜索数据类型：聊天消息/用户/公众号文章/群组
     * @param userId 搜索用户
     * @param q      搜索关键词
     * @param page   搜索页码
     * @param size   搜索条数
     * @return
     */
    public APIPageResponseBean<List<T>> query(Integer type, String userId, String q , int page, int size) {
        SearchRequest searchRequest = new SearchRequest(getIndexName());
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 创建HighlightBuilder并设置高亮标签
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<span style='color:red'>"); // 高亮开始标签
        highlightBuilder.postTags("</span>"); // 高亮结束标签

        // 创建multi-match查询
        MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(q, "content");

        // 创建level的term查询
        TermQueryBuilder senderIdQuery = QueryBuilders.termQuery("sender_id", userId);
        TermQueryBuilder receiverIdQuery = QueryBuilders.termQuery("receiver_id", userId);
        TermQueryBuilder proxySenderIdQuery = QueryBuilders.termQuery("proxy_sender_id", userId);

        // 将两个查询合并为一个bool查询，使用should并设置minimumShouldMatch为1
        BoolQueryBuilder userIdBoolQuery = QueryBuilders.boolQuery()
                .should(senderIdQuery)
                .should(receiverIdQuery)
                .should(proxySenderIdQuery)
                .minimumShouldMatch(1);

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(multiMatchQuery)
                .must(userIdBoolQuery);

        // 应用查询
        searchSourceBuilder.query(boolQueryBuilder);
        searchSourceBuilder.highlighter(highlightBuilder);
        // 设置分页参数
        searchSourceBuilder.from((page - 1) * size);  // 计算偏移量
        searchSourceBuilder.size(size);
        // 按照相关性排序
        searchSourceBuilder.sort("_score", SortOrder.DESC);
        searchRequest.source(searchSourceBuilder);
        // 执行搜索
        List<T> results = new ArrayList<>();
        long total = 0L;
        try {
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            total = searchResponse.getHits().getTotalHits().value;
            if (total > 0) {
                for (SearchHit hit : searchResponse.getHits()) {
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    Map<String, Object> searchRespMap = hit.getSourceAsMap();
                    String dataJson = String.valueOf(searchRespMap.get("data"));
                    results.add(this.convert(dataJson));
                }
            }
        } catch (IOException e) {
            // 版本兼容问题，不影响结果
        }
        this.setSearchResultExtInfo(results);
        return APIPageResponseBean.success(page, size, total, results);
    }

    /**
     * 增量数据同步ES
     *
     */
    public void sync(AbstractESMessageDTO dto) {
        this.saveDocument(dto);
    }

    /**
     * 批量同步数据到ES，常被用于增量数据同步场景
     * @param dtoList
     */
    public void bulkSync(List<AbstractESMessageDTO> dtoList) {
        this.bulkSaveDocument(dtoList);
    }

    /**
     * 创建 ES 索引
     *
     * @return
     */
    public boolean createIndex() {
        String indexName = this.getIndexName();
        XContentBuilder mapping = null;
        try {
            mapping = this.getXContentBuilder();
        } catch (IOException e) {
            log.error("ES索引创建失败 =====> indexName:{}", indexName, e);
            throw new RuntimeException(e);
        }
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName)
                                                .settings(this.getSettings())
                                                .mapping(mapping);
        try {
            return client.indices().create(createIndexRequest, RequestOptions.DEFAULT).isAcknowledged();
        } catch (Exception e) {
            log.error("ES索引创建失败 =====> indexName:{}", indexName, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除索引
     *
     * @return
     */
    public boolean deleteIndex() {
        String indexName = this.getIndexName();
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
        try {
            return client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT).isAcknowledged();
        } catch (IOException e) {
            log.error("ES索引删除失败 =====> indexName:{}", indexName, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * （单条记录写入）保存文档数据到ES
     *
     */
    public void saveDocument(AbstractESMessageDTO dto) {
        String documentId = String.format("%s_%s", dto.getType(), dto.getBizId());
        IndexRequest indexRequest = new IndexRequest(this.getIndexName(), "_doc", documentId).id(documentId);
        indexRequest.source(JsonUtil.toJsonString(dto), XContentType.JSON);
        try {
            client.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
        }
    }

    /**
     * （批量）文档数据批量写入ES
     *
     * @param dtoList
     */
    public void bulkSaveDocument(List<AbstractESMessageDTO> dtoList) {
        BulkRequest bulkRequest = new BulkRequest();
        Lists.partition(dtoList, BULK_SAVE_SIZE).forEach(dataList -> dataList.forEach(data -> {
            String documentId = String.format("%s_%s", data.getType(), data.getBizId());
            IndexRequest indexRequest = new IndexRequest(this.getIndexName())
                                                         .id(documentId)
                                                         .source(JsonUtil.toJsonString(data), XContentType.JSON);
            bulkRequest.add(indexRequest);
        }));
        try {
            // 批量写入es
            client.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES 文档数据批量写入ES异常 =====> indexName:{}, dtoList:{}",
                    this.getIndexName(), JsonUtil.toJsonString(dtoList), e);
            throw new RuntimeException(e);
        }
    }




}
