package com.dayou.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dayou.annotation.LuceneResource;
import com.dayou.annotation.LuceneSearchable;
import com.dayou.configuration.DfsConfig;
import com.dayou.service.ILuceneSearchService;
import com.dayou.utils.MybatisPlusUtils;
import com.dayou.utils.ReflectUtils;
import com.dayou.utils.SpringContextHolder;
import com.dayou.vo.LuceneSearchVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.FileSystems;
import java.util.*;

/**
 * 类说明：全局搜索服务
 *
 * @author: wucl
 * @since: 2024/4/26
 * created with IntelliJ IDEA.
 */
@Slf4j
@Service
public class LuceneSearchServiceImpl implements ILuceneSearchService {

    private final String BASE_PACKAGE = "com.dayou.entity";
    private final String RESOURCE_PATTERN = "/**/*.class";

    @Autowired
    private DfsConfig dfsConfig;



    @Override
    public void initializeIndex() {
        Map<String, Class> luceneResourceEntity = scanLuceneEntity();
        if (CollectionUtil.isNotEmpty(luceneResourceEntity)){
            Collection<Document> docs = new ArrayList<>();
            for(Map.Entry<String,Class> entry :luceneResourceEntity.entrySet()){
                Class clazz = entry.getValue();
                String beanName = entry.getKey();
                ServiceImpl serviceBean = (ServiceImpl) SpringContextHolder.getBean(beanName);
                Field[] fields = clazz.getDeclaredFields();
                List<String> searchAbleFields = new ArrayList<>();
                for (Field field :fields){
                    LuceneSearchable annotation = field.getAnnotation(LuceneSearchable.class);
                    if (annotation!=null){
                        String value = annotation.value();
                        if (StrUtil.isBlank(value)){
                            value = field.getName();
                        }
                        searchAbleFields.add(value);
                    }
                }
                LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
                LambdaQueryWrapper wrapper = MybatisPlusUtils.fieldValues(searchAbleFields, queryWrapper, clazz);
                if (wrapper!=null){
                    List list = serviceBean.list(wrapper);
                    try {
                        if (CollectionUtil.isNotEmpty(list)){
                            doCreatedLuceneIndex(list,docs);
                        }
                    } catch (NoSuchFieldException e) {
                        log.error("!!!create index field error");
                    }
                }

            }
            log.info("=========>LUCENE索引文件初始化完成...");
        }
    }

    @Override
    public List<LuceneSearchVO> globalSearch(String businessType, String keyword) throws IOException, ParseException, InvalidTokenOffsetsException {
        Directory directory = null;
        DirectoryReader reader = null;
        Query query = null;
        List<LuceneSearchVO> results;
        try {
            directory = FSDirectory.open(FileSystems.getDefault().getPath(dfsConfig.getLuceneDir()));
            reader = DirectoryReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(reader);

            if (!StrUtil.equals("ALL",businessType)){
                String[] queries = { businessType, keyword };
                String[] fields = { "businessEnum", "result" };
                BooleanClause.Occur[] clauses = { BooleanClause.Occur.MUST, BooleanClause.Occur.MUST };
                query = MultiFieldQueryParser.parse(queries, fields, clauses, new IKAnalyzer());
            }else {
                QueryParser parser = new QueryParser("result", new IKAnalyzer());
                query = parser.parse(keyword);
            }

            TopDocs topDocs = indexSearcher.search(query, 10);
            //加入高亮显示的
            SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
            Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
            Fragmenter fragmenter = new SimpleFragmenter(50);
            highlighter.setTextFragmenter(fragmenter);


            log.info("本次搜索共找到" + topDocs.totalHits + "条数据");
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            results = new ArrayList<>();
            for (ScoreDoc scoreDoc :scoreDocs){
                LuceneSearchVO luceneSearchVO = new LuceneSearchVO();
                // 取出文档编号
                int docId = scoreDoc.doc;
                Document doc = reader.document(docId);
                luceneSearchVO.setId(doc.get("id"));
                String result = doc.get("result");
                luceneSearchVO.setResult(result);
                if (StrUtil.isNotBlank(result)){
                    result = highlighter.getBestFragment(new IKAnalyzer(), "result", result);
                }
                luceneSearchVO.setHighlighter(result);
                luceneSearchVO.setBusinessEnum(doc.get("businessEnum"));
                luceneSearchVO.setMenuName(doc.get("menuName"));
                luceneSearchVO.setUrl(doc.get("url"));
                results.add(luceneSearchVO);
            }
        } finally {
            directory.close();
            reader.close();
        }

        return results;
    }


