package com.yeejoin.amos.knowledgebase.face.service;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yeejoin.amos.knowledgebase.face.model.KnowledgeDocContentModel;
import com.yeejoin.amos.knowledgebase.face.model.KnowledgeDynamicsValueModel;
import com.yeejoin.amos.knowledgebase.face.model.KnowledgeTagInstanceModel;
import com.yeejoin.amos.knowledgebase.face.model.KnowledgeTagValueModel;
import com.yeejoin.amos.knowledgebase.face.orm.dao.ESDocRepository;
import com.yeejoin.amos.knowledgebase.face.orm.entity.ESDocEntity;
import com.yeejoin.amos.knowledgebase.face.orm.entity.ESTagEntity;
import com.yeejoin.amos.knowledgebase.face.orm.entity.KnowledgeDocContent;
import com.yeejoin.amos.knowledgebase.face.util.RemoteData;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.typroject.tyboot.core.foundation.utils.Bean;
import org.typroject.tyboot.core.foundation.utils.DateUtil;
import org.typroject.tyboot.core.foundation.utils.ValidationUtil;
import org.typroject.tyboot.core.restful.exception.instance.RequestForbidden;

import java.util.*;

/**
 * ES检索服务
 *
 * @author tiantao
 */
@Service
public class ESDocService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    @Autowired
    private ESDocRepository esDocRepository;
    @Autowired
    private DocContentService docContentService;
    @Autowired
    private DocLibraryService docLibraryService;
    @Autowired
    private DynamicsValueService dynamicsValueService;

    public static final String COMMON_FORMAT = "yyyy年MM月dd日HH时mm分";

    /**
     * 批量保存
     *
     * @param list 文档列表
     */
    public void saveAll(List<ESDocEntity> list) {
        esDocRepository.saveAll(list);
    }

    /**
     * 查询所有，根据发布时间倒叙排列
     *
     * @param current 页码
     * @param size    页面大小
     * @return Page对象
     */
    @SuppressWarnings("rawtypes")
    public Page<ESDocEntity> findAll(int current, int size) {
        // 创建查询构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
                // 分页
                .withPageable(PageRequest.of(current, size))
                // 排序
                .withSort(SortBuilders.fieldSort("sortStr.keyword").order(SortOrder.DESC));

        List<ESDocEntity> list = new LinkedList<>();
        try {
            SearchHits<ESDocEntity> searchHits = elasticsearchTemplate.search(queryBuilder.build(), ESDocEntity.class);

            for (SearchHit searchHit : searchHits.getSearchHits()) {
                ESDocEntity docEntity = (ESDocEntity) searchHit.getContent();
                list.add(docEntity);
            }

            return new AggregatedPageImpl<>(list, PageRequest.of(current, size), searchHits.getTotalHits());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new AggregatedPageImpl<>(list, PageRequest.of(current, size), 0);
    }

    /**
     * 根据id查询ES存储对象
     *
     * @param sequenceNbr id
     * @return ES实例
     */
    public ESDocEntity queryById(Long sequenceNbr) {
        return esDocRepository.findById(sequenceNbr).orElse(null);
    }

    /**
     * 根据关键字查询文档，关键字不为空时按相关性从大到小排序
     *
     * @param queryStr 关键字
     * @param current  当前页码
     * @param size     页面大小
     * @return
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public Page<ESDocEntity> queryByKeys(String queryStr, int current, int size) {
        List<String> keys = queryStr2List(queryStr);
        if (ValidationUtil.isEmpty(keys)) {
            return findAll(current, size);
        }
        // 条件构造，多条件循环匹配
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        for (String key : keys) {
            boolMust.must(QueryBuilders.boolQuery().minimumShouldMatch(1)
                    .should(QueryBuilders.matchQuery("docTitle", key).boost(1f))
                    .should(QueryBuilders.matchQuery("docInfo", key).boost(0.3f))
                    .should(QueryBuilders.matchQuery("author", key).boost(0.3f))
                    .should(QueryBuilders.matchQuery("textContent", key).boost(0.5f))
                    .should(QueryBuilders.matchQuery("docTags.tagInfo", key).boost(0.8f))
                    .should(QueryBuilders.matchQuery("contentTags.tagInfo", key).boost(0.8f))
            );
        }

        // 创建查询构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
                // 高亮字段
                .withHighlightFields(
                        new HighlightBuilder.Field("docTitle").preTags("<em>").postTags("</em>").numOfFragments(0),
                        new HighlightBuilder.Field("textContent").preTags("<em>").postTags("</em>").numOfFragments(1).fragmentSize(200).noMatchSize(100).order("score"),
                        new HighlightBuilder.Field("docTags.tagInfo").preTags("").postTags("").numOfFragments(0),
                        new HighlightBuilder.Field("contentTags.tagInfo").preTags("").postTags("").numOfFragments(0)
                )
                // 分页
                .withPageable(PageRequest.of(current, size))
                //过滤条件
                .withQuery(boolMust);

        // 对高亮词条进行操作
        List<ESDocEntity> list = new ArrayList<>();
        try {
            SearchHits<ESDocEntity> searchHits = elasticsearchTemplate.search(queryBuilder.build(), ESDocEntity.class);

            for (SearchHit searchHit : searchHits.getSearchHits()) {

                ESDocEntity docEntity = (ESDocEntity) searchHit.getContent();
                searchHit.getHighlightFields();
                // 文档标题
                List<String> docTitle = searchHit.getHighlightField("docTitle");
                if (!ValidationUtil.isEmpty(docTitle)) {
                    StringBuilder sb = new StringBuilder();
                    for (String fragment : docTitle) {
                        sb.append(fragment);
                    }
                    docEntity.setDocTitle(sb.toString());
                }
                // 文档内容
                List<String> textContent = searchHit.getHighlightField("textContent");
                if (!ValidationUtil.isEmpty(textContent)) {
                    StringBuilder sb = new StringBuilder();
                    for (String fragment : textContent) {
                        sb.append(fragment);
                    }
                    docEntity.setSummary(sb.toString());
                }
                Set<String> tagInfoSet = new HashSet<>();
                // 文档标签
                List<String> docTagsInfo = searchHit.getHighlightField("docTags.tagInfo");
                if (!ValidationUtil.isEmpty(docTagsInfo)) {
                    for (String fragment : docTagsInfo) {
                        tagInfoSet.add(fragment);
                    }
                }
                List<String> contentTagsInfo = searchHit.getHighlightField("contentTags.tagInfo");
                if (!ValidationUtil.isEmpty(contentTagsInfo)) {
                    for (String fragment : contentTagsInfo) {
                        tagInfoSet.add(fragment);
                    }
                }
                // 删除不匹配的和重复的标签
                deleteRepeatedTags(docEntity.getDocTags(), tagInfoSet);
                deleteRepeatedTags(docEntity.getContentTags(), tagInfoSet);

                list.add(docEntity);
            }


            return new AggregatedPageImpl<>(list, PageRequest.of(current, size), searchHits.getTotalHits());
        } catch (Exception e) {
            return new AggregatedPageImpl<>(list, PageRequest.of(current, size), 0);
        }
    }

    /**
     * 将搜索词转换为词组
     *
     * @param queryStr 关键词
     * @return 词组
     */
    private List<String> queryStr2List(String queryStr) {
        List<String> resList = new ArrayList<>();
        if (!ValidationUtil.isEmpty(queryStr)) {
            String[] keys = queryStr.trim().split("\\s");
            for (String key : keys) {
                if (!ValidationUtil.isEmpty(key)) {
                    resList.add(key);
                }
            }
        }
        return resList;
    }

    /**
     * 删除未命中的标签&去重
     *
     * @param list       标签列表
     * @param tagInfoSet 命中的tagInfo
     */
    private static void deleteRepeatedTags(List<ESTagEntity> list, Set<String> tagInfoSet) {
        if (!ValidationUtil.isEmpty(tagInfoSet)) {
            Iterator<ESTagEntity> tagIterator = list.iterator();
            while (tagIterator.hasNext()) {
                ESTagEntity tag = tagIterator.next();
                if (tagInfoSet.contains(tag.getTagInfo())) {
                    tagInfoSet.remove(tag.getTagInfo());
                    tag.setTagInfo(null);
                } else {
                    tagIterator.remove();
                }
            }
        }
    }

    /**
     * 为搜索结果添加人名翻译/汇总信息
     *
     * @param key     查询关键字
     * @param current 页码
     * @param size    每页数量
     * @return Page对象
     */
    public Page queryAndDetail(String key, int current, int size) {
        List<Map<String, Object>> resList = new ArrayList<>();
        Page<ESDocEntity> entityPage = queryByKeys(key, current, size);
        List<ESDocEntity> docEntityList = entityPage.getContent();
        docLibraryService.fillDirectoryName(docEntityList);
        Set<String> userSet = new HashSet<>();
        for (ESDocEntity esDocEntity : docEntityList) {
            KnowledgeDocContentModel contentModel = JSON.parseObject(esDocEntity.getDocJson(), KnowledgeDocContentModel.class);
            List<KnowledgeDynamicsValueModel> listByInstance = dynamicsValueService.queryByInstanceId(contentModel.getSequenceNbr());
            Map<String, Object> fieldMap = Bean.listToMap(listByInstance, "fieldName", "fieldValue", KnowledgeDynamicsValueModel.class);
            Map<String, Object> map = Bean.BeantoMap(esDocEntity);
            // 收藏数/引用数
            map.put("referenceNum", docLibraryService.getReferenceCount(contentModel.getSequenceNbr()));
            map.put("collectionNum", docLibraryService.getCollectionCount(contentModel.getSequenceNbr()));
            map.put("userId", contentModel.getUserId());
            userSet.add(contentModel.getUserId());
            map.putAll(fieldMap);
            resList.add(map);
        }
        Map<String, String> userMap = RemoteData.getUserMap(userSet);
        for (Map<String, Object> map : resList) {
            map.put("userName", userMap.get(map.get("userId")));
            map.remove("htmlContent");
            map.remove("docJson");
        }
        return new AggregatedPageImpl<>(resList, PageRequest.of(current, size), entityPage.getTotalElements());
    }

    /**
     * 保存文档至ES库
     */
    public boolean savePublishedDoc(List<KnowledgeDocContentModel> docs) {
        if (!ValidationUtil.isEmpty(docs)) {
            saveDocToESWithDetailModels(docs);
        }
        return true;
    }

    /**
     * 批量保存文档
     *
     * @param docs 填充字段完毕的文档实例列表
     */
    private void saveDocToESWithDetailModels(List<KnowledgeDocContentModel> docs) {
        if (!ValidationUtil.isEmpty(docs)) {
            // 组装字典中英文Map
            Map<String, Map<String, String>> enumCnEnMap = docLibraryService.getBaseEnumMap();
            List<ESDocEntity> esDocs = new ArrayList<>();
            for (KnowledgeDocContentModel docDetail : docs) {
                if (ValidationUtil.equals(docDetail.getDocStatus(), DocContentService.DOC_STATUS_PUBLISHED)) {
                    List<ESTagEntity> docTags = buildESTagsByInstanceList(docDetail.getDocTags());
                    List<ESTagEntity> contentTags = buildESTagsByInstanceList(docDetail.getDocContentTags());
                    ESDocEntity esDocEntity = new ESDocEntity()
                            .setSequenceNbr(docDetail.getSequenceNbr())
                            .setDocTitle(docDetail.getDocTitle())
                            .setDirectoryId(docDetail.getDirectoryId())
                            .setDirectoryName(docDetail.getDirectoryName())
                            .setLastUpdateTime(docDetail.getLastUpdateTime())
                            .setSummary(docDetail.getSummary())
                            .setHtmlContent(docDetail.getHtmlContent())
                            .setDocTags(docTags)
                            .setContentTags(contentTags)
                            .setDocJson(JSON.toJSONString(docDetail))
                            .setAuthor(docDetail.getUserName())
                            .setTextContent(docDetail.getTextContent())
                            .setSortStr(docDetail.getSortStr())
                            .setDocInfo(baseInfo2Str(docDetail.getDocBaseInfo(), enumCnEnMap));
                    esDocs.add(esDocEntity);
                }
            }
            this.saveAll(esDocs);
        }
    }

    /**
     * 将文档基本信息转为关键字符串
     *
     * @param baseInfo    基本信息
     * @param enumCnEnMap 枚举字典数据
     * @return
     */
    private String baseInfo2Str(Map<String, Object> baseInfo, Map<String, Map<String, String>> enumCnEnMap) {
        StringBuilder infoStr = new StringBuilder();
        if (!ValidationUtil.isEmpty(baseInfo)) {
            for (String field : baseInfo.keySet()) {
                if (enumCnEnMap.containsKey(field)) {
                    String enumCn = enumCnEnMap.get(field).get(baseInfo.get(field));
                    if (!ValidationUtil.isEmpty(enumCn)) {
                        infoStr.append(enumCn).append(" ");
                    }
                } else {
                    Object value = baseInfo.get(field);
                    if (!ValidationUtil.isEmpty(value)) {
                        infoStr.append(value.toString()).append(" ");
                    }
                }
            }
        }
        return infoStr.toString().trim();
    }

    /**
     * 根据标签实例列表创建ES标签列表
     */
    private List<ESTagEntity> buildESTagsByInstanceList(List<KnowledgeTagInstanceModel> instanceList) {
        List<ESTagEntity> tags = new ArrayList<>();
        if (!ValidationUtil.isEmpty(instanceList)) {
            for (KnowledgeTagInstanceModel instanceModel : instanceList) {
                ESTagEntity tag = new ESTagEntity()
                        .setSequenceNbr(instanceModel.getSequenceNbr())
                        .setTagSeq(instanceModel.getTagSeq())
                        .setTagName(instanceModel.getTagName())
                        .setTagInfo(getTagInfoStr(instanceModel))
                        .setTagJson(JSON.toJSONString(instanceModel));
                tags.add(tag);
            }
        }
        return tags;
    }

    /**
     * 获取标签信息：标签名&值
     *
     * @param instanceModel 标签实例
     * @return 信息文本
     */
    private String getTagInfoStr(KnowledgeTagInstanceModel instanceModel) {
        StringBuilder infoStr = new StringBuilder();
        infoStr.append(instanceModel.getTagName()).append(" ");
        if (TagService.TAG_TYPE_TEXT.equals(instanceModel.getTagType())
                || ValidationUtil.isEmpty(instanceModel.getTagValues())) {
            return infoStr.toString().trim();
        }
        for (KnowledgeTagValueModel valueModel : instanceModel.getTagValues()) {
            String fieldName = valueModel.getFieldName();
            String tagValue = valueModel.getTagValue();
            switch (fieldName) {
                case TagValueService.VALUE_TAG_FIELD_DATE_H:
                    infoStr.append(tagValue).append(" ");
                    try {
                        Date date = DateUtil.formatStringToDate(tagValue, null);
                        infoStr.append(DateUtil.formatDate(date, DateUtil.YMD)).append(" ");
                        infoStr.append(DateUtil.formatDate(date, DateUtil.YMDHM)).append(" ");
                        infoStr.append(DateUtil.formatDate(date, DateUtil.ymd)).append(" ");
                        infoStr.append(DateUtil.formatDate(date, DateUtil.Y_M_D)).append(" ");
                        infoStr.append(DateUtil.formatDate(date, COMMON_FORMAT)).append(" ");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    infoStr.append(tagValue).append(" ");
            }
        }
        return infoStr.toString().trim();
    }

    /**
     * 从ES库批量删除文档
     *
     * @param docs 文档列表
     * @return 删除成功
     */
    public boolean deleteDocById(List<KnowledgeDocContentModel> docs) {
        if (!ValidationUtil.isEmpty(docs)) {
            try {
                for (KnowledgeDocContentModel doc : docs) {
                    Long sequenceNbr = doc.getSequenceNbr();
                    if (esDocRepository.existsById(sequenceNbr)) {
                        esDocRepository.deleteById(sequenceNbr);
                    }
                }
            } catch (Exception e) {
                throw new RequestForbidden("删除ES库文档出错");
            }
        }

        return true;
    }

    /**
     * 重建索引
     */
    public boolean init() {
        esDocRepository.deleteAll();
        //保存所有已发布文档
        QueryWrapper<KnowledgeDocContent> wrapper = new QueryWrapper<>();
        wrapper.eq("doc_status", DocContentService.DOC_STATUS_PUBLISHED);
        int count = docContentService.count(wrapper);
        int finishNmu = 0;
        int current = 0;
        while (count > finishNmu) {
            com.baomidou.mybatisplus.extension.plugins.pagination.Page page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(current++, 30);
            IPage resPage = docContentService.page(page, wrapper);
            List<KnowledgeDocContent> records = resPage.getRecords();
            Set ids = Bean.listToMap(records, "sequenceNbr", KnowledgeDocContent.class).keySet();
            List<KnowledgeDocContentModel> list = docLibraryService.efficientList(ids);
            saveDocToESWithDetailModels(list);
            finishNmu += records.size();
        }
        return true;
    }
}
