package com.ruoyi.llm.service.impl;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

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.client.MilvusServiceClient;
import io.milvus.grpc.SearchResults;
import io.milvus.param.ConnectParam;
import io.milvus.param.MetricType;
import io.milvus.param.R;
import io.milvus.param.RpcStatus;
import io.milvus.param.collection.LoadCollectionParam;
import io.milvus.param.collection.ReleaseCollectionParam;
import io.milvus.param.dml.SearchParam;
import io.milvus.response.SearchResultsWrapper;
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.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 static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();

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

    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
            ConnectParam.newBuilder()
                    .withHost("192.168.28.188")
                    .withPort(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);
        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");
            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 = "好的，我已经收到您上传的招标文件，我将给您提供技术文件模板，您可点击进行预览：" +
                    "【<a href='/profile" + outputFilename + "'> 模版 " + "</a>】\n\n" +
                    chapters + "\n\n" +
                    "请问您需要哪个章节撰写的帮助？";
        }
        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 "上传成功";
    }

    /**
     * 新增智能体
     * 
     * @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 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("请根据上述招标文件内容，严格按以下格式列出").append(question).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 chapter2 = generateAnswer(prompt);

        StringBuilder sb = new StringBuilder("二级标题如下：\n" + chapter2 + "\n 请根据参考内容编排三级标题，严格按以下格式列出")
                .append(":\n").append("6.1.1 XX\n" +
                        "6.1.2 XX\n" +
                        "6.1.3 XX");
        List<JSONObject> contexts = retrieveFromMilvus("technical", chapter2, 10);
        for (JSONObject context : contexts) {
            sb.append("参考内容").append(": ")
                    .append(context.getString("content")).append("\n\n");
        }
        String chapter3 = generateAnswer(sb.toString());
        sb = new StringBuilder("二级标题如下：\n" + chapter2 + "\n 三级标题如下：" + chapter3 + "帮我合并二三级标题，严格按以下格式列出")
                .append(":\n").append("6.1 XX\n" +
                        "6.1.1 XX\n" +
                        "6.1.2 XX\n"+
                        "6.2 XX\n" +
                        "6.3 XX");
        String content = generateAnswer(sb.toString());
        writeContent(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) {
        SearchResultsWrapper wrapper = retrieve(collectionName, query, topK);
        return wrapper.getRowRecords(0).stream()
                .map(record -> {
                    JSONObject result = new JSONObject();
                    result.put("content", record.get("content"));
                    return result;
                })
                .collect(Collectors.toList());
    }

    /**
     * 检索知识库
     */
    private SearchResultsWrapper retrieve(String collectionName, String query, int topK) {
        List<List<Float>> queryVector = Collections.singletonList(embeddingModel.embed(query).content().vectorAsList());

        //  加载集合
        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
                .withCollectionName(collectionName)
                .build();

        R<RpcStatus> loadResponse = milvusClient.loadCollection(loadParam);
        if (loadResponse.getStatus() != R.Status.Success.getCode()) {
            System.err.println("加载Collection失败: " + loadResponse.getMessage());
            milvusClient.close();
        }

        // 构建SearchParam
        SearchParam searchParam = SearchParam.newBuilder()
                .withCollectionName(collectionName)
                .withVectors(queryVector)
                .withTopK(topK)
                .withOutFields(Arrays.asList("content"))
                .withVectorFieldName("embedding")
                .withMetricType(MetricType.COSINE)
                .withParams("{\"nprobe\": 8}")
                .build();

        R<SearchResults> response = milvusClient.search(searchParam);
        SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());

        // 释放集合
        ReleaseCollectionParam param = ReleaseCollectionParam.newBuilder()
                .withCollectionName(collectionName)
                .build();
        milvusClient.releaseCollection(param);

        return wrapper;
    }

    /**
     * 写入章节大纲
     * @return
     */
    public void writeContent(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();
               contentParagraph.setStyle("3");
               boolean title2 = chapters.get(i).indexOf(".") == chapters.get(i).lastIndexOf(".");
               if (!title2)
                   contentParagraph.setStyle("4");

               XWPFRun run = contentParagraph.createRun();
               if (chapters.get(i).contains(" "))
                   run.setText(chapters.get(i).split(" ")[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);
            }
        }
    }

    /**
     * 检索知识库
     */
    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);
    }

}