    @Override
    public void addIndex() {

    }

    @Override
    public void removeIndex() {

    }


    private Map<String, Class> scanLuceneEntity(){
        Map<String, Class> luceneResourceEntity = new HashMap<>();
        //spring工具类，可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = readerfactory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(classname);
                //判断是否有指定主解
                LuceneResource anno = clazz.getAnnotation(LuceneResource.class);
                if (anno != null) {
                    //将注解中的类型值作为key，对应的类作为 value
                    String serviceImplClassName = anno.serviceImplClassName();
                    luceneResourceEntity.put(serviceImplClassName, clazz);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
        }
        return luceneResourceEntity;
    }

    private void doCreatedLuceneIndex(List<Object> luceneData, Collection<Document> docs ) throws NoSuchFieldException {
        Class aClass = luceneData.get(0).getClass();
        log.info("=========>正在初始化[{}]索引文件...", aClass.getName());
        LuceneResource annotation = (LuceneResource) aClass.getAnnotation(LuceneResource.class);
        String businessType = annotation.value().getMsg();
        String menuName = annotation.menuName();
        String url = annotation.url();
        String checkField = annotation.checkField();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Object t : luceneData) {
            Object id = ReflectUtils.getFieldValue(t, "id");
            String xUrl = url;
            String xMenuName = menuName;
            Boolean checkValue = false;
            if (StrUtil.isNotBlank(checkField)){
                checkValue = ReflectUtils.getFieldValue(t, checkField);
                if (checkValue){
                    LuceneSearchable declaredField = aClass.getDeclaredField(checkField).getAnnotation(LuceneSearchable.class);
                     xUrl = declaredField.url();
                     xMenuName = declaredField.menuName();
                }

            }
            for (Field field : declaredFields) {
                LuceneSearchable anno = field.getAnnotation(LuceneSearchable.class);
                if (anno != null) {
                    Object fieldValue = ReflectUtils.getFieldValue(t, field.getName());
                    if (fieldValue != null) {
                        Document document = new Document();
                        document.add(new StringField("id", String.valueOf(id), org.apache.lucene.document.Field.Store.YES));
                        document.add(new TextField("result", String.valueOf(fieldValue), org.apache.lucene.document.Field.Store.YES));
                        document.add(new TextField("businessEnum", businessType, org.apache.lucene.document.Field.Store.YES));
                        if (checkValue){
                            document.add(new TextField("menuName", xMenuName, org.apache.lucene.document.Field.Store.YES));
                            document.add(new TextField("url", xUrl, org.apache.lucene.document.Field.Store.YES));
                        }else{
                            document.add(new TextField("menuName", menuName, org.apache.lucene.document.Field.Store.YES));
                            document.add(new TextField("url", url, org.apache.lucene.document.Field.Store.YES));
                        }
                        docs.add(document);
                    }
                }
            }
        }

        Analyzer analyzer = new IKAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

        try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(dfsConfig.getLuceneDir()));
             IndexWriter indexWriter = new IndexWriter(directory, config)) {
            indexWriter.addDocuments(docs);
            indexWriter.commit();
        } catch (Exception e) {
            log.error("!!!create index error");
        }
    }
}
