package com.ruoyi.llm.service.impl;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SnowFlake;
import com.ruoyi.llm.domain.CmcChat;
import com.ruoyi.llm.domain.CmcDocument;
import com.ruoyi.llm.mapper.CmcChatMapper;
import com.ruoyi.llm.mapper.CmcDocumentMapper;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.LoadCollectionReq;
import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.BaseVector;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.extractor.POITextExtractor;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.XmlCursor;
import org.noear.solon.Solon;
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.ChatResponse;
import org.noear.solon.ai.chat.ChatSession;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.session.InMemoryChatSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.llm.mapper.CmcAgentMapper;
import com.ruoyi.llm.domain.CmcAgent;
import com.ruoyi.llm.service.ICmcAgentService;
import org.springframework.web.multipart.MultipartFile;

/**
 * 智能体Service业务层处理
 *
 * @author ruoyi
 * @date 2025-07-17
 */
@Service
public class CmcAgentServiceImpl implements ICmcAgentService
{
    @Autowired
    private CmcAgentMapper cmcAgentMapper;

    @Autowired
    private CmcDocumentMapper cmcDocumentMapper;

    @Autowired
    private CmcChatMapper cmcChatMapper;

    private String processValue = "";

    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();

    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";

    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
            ConnectConfig.builder()
                    .uri("http://192.168.28.188:19530")
                    .build());

    /**
     * 查询智能体
     *
     * @param agentId 智能体主键
     * @return 智能体
     */
    @Override
    public CmcAgent selectCmcAgentByAgentId(Integer agentId)
    {
        return cmcAgentMapper.selectCmcAgentByAgentId(agentId);
    }

    /**
     * 查询智能体列表
     *
     * @param cmcAgent 智能体
     * @return 智能体
     */
    @Override
    public List<CmcAgent> selectCmcAgentList(CmcAgent cmcAgent)
    {
        return cmcAgentMapper.selectCmcAgentList(cmcAgent);
    }

    /**
     * 获取开场白
     *
     * @param agentName 智能体名称
     * @return 结果
     */
    @Override
    public String getOpening(String agentName) {
        String content = "";
        if (agentName.contains("技术"))
            content = "我是投标文件写作助手，我将助您完成技术文件部分撰写。请上传招标询价文件，分析后将依招标服务要求，运用技术方案知识库，为您提供参考。";
        return content;
    }

    /**
     * 上传单文件
     *
     * @param file 文件
     * @return 结果
     */
    @Override
    public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException {
        String prefixPath = "/upload/agent/" + agentName;
        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
        if (!profilePath.exists())
            profilePath.mkdirs();
        String chatId = new SnowFlake().generateId();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("chatId", chatId);
        File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
        if (!transferFile.exists())
            file.transferTo(transferFile);
        processValue = "上传完成:100%";
        CmcDocument cmcDocument = new CmcDocument();
        cmcDocument.setDocumentId(new SnowFlake().generateId());
        cmcDocument.setChatId(chatId);
        cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
        cmcDocumentMapper.insertCmcDocument(cmcDocument);
        String message = "";
        if (agentName.contains("技术")) {
            CmcChat cmcChat = new CmcChat();
            cmcChat.setChatId(jsonObject.getString("chatId"));
            cmcChat.setInputTime(new Date());
            cmcChat.setInput("招标文件地址：" + prefixPath + "/" + file.getOriginalFilename());
            cmcChat.setUserId(SecurityUtils.getUserId());
            cmcChatMapper.insertCmcChat(cmcChat);
            String[] filenameSplit = file.getOriginalFilename().split("\\.");
            String outputFilename = prefixPath + "/" + file.getOriginalFilename()
                    .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
            if (file.getOriginalFilename().endsWith(".doc"))
                outputFilename = outputFilename.replace(".doc", ".docx");
            Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
            Files.deleteIfExists(outputFilePath);
            InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
                // 保存文档到本地文件系统
                try (FileOutputStream out = new FileOutputStream(RuoYiConfig.getProfile() + outputFilename)) {
                    doc.write(out);
                }
            }
            String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
                    RuoYiConfig.getProfile() + outputFilename, "工作大纲");
            message = "好的，我已经收到您上传的招标文件。\n\n"+ chapters + "\n\n" +
                    "若您对章节标题有异议，请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改，后续将根据修改后的章节标题，帮您生成对应章节内容。\n\n" +
                    "思考时间可能较长，请耐心等待！\n";
        }
        jsonObject.put("assistantMessage", message);
        return jsonObject;
    }

    /**
     * 上传修改文件
     *
     * @param file 文件
     * @return 结果
     */
    @Override
    public JSONObject uploadModifyFile(MultipartFile file, String agentName) throws IOException {
        String prefixPath = "/upload/agent/" + agentName;
        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
        if (!profilePath.exists())
            profilePath.mkdirs();
        String chatId = new SnowFlake().generateId();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("chatId", chatId);
        File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
        if (!transferFile.exists())
            file.transferTo(transferFile);
        else {
            Path inputFilePath = Paths.get(profilePath + "/" + file.getOriginalFilename());
            Files.deleteIfExists(inputFilePath);
            file.transferTo(transferFile);
        }
        processValue = "上传完成:100%";
        CmcDocument cmcDocument = new CmcDocument();
        cmcDocument.setDocumentId(new SnowFlake().generateId());
        cmcDocument.setChatId(chatId);
        cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
        cmcDocumentMapper.insertCmcDocument(cmcDocument);
        String message = "";
        if (agentName.contains("技术")) {
            CmcChat cmcChat = new CmcChat();
            cmcChat.setChatId(jsonObject.getString("chatId"));
            cmcChat.setInputTime(new Date());
            cmcChat.setInput("投标文件地址：" + prefixPath + "/" + file.getOriginalFilename());
            cmcChat.setUserId(SecurityUtils.getUserId());
            cmcChatMapper.insertCmcChat(cmcChat);
            InputStream fileInputStream = new FileInputStream(profilePath + "/" + file.getOriginalFilename());
            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
                // 保存文档到本地文件系统
                try (FileOutputStream out = new FileOutputStream(profilePath + "/" + file.getOriginalFilename())) {
                    doc.write(out);
                }
            }
            List<String> subTitles = extractSubTitles(profilePath + "/" + file.getOriginalFilename(), "技术文件");
            message = generateAnswerWithDocumentAndCollection(profilePath + "/" + file.getOriginalFilename(), subTitles);
        }
        jsonObject.put("assistantMessage", message);
        return jsonObject;
    }

    /**
     * 上传多文件
     *
     * @param fileList 文件
     * @return 结果
     */
    @Override
    public String uploadDocumentList(MultipartFile[] fileList, String agentName) throws IOException {
        File profilePath = new File( RuoYiConfig.getProfile() + "/upload/agent/" + agentName);
        if (!profilePath.exists())
            profilePath.mkdirs();
        for (MultipartFile file : fileList) {
            File transferFile = new File(profilePath + File.separator + file.getOriginalFilename());
            if (!transferFile.exists())
                file.transferTo(transferFile);
        }
        return "上传成功";
    }

    /**
     * 上传多文件
     *
     * @return 结果
     */
    public String getProcess() {
        return processValue;
    }

    /**
     * 新增智能体
     *
     * @param cmcAgent 智能体
     * @return 结果
     */
    @Override
    public int insertCmcAgent(CmcAgent cmcAgent)
    {
        cmcAgent.setCreateTime(DateUtils.getNowDate());
        return cmcAgentMapper.insertCmcAgent(cmcAgent);
    }

    /**
     * 修改智能体
     *
     * @param cmcAgent 智能体
     * @return 结果
     */
    @Override
    public int updateCmcAgent(CmcAgent cmcAgent)
    {
        return cmcAgentMapper.updateCmcAgent(cmcAgent);
    }

    /**
     * 批量删除智能体
     *
     * @param agentIds 需要删除的智能体主键
     * @return 结果
     */
    @Override
    public int deleteCmcAgentByAgentIds(Integer[] agentIds)
    {
        return cmcAgentMapper.deleteCmcAgentByAgentIds(agentIds);
    }

    /**
     * 删除智能体信息
     *
     * @param agentId 智能体主键
     * @return 结果
     */
    @Override
    public int deleteCmcAgentByAgentId(Integer agentId)
    {
        return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
    }

    /**
     * 调用LLM生成回答 - 针对技术文件章节内容生成
     */
    public String generateAnswerWithDocumentAndCollection(String templatePath, List<String> titles) throws IOException {
        File profilePath = new File(templatePath);

        // 构建招标文件内容的嵌入向量存储
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        List<TextSegment> segments = splitDocument(profilePath);
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
        embeddingStore.addAll(embeddings, segments);

        StringBuilder allGeneratedContent = new StringBuilder();
        Map<String, String> titleContentMap = new HashMap<>();

        List<String> successTitles = new ArrayList<>();
        List<String> failureTitles = new ArrayList<>();

        // 为每个章节标题生成内容
        for (String title : titles) {
            try {
                // 1. 从招标文件中检索相关内容
                List<String> tenderContent = retrieveFromTenderDocument(embeddingStore, title);

                // 2. 从知识库中检索相似章节内容
                List<JSONObject> knowledgeContexts = retrieveFromMilvus("technical", title, 10);

                // 3. 构建针对该章节的提示语
                String prompt = buildChapterPrompt(title, tenderContent, knowledgeContexts);

                // 4. 生成章节内容
                String chapterContent = generateAnswer(prompt);

                // 5. 存储章节内容
                titleContentMap.put(title, chapterContent);
                allGeneratedContent.append(title).append("\n").append(chapterContent).append("\n\n");

                // 更新进度
                processValue = "生成章节内容中: " + Double.parseDouble(String.format("%.2f%n", (double) (titles.indexOf(title) + 1)  / titles.size() * 100)) + "%";
                successTitles.add(title);

            } catch (Exception e) {
                failureTitles.add(title);
                System.err.println("生成章节 " + title + " 内容时出错: " + e.getMessage());
                titleContentMap.put(title, "该章节内容生成失败，请手动填写。");
            }
        }

        // 将生成的内容写入技术文件
        writeContentToDocument(titleContentMap, titles, templatePath);

        // 构建返回消息
        StringBuilder message = new StringBuilder();
        message.append("技术文件章节内容生成完成！\n\n");
        message.append("已为以下章节生成内容：\n");
        for (String title : successTitles) {
            message.append("• ").append(title).append("\n");
        }
        message.append("以下章节内容待生成：\n");
        for (String title : failureTitles) {
            message.append("× ").append(title).append("\n");
        }
        message.append("\n技术文件已更新，请查看：\n");
        message.append("【<a href='/profile").append(templatePath.replace("\\", "/").replace(RuoYiConfig.getProfile(), ""))
               .append("'>点击查看技术文件</a>】\n\n");
        message.append("如需修改特定章节，请输入章节标题名称。\n").append("若提示未检测到工具，麻烦重新发送，谢谢");

        return message.toString();
    }

    /**
     * 从招标文件中检索与指定标题相关的内容
     */
    private List<String> retrieveFromTenderDocument(InMemoryEmbeddingStore<TextSegment> embeddingStore, String title) {
        List<String> relevantContent = new ArrayList<>();

        try {
            Embedding queryEmbedding = embeddingModel.embed(title).content();
            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .minScore(0.7) // 降低阈值以获取更多相关内容
                    .maxResults(5) // 限制结果数量
                    .build();

            List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
            results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());

            for (EmbeddingMatch<TextSegment> match : results) {
                relevantContent.add(match.embedded().text());
            }
        } catch (Exception e) {
            System.err.println("从招标文件检索内容时出错: " + e.getMessage());
        }

        return relevantContent;
    }

    /**
     * 构建针对特定章节的提示语
     */
    private String buildChapterPrompt(String title, List<String> tenderContent, List<JSONObject> knowledgeContexts) {
        StringBuilder prompt = new StringBuilder();

        // 基础指令
        prompt.append("你是一位专业的技术文件撰写专家。请根据以下信息为技术文件章节生成详细内容。\n\n");

        // 章节信息
        prompt.append("【目标章节】: ").append(title).append("\n\n");

        // 招标文件相关内容
        if (!tenderContent.isEmpty()) {
            prompt.append("【招标文件要求】:\n");
            for (int i = 0; i < tenderContent.size(); i++) {
                prompt.append(i + 1).append(". ").append(tenderContent.get(i)).append("\n");
            }
            prompt.append("\n");
        }

        // 知识库参考内容
        if (!knowledgeContexts.isEmpty()) {
            prompt.append("【参考模板内容】:\n");
            for (int i = 0; i < knowledgeContexts.size(); i++) {
                JSONObject context = knowledgeContexts.get(i);
                prompt.append("参考文件 ").append(i + 1).append(" (").append(context.getString("file_name")).append("):\n");
                prompt.append(context.getString("content")).append("\n\n");
            }
        }

        // 具体要求
        prompt.append("【撰写要求】:\n");
        prompt.append("1. 内容必须与招标文件要求高度匹配\n");
        prompt.append("2. 参考模板内容的结构和表达方式\n");
        prompt.append("3. 内容要专业、具体、可操作\n");
        prompt.append("4. 语言要规范、准确，符合技术文件标准\n");
        prompt.append("5. 内容要完整，包含必要的技术细节\n");
        prompt.append("6. 避免空洞的套话，要有实质性的技术内容\n\n");

        // 输出格式要求
        prompt.append("【输出格式】:\n");
        prompt.append("请直接输出章节的正文内容，不要包含标题。内容应该包括：\n");
        prompt.append("- 技术方案概述\n");
        prompt.append("- 具体实施方法\n");
        prompt.append("- 技术参数和标准\n");
        prompt.append("- 质量保证措施\n");
        prompt.append("- 其他相关技术细节\n\n");

        prompt.append("请开始生成 ").append(title).append(" 的详细内容：");

        return prompt.toString();
    }

    /**
     * 将生成的内容写入文档
     */
    private void writeContentToDocument(Map<String, String> titleContentMap, List<String> titles, String absolutePath) throws IOException {
        File file = new File(absolutePath);
        FileInputStream fileInputStream = new FileInputStream(file);

        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            List<XWPFParagraph> paragraphs = document.getParagraphs();
            List<Integer> titlePositions = new ArrayList<>();
            List<String> contents = new ArrayList<>();

            // 找到所有标题的位置
            for (int i = 0; i < paragraphs.size(); i++) {
                XWPFParagraph paragraph = paragraphs.get(i);
                String paragraphText = paragraph.getText().trim();

                for (String title : titles) {
                    if (paragraphText.contains(title)) {
                        titlePositions.add(i);
                        contents.add(titleContentMap.getOrDefault(title, ""));
                        break;
                    }
                }
            }

            // 从后往前插入内容，避免位置偏移
            for (int i = titlePositions.size() - 1; i >= 0; i--) {
                int insertPos = titlePositions.get(i) + 1;
                if (insertPos < paragraphs.size()) {
                    XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
                    XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
                    contentParagraph.setStyle("1"); // 正文样式

                    XWPFRun run = contentParagraph.createRun();
                    String content = contents.get(i);
                    if (content != null && !content.trim().isEmpty()) {
                        run.setText(content);
                    }
                }

                // 更新进度
                processValue = "章节内容写入: " + Double.parseDouble(String.format("%.2f%n", (double) (i + 1)  / titles.size() * 100)) + "%";

            }

            // 保存文档
            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 调用LLM生成回答
     */
    public String generateAnswerWithDocument(String uploadFilePath, String templatePath, String question) throws IOException {
        StringBuilder sb = new StringBuilder("招标文件内容：\n\n");
        File profilePath = new File(uploadFilePath);
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        List<TextSegment> segments = splitDocument(profilePath);
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
        embeddingStore.addAll(embeddings, segments);
        Embedding queryEmbedding = embeddingModel.embed(question).content();
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .minScore(0.7)
                .build();
        List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
        results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
        for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
            String requests = embeddingMatch.embedded().toString();
            sb.append(requests).append("\n\n");
        }
        //根据招标文件工作大纲要求生成二级标题
        sb.append("请基于上述招标文件中提到的工作大纲内容，先列出二级章节标题，严格按以下格式，仅输出标题列表：\n")
                .append("6.1 XX\n" +
                "6.2 XX\n" +
                "6.3 XX");
        return writeChapters(sb.toString(), templatePath);
    }

    /**
     * 编排章节
     * @return
     */
    public String writeChapters(String prompt, String templatePath) throws IOException {
        String chapters2 = generateAnswer(prompt);

        //根据技术文档知识库生成二级标题下三级标题
        StringBuilder chapter3 = new StringBuilder();
        List<String> chapter2List = new ArrayList<>();
        String[] contentLines = chapters2.split("\n");
        for (String line : contentLines) {
            if (line.contains("6."))
                chapter2List.add(line.replace("*", "").replace("#", "").replace("-", ""));
        }
        for (String chapter2 : chapter2List) {
            String orderNum = chapter2.split(" ")[0];
            StringBuilder sb = new StringBuilder("二级标题如下：\n").append(chapter2)
                    .append("\n 请以上述二级标题作为检索词，到技术知识库中检索最相似的片段，结合检索结果编排该二级标题下的三级标题，严格按以下格式，仅输出三级标题列表：\n")
                    .append(orderNum).append(".1 XX\n")
                    .append(orderNum).append(".2 XX\n")
                    .append(orderNum).append(".3 XX\n");

            List<JSONObject> contexts = retrieveFromMilvus("technical", chapter2, 10);
            for (JSONObject context : contexts) {
                sb.append("参考内容").append(": ")
                        .append(context.getString("content")).append("\n\n");
            }
            chapter3.append(generateAnswer(sb.toString()));
            processValue = "分析中:" + Double.parseDouble(String.format("%.2f%n", (double) (chapter2List.indexOf(chapter2) + 1)  / chapter2List.size() * 100)) + "%";
        }
        String sb = "二级标题如下：\n" + chapters2 +
                "\n 三级标题如下：" + chapter3 +
                "请将上述二级与对应的三级标题进行合并，严格按以下格式输出完整大纲，仅输出标题：\n" +
                "6 技术文件\n" +
                "6.1 XX\n" +
                "6.1.1 XX\n" +
                "6.1.2 XX\n" +
                "6.2 XX\n" +
                "6.2.1 XX";
        String content = generateAnswer(sb);
        writeTitles(content, templatePath);
        return content;
    }

    /**
     * 调用LLM生成回答
     * @return
     */
    public String generateAnswer(String prompt) throws IOException {
        ChatModel chatModel = ChatModel.of(llmServiceUrl)
                .model("Qwen2.5-1.5B-Instruct")
                .build();

        List<ChatMessage> messages = new ArrayList<>();
        messages.add(ChatMessage.ofUser(prompt));
        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
        ChatResponse response = chatModel.prompt(chatSession).call();

        return response.lastChoice().getMessage().getContent();
    }

    /**
     * 从Milvus检索相关文档
     * @return
     */
    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
        List<JSONObject> resultList = new ArrayList<>();
        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
        searchResultList.forEach(searchResult -> {
            JSONObject result = new JSONObject();
            result.put("content", searchResult.get(0).getEntity().get("content"));
            resultList.add(result);
        });
        return resultList;
    }

    /**
     * 检索知识库
     */
    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));

        //  加载集合
        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
                .collectionName(collectionName)
                .build();
        milvusClient.loadCollection(loadCollectionReq);

        // 构建SearchParam
        Map<String, Object> searchParams = new HashMap<>();
        searchParams.put("nprobe", 8);
        SearchReq searchReq = SearchReq.builder()
                .collectionName(collectionName)
                .data(queryVector)
                .topK(topK)
                .outputFields(Arrays.asList("file_name", "file_type", "content"))
                .annsField("embedding")
                .metricType(IndexParam.MetricType.COSINE)
                .searchParams(searchParams)
                .build();

        SearchResp searchResp = milvusClient.search(searchReq);
        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();

        // 释放集合
        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
                .collectionName(collectionName)
                .build();
        milvusClient.releaseCollection(releaseCollectionReq);

        return searchResultList;
    }

    /**
     * 写入章节大纲
     * @return
     */
    public void writeTitles(String content, String templatePath) throws IOException {
        List<String> chapters = new ArrayList<>();
        String[] contentLines = content.split("\n");
        for (String line : contentLines) {
            if (line.contains("6."))
                chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
        }
        File file = new File(templatePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
           for (int i = 0; i < chapters.size(); i++) {
               XWPFParagraph contentParagraph = document.createParagraph();
               int dotNum = chapters.get(i).split("\\.").length - 1;
               contentParagraph.setStyle(String.valueOf(dotNum + 2));

               XWPFRun run = contentParagraph.createRun();
               if (chapters.get(i).split(" ").length > 1)
                   run.setText(chapters.get(i).split(" ")[1]);
               if (dotNum > 1)
                   run.addBreak();
               if (i < chapters.size() - 1 && chapters.get(i + 1).indexOf(".") == chapters.get(i + 1).lastIndexOf("."))
                   run.addBreak(BreakType.PAGE);
           }

            try (FileOutputStream out = new FileOutputStream(templatePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 写入章节内容
     * @return
     */
    public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
        String[] contentLines = content.split("\n");
        Map<String, String> map = new HashMap<>();
        File file = new File(absolutePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            for (int i = 0; i < titles.size(); i++) {
                int startIndex = Arrays.asList(contentLines).indexOf(titles.get(i));
                StringBuilder text = new StringBuilder();
                if (startIndex >= 0) {
                    if (i < titles.size() - 1) {
                        int endIndex = Arrays.asList(contentLines).indexOf(titles.get(i + 1));
                        for (int c = startIndex + 1; c < endIndex; c++) {
                            text.append(contentLines[c]).append("\n\n");
                        }
                    } else {
                        if (startIndex + 1 < contentLines.length) {
                            for (int c = startIndex + 1; c < contentLines.length; c++) {
                                text.append(contentLines[c]).append("\n\n");
                            }
                        }
                    }
                }
                else
                    text.append(content);
                map.put(titles.get(i), text.toString());
            }

            List<Integer> positions = new ArrayList<>();
            List<String> contents = new ArrayList<>();
            List<XWPFParagraph> paragraphs = document.getParagraphs();

            for (int i = 0; i < paragraphs.size(); i++) {
                XWPFParagraph paragraph = paragraphs.get(i);
                for (String title : titles) {
                    if (paragraph.getText().contains(title)) {
                        positions.add(i);
                        contents.add(map.get(title));
                    }
                }
            }

            for (int i = positions.size() - 1; i >= 0; i--) {
                int insertPos = positions.get(i) + 1;
                XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
                XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
                contentParagraph.setStyle("1");
                XWPFRun run = contentParagraph.createRun();
                run.setText(contents.get(i));
            }

            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 获取最低级别子标题列表
     */
    public List<String> extractSubTitles(String filename, String question) throws IOException {
        List<String> subTitles = new ArrayList<>();
        InputStream fileInputStream = new FileInputStream(filename);

        boolean foundParent = false;
        int parentLevel = -1;
        // 用于跟踪当前路径
        List<XWPFParagraph> currentPath = new ArrayList<>();

        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {

            for (XWPFParagraph  paragraph : document.getParagraphs()) {
                String text = paragraph.getText();
                int level = Integer.parseInt(paragraph.getStyle());

                // 维护当前路径
                while (!currentPath.isEmpty() && Integer.parseInt(currentPath.get(currentPath.size() - 1).getStyle()) >= level) {
                    currentPath.remove(currentPath.size() - 1);
                }
                currentPath.add(paragraph);
                if (foundParent) {
                    if (level > parentLevel) { // 是子标题
                        if (isLeafNode(document, paragraph, level)) {
                            subTitles.add(text.replace("\n", ""));
                        }
                    } else {
                        // 遇到同级或更高级别的标题，停止搜索
                        break;
                    }
                } else if (text.equals(question)) {
                    foundParent = true;
                    parentLevel = level;
                }
            }


            return subTitles;

        }
    }

    // 检查一个标题是否是叶子节点（没有更低级别的子标题）
    private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
        int index = doc.getPosOfParagraph(p);
        if (index == -1 || index >= doc.getParagraphs().size()-1) {
            return true;
        }

        // 检查后续段落
        XWPFParagraph nextP = doc.getParagraphs().get(index + 1);
        int nextLevel = Integer.parseInt(nextP.getStyle());
        // 遇到同级或更高级别标题，是叶子节点
        return nextLevel <= level; // 存在更低级别的标题，不是叶子节点

    }

    /**
     * 获取二、三级标题列表
     */
    public String extractTitles(String filename) throws IOException {
        StringBuilder subTitles = new StringBuilder();
        InputStream fileInputStream = new FileInputStream(filename);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                String text = paragraph.getText().trim();
                if (paragraph.getStyle() != null) {
                    // 判断主标题
                    if (paragraph.getStyle().equals("3") || paragraph.getStyle().equals("4") ) {
                        subTitles.append(text).append("\n");
                    }
                }
            }
        }
        return subTitles.toString();
    }

    /**
     * 分割文档
     */
    private List<TextSegment> splitDocument(File transferFile) throws IOException {
        // 加载文档
        Document document;
        InputStream fileInputStream = new FileInputStream(transferFile);
        String filename = transferFile.getName().toLowerCase();
        if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
            try (POITextExtractor extractor = ExtractorFactory.createExtractor(fileInputStream)) {
                String text = extractor.getText();
                document = Document.from(text);
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        else if (filename.endsWith(".pdf")) {
            document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
        }
        else if (filename.endsWith(".txt")) {
            document = new TextDocumentParser().parse(fileInputStream);
        }
        else {
            throw new UnsupportedOperationException("不支持文件类型: " + filename);
        }
        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
        return splitter.split(document);
    }

}
