package com.keyware.shandan.browser.service;

import cn.hutool.core.util.ReflectUtil;
import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.enums.ReviewStatus;
import com.keyware.shandan.bianmu.service.DirectoryService;
import com.keyware.shandan.browser.SearchLogProcessor;
import com.keyware.shandan.browser.entity.FullSearchParam;
import com.keyware.shandan.browser.entity.PageVo;
import com.keyware.shandan.browser.entity.SearchResultRow;
import com.keyware.shandan.common.constants.DirConstant;
import com.keyware.shandan.common.util.StreamUtil;
import com.keyware.shandan.common.util.StringUtils;
import com.keyware.shandan.system.entity.SysFile;
import com.keyware.shandan.system.entity.SysSetting;
import com.keyware.shandan.system.service.SysSettingService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * FileSearchService
 *
 * @author Administrator
 * @since 2021/12/21
 */
@Slf4j
@Service
public class FileSearchService {
    private final String[] imageTypes = {"apng", "avif", "jpeg", "jpg", "png", "svg", "webp", "bmp", "gif", "ico", "tiff"};

    @Autowired
    private RestHighLevelClient esClient;
    @Autowired
    private DirectoryService directoryService;
    @Autowired
    private SearchLogProcessor searchLogProcessor;
    @Autowired
    private SysSettingService settingService;

    public PageVo searchFile(FullSearchParam param) throws IOException {
        SearchRequest request = Requests.searchRequest("shandan");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.from(param.getPage() * param.getSize() - param.getSize()).size(param.getSize());

        String metaId = param.getMetaId();
        String text = param.getSearch();
        boolean defaultSearch = StringUtils.isBlank(text) && (StringUtils.isBlank(metaId) || "ROOT".equals(metaId));
        if (defaultSearch) {
            List<String> words = new ArrayList<>();
            for (String systemWord : searchLogProcessor.getSystemWords()) {
                words.add(systemWord);
                if (words.size() == 5) {
                    break;
                }
            }
            text = String.join(" ", words);
        }

        String[] searchFields = param.getSearchFields().split(",");

        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        if (!DirConstant.DIR_ROOT_ID.equals(metaId)) {
            List<String> ids = new ArrayList<>();
            ids.add(metaId);
            ids.addAll(getChildrenByDirId(metaId));
            builder.must(QueryBuilders.termsQuery("entityId.keyword", ids));
        }
        if (StringUtils.hasText(text)) {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            String[] includesCol = {"fileName", "entryStaff", "equipmentModel", "source", "taskCode", "taskNature", "troopCode", "targetNumber", "missileNumber", "themeTask", "remark", "text"};

            for (String colName : searchFields) {
                if (Arrays.asList(includesCol).contains(colName)) {
                    if ("phrase".equals(param.getSearchType())) {
                        boolQuery.should(QueryBuilders.matchPhraseQuery(colName, text));
                    } else {
                        boolQuery.should(QueryBuilders.matchQuery(colName, text));
                    }
                }
            }
            builder.must(boolQuery);
        }

        // 高亮显示设置
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.requireFieldMatch(true);
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        //builder.fragmentSize(20); //最大高亮分片数
        //builder.numOfFragments(0); //从第一个分片获取高亮片段
        highlightBuilder.preTags("<label style=\"color:red\">").postTags("</label>").field("*");
        highlightBuilder.forceSource(true);
        searchSourceBuilder.query(builder).highlighter(highlightBuilder);

        // 设置排序
        if (!param.isEmpty()) {
            String sortField = param.getField();
            // 使用反射获取文件实体类中字段上的注解设置的字段类型
            Field field = ReflectUtil.getField(SysFile.class, sortField);
            if (field != null) {
                org.springframework.data.elasticsearch.annotations.Field fieldAnno = field.getAnnotation(org.springframework.data.elasticsearch.annotations.Field.class);
                if (fieldAnno.type() != FieldType.Text
                        && fieldAnno.type() != FieldType.Object
                        && fieldAnno.type() != FieldType.Auto
                        && fieldAnno.type() != FieldType.Nested
                        && fieldAnno.type() != FieldType.Ip
                        && fieldAnno.type() != FieldType.Attachment) {
                    if (fieldAnno.type() == FieldType.Keyword) {
                        sortField = sortField.concat(".keyword");
                    }
                    searchSourceBuilder.sort(sortField, SortOrder.fromString(param.getSort()));
                } else {
                    log.error("不支持的排序类型:{}:{}", sortField, fieldAnno.type());
                }
            }
        }
        request.source(searchSourceBuilder);

        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
        return PageVo.ofSearchHits(response.getHits(), param);
    }

    /**
     * 图片检索
     *
     * @param dirId
     * @param searchText
     * @param pageNo
     * @param pageSize
     * @return
     * @throws IOException
     */
    public PageVo<Object> searchImage(String dirId, String searchText, int pageNo, int pageSize) throws IOException {
        SearchRequest request = Requests.searchRequest("shandan");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.from(pageNo * pageSize - pageSize).size(pageSize);

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        queryBuilder.must(QueryBuilders.termsQuery("fileType", imageTypes));
        if (!DirConstant.DIR_ROOT_ID.equals(dirId)) {
            List<String> ids = new ArrayList<>();
            ids.add(dirId);
            ids.addAll(getChildrenByDirId(dirId));
            queryBuilder.must(QueryBuilders.termsQuery("entityId.keyword", ids));
        }
        if (StringUtils.hasText(searchText)) {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            MatchPhraseQueryBuilder matchPhraseQuery = QueryBuilders.matchPhraseQuery("fileName", searchText);
            boolQuery.should();
            queryBuilder.must(matchPhraseQuery);
        }

        searchSourceBuilder.query(queryBuilder);
        request.source(searchSourceBuilder);
        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);

        SysSetting bianmuSetting = settingService.getById("BIANMU");
        PageVo page = new PageVo();
        page.setPage(pageNo);
        page.setSize(pageSize);

        SearchHits hits = response.getHits();
        page.setTotal(hits.getTotalHits());
        page.setPageTotal((int) Math.ceil(page.getTotal() / (double) page.getSize()));
        page.setRecords(Arrays.stream(hits.getHits()).map(hit -> {
            Map<String, Object> dataMap = hit.getSourceAsMap();
            dataMap.put("title", dataMap.get("fileName"));
            String path = ((String) dataMap.get("path")).replace("\\", "/");
            dataMap.put("src", bianmuSetting.getSysAddress() + "/upload/" + path);
            return dataMap;
        }).collect(Collectors.toList()));
        return PageVo.ofSearchHits(response.getHits(), page);
    }

    private List<String> getChildrenByDirId(String dirId) {
        DirectoryVo dir = directoryService.getById(dirId);
        return StreamUtil.as(directoryService.childrenLists(dir, ReviewStatus.SUBMITTED)).map(DirectoryVo::getId).toList();
    }

}
