package com.storlead.es.server.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.storlead.es.internal.BaseSearchService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import com.storlead.framework.common.dto.page.PageDTO;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: sp-tems-gotr
 * @description:
 * @author: chenkq
 * @create: 2023-06-16 16:42
 */
@Service
@ConditionalOnProperty(prefix = "storlead.es", name = "enabled", havingValue = "true")
public class BaseSearchServiceImpl<T> implements BaseSearchService<T> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private ElasticsearchRestTemplate elasticsearchTemplate;


    public void save(Class<T> clazz) {
        try {
            elasticsearchTemplate.save(clazz);
        }catch (Exception e) {
            logger.error("");
        }
    }

    public void save(Class<T> clazz,String indexId) {
        try {
            T info = elasticsearchTemplate.get(indexId, clazz);
            if (Objects.nonNull(info)) {
                elasticsearchTemplate.delete(indexId,clazz);
                elasticsearchTemplate.save(clazz);
            }
        }catch (Exception e) {
            logger.error("");
        }
    }

    @Override
    public List<T> query(Class<T> clazz,String keyword, String indexName) {
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .build();
        IndexCoordinates indexCoordinates = IndexCoordinates.of(indexName);
        List<T> infos = elasticsearchTemplate.search(searchQuery,clazz,indexCoordinates)
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
         return infos;
    }

    @Override
    public IPage<T> queryPage(Class<T> clazz, String keyword, PageDTO page, String indexName) {
        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .withPageable(pageable)
                .build();
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz);
        long totalHits = searchHits.getTotalHits();
        List<T> infos = searchHits
                .stream()
                .map(org.springframework.data.elasticsearch.core.SearchHit::getContent)
                .collect(Collectors.toList());

        IPage<T> res = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page.getPageIndex(),page.getPageSize());
        res.setTotal(totalHits);
        res.setRecords(infos);
        return res;
    }

    @Override
    public IPage<T> queryHitPage(Class<T> clazz, String keyword, PageDTO page,String ... hightFields) {
        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());

        HighlightBuilder highlightBuilder = builderHighlightField(hightFields);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .withPageable(pageable)
                .withHighlightBuilder(highlightBuilder)
                .build();
        SearchHits<T> searchHit = elasticsearchTemplate.search(searchQuery,clazz);
        List<SearchHit<T>> searchHits = searchHit.getSearchHits();
        List<T> infos = new ArrayList<>();
        for(SearchHit<T> hit :searchHits) {
            T info = hit.getContent();
            Map<String, List<String>> maps =  hit.getHighlightFields();
            for (String key : maps.keySet()) {
                 List<String> vals = maps.get(key);
                 if (!CollectionUtils.isEmpty(vals)) {
                     String fieldValue = vals.get(0);
                     try {
                         Field field = info.getClass().getDeclaredField(key);
                         field.setAccessible(true);
                         field.set(info,fieldValue);
                     }catch (Exception e) {
                         logger.error("---",e);
                     }
                 }
            }
            infos.add(hit.getContent());
        }
        long totalHits = searchHit.getTotalHits();
        IPage<T> res = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page.getPageIndex(),page.getPageSize());
        res.setTotal(totalHits);
        res.setRecords(infos);
        return res;
    }

    private HighlightBuilder builderHighlightField(String ... hightFields) {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<em>").postTags("</em>");
        highlightBuilder.highlighterType("plain");
        highlightBuilder.numOfFragments(0);
        if (Objects.isNull(hightFields) || hightFields.length == 0) {
            return highlightBuilder;
        }
        for(String hightField : hightFields) {
            highlightBuilder.field(hightField);
        }
        return highlightBuilder;
    }

    @Override
    public  List<T> queryHit(Class<T> clazz,String keyword,String fieldName,String indexName) {
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("title")
                .preTags("<em>")
                .postTags("</em>");
        QueryBuilder queryBuilder = QueryBuilders.matchQuery(fieldName,keyword);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withHighlightBuilder(highlightBuilder)
                .build();
        IndexCoordinates indexCoordinates = IndexCoordinates.of(indexName);
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz,indexCoordinates);
        List<T> infos = searchHits
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        return infos;
    }

    @Override
    public  List<T> queryHitByPage(Class<T> clazz,PageDTO page,String keyword,String fieldName,String indexName) {
        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
        QueryBuilder queryBuilder = QueryBuilders.matchQuery(fieldName,keyword);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(pageable)
                .build();
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
        List<T> infos = searchHits
                .stream()
                .map(org.springframework.data.elasticsearch.core.SearchHit::getContent)
                .collect(Collectors.toList());
        return infos;
    }

    @Override
    public  List<T> queryHit(Class<T> clazz,String keyword,String indexName,String ... fieldNames) {
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz);
        List<T> infos = searchHits
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        return infos;
    }

    @Override
    public  List<T> queryHitByPage(Class<T> clazz,PageDTO page,String keyword,String indexName,String ... fieldNames) {
        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(pageable)
                .build();
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
        List<T> infos = searchHits
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        return infos;
    }

    @Override
    public  List<T> querySimilarityPage(Class<T> clazz,PageDTO page,String keyword,String indexName,String ... fieldNames) {
        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(pageable)
                .build();
        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
        List<T> infos = searchHits
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        return infos;
    }

    @Override
    public List<Map<String, Object>> searchByNameSimilarity(String inputName) {
        SearchRequest searchRequest = new SearchRequest("customer_data");
        searchRequest.source().query(QueryBuilders.termQuery("customer_name", "sear"));
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.fuzzyQuery("customer_name", inputName).fuzziness(Fuzziness.AUTO))
                .withPageable(PageRequest.of(0, 10))
                .build();
        SearchHits<Map> searchHits = elasticsearchTemplate.search(query, Map.class, IndexCoordinates.of("customer_data"));
        return searchHits.getSearchHits().stream().map(hit -> {
            Map<String, Object> result = new HashMap<>();
            result.put("id", hit.getId());
            result.put("customer_name", ((Map<String, Object>) hit.getContent()).get("customer_name"));
            result.put("similarity_score", hit.getScore());
            return result;
        }).collect(Collectors.toList());
    }
}
