package com.ruoyi.agent.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.agent.service.IMcpService;
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 io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.spec.McpServerSession;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral;
import org.noear.solon.Solon;
import org.noear.solon.ai.annotation.ToolMapping;
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.AssistantMessage;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.session.InMemoryChatSession;
import org.noear.solon.ai.mcp.McpChannel;
import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
import org.noear.solon.annotation.Param;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.*;
import java.sql.*;
import java.util.*;

@Service
@McpServerEndpoint(channel = McpChannel.STREAMABLE_STATELESS , mcpEndpoint = "/mcp/sse")
public class McpServiceImpl implements IMcpService {

    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();

    @Value("${cmc.llmService.url}")
    private String llmServiceUrl;

    @Value("${cmc.milvusService.url}")
    private String milvusServiceUrl;

    @Value("${cmc.profile}")
    private String profile;

    private MilvusClientV2 milvusClient;

    /**
     * 初始化 Milvus 客户端
     */
    @PostConstruct
    public void initMilvusClient() {
        if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
            throw new IllegalStateException("Milvus service URL 未配置");
        }
//        milvusClient = new MilvusClientV2(
//                ConnectConfig.builder()
//                        .uri(milvusServiceUrl)
//                        .build());
    }

    @PreDestroy
    public void destroyMilvusClient() {
        if (milvusClient != null) {
            try {
                // 关闭 Milvus 客户端，释放 gRPC 通道
                milvusClient.close();
                System.out.println("Milvus client closed successfully");
            } catch (Exception e) {
                System.err.println("Error closing Milvus client: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 调用LLM+RAG（外部文件+知识库）生成回答
     */
    @ToolMapping(description = "章节撰写")
    public AssistantMessage writeParagraph(@Param(description = "知识库名称") String collectionName,
                                           @Param(description = "智能体名称") String agentName,
                                           @Param(description = "章节名称") String title,
                                           @Param(description = "技术文件地址") String templatePath) throws IOException
    {
        try {
            if (templatePath.startsWith("/dev-api"))
                templatePath = templatePath.replace("/dev-api/profile", profile);
            templatePath =profile + templatePath;
            List<String> subTitles = extractSubTitles(templatePath, title);
            List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
            return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询OA数据生成回答
     */
    @ToolMapping(description = "执行SQL查询")
    public JSONArray SQLQuery(@Param(description = "sql语句") String sqlString)
    {
        sqlString = sqlString.replace("[","").replace("]","");
        return executeQuery(sqlString);
    }

    /**
     * 获取所有表名列表
     */
    @ToolMapping(description = "获取数据库中所有表名列表")
    public JSONArray GetAllTableNames()
    {
        return getAllTableNames();
    }

    /**
     * 获取指定表的结构信息
     */
    @ToolMapping(description = "获取指定表的结构信息")
    public JSONObject GetTableStructure(@Param(description = "表名") String tableName)
    {
        return getTableStructure(tableName);
    }

    public JSONArray executeQuery(String sql) {
        JSONArray result = new JSONArray();

        try {
            // 显式加载 MySQL 驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("MySQL 驱动未找到", e);
        }

        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"));
             PreparedStatement stmt = conn.prepareStatement(sql)) {

            ResultSet rs = stmt.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();

            while (rs.next()) {
                JSONObject row = new JSONObject();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i);
                    Object value = rs.getObject(i);
                    row.put(columnName, value);
                }
                result.add(row);
            }

        } catch (SQLException e) {
            throw new RuntimeException("执行 SQL 失败: " + sql, e);
        }

        return result;
    }

    /**
     * 从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("file_name", searchResult.get(0).getEntity().get("file_name"));
            result.put("content", searchResult.get(0).getEntity().get("content"));
            resultList.add(result);
        });
        return resultList;
    }

    /**
     * 调用LLM生成回答
     */
    public AssistantMessage generateAnswerWithDocumentAndCollection(String agentName, String templatePath, List<String> titles, List<JSONObject> contexts) throws IOException {
        StringBuilder sb = new StringBuilder("招标文件内容：\n\n");
        String filename = templatePath.replace("_" + agentName, "");
        File profilePath = new File(filename);
        if (!profilePath.exists()) {
            filename = filename.replace(".docx", ".doc");
            profilePath = new File(filename);
        }
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        List<TextSegment> segments = splitDocument(profilePath);
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
        embeddingStore.addAll(embeddings, segments);
        StringBuilder content = new StringBuilder();
        for (String title : titles) {
            Embedding queryEmbedding = embeddingModel.embed(title).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\n").append(title);
            content.append(generateAnswer(sb.toString()));
        }
        String absolutePath = templatePath.replace("/dev-api/profile", profile);
        writeContent(content.toString(), titles, absolutePath);
        for (JSONObject context : contexts) {
            sb.append("文件").append(": ")
                    .append(context.getString("file_name")).append("\n\n")
                    .append("段落格式").append(": ")
                    .append(context.getString("content")).append("\n\n");
        }
        content.append( "招标文件分析完成，章节内容已写入【<a href='")
                .append(templatePath.replace(profile, "/dev-api/profile"))
                .append("'> 技术文件" + "</a>】，请查阅\n\n")
                .append("如需修改，请输入技术文件已有内容的章节标题\n\n")
                .append(extractTitles(templatePath));
        return ChatMessage.ofAssistant(content.toString());
    }

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

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

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

    /**
     * 写入章节内容
     * @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);
            }
        }
    }

    /**
     * 获取DOCX段落的大纲级别
     */
    private int getDocxOutlineLevel(XWPFParagraph paragraph, XWPFDocument document) {
        int level = 0;
        String styleId = paragraph.getStyle();
        if (styleId != null) {
            XWPFStyle style = document.getStyles().getStyle(styleId);
            if (style != null) {
                CTPPrGeneral stylePPr = style.getCTStyle().getPPr();
                if (stylePPr != null && stylePPr.getOutlineLvl() != null) {
                    level = stylePPr.getOutlineLvl().getVal().intValue() + 1;
                }
            }
        }
        return level;
    }

    /**
     * 获取DOC段落的大纲级别
     */
    private int getDocOutlineLevel(Paragraph paragraph) {
        int level = 0;
        short styleIndex = paragraph.getStyleIndex();
        if (styleIndex >= 1 && styleIndex <= 9) {
            level = styleIndex;
        }
        return level;
    }

    /**
     * 检查DOCX段落是否是三级标题
     */
    private boolean isDocxLevel3Title(String paraText, XWPFDocument document) {
        for (XWPFParagraph p : document.getParagraphs()) {
            if (p.getText().trim().equals(paraText.trim())) {
                return getDocxOutlineLevel(p, document) == 3;
            }
        }
        return false;
    }

    /**
     * 按三级标题分割DOCX内容
     */
    private void splitDocxByLevel3(String content, XWPFDocument document, List<TextSegment> segments) {
        StringBuilder currentLevel3Content = new StringBuilder();
        String[] paragraphs = content.split("\n");
        for (String para : paragraphs) {
            if (para.trim().isEmpty()) continue;

            if (isDocxLevel3Title(para.trim(), document)) {
                if (currentLevel3Content.length() != 0) {
                    TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                    segments.add(segment);
                    currentLevel3Content = new StringBuilder();
                }
                currentLevel3Content.append(para).append("\n");
            } else {
                if (currentLevel3Content.length() != 0) {
                    currentLevel3Content.append(para).append("\n");
                }
            }
        }
        if (currentLevel3Content.length() != 0) {
            TextSegment segment = TextSegment.from(currentLevel3Content.toString());
            segments.add(segment);
        }
    }

    /**
     * 按三级标题分割DOC内容
     */
    private void splitDocByLevel3(String content, HWPFDocument document, List<TextSegment> segments) {
        StringBuilder currentLevel3Content = new StringBuilder();
        Range level2Range = document.getRange();
        boolean foundCurrentLevel2 = false;

        for (int j = 0; j < level2Range.numParagraphs(); j++) {
            Paragraph p = level2Range.getParagraph(j);
            String paraText = p.text().trim();
            if (paraText.isEmpty()) continue;

            int pLevel = getDocOutlineLevel(p);
            if (pLevel == 2 && paraText.equals(content.split("\n")[0].trim())) {
                foundCurrentLevel2 = true;
            }

            if (foundCurrentLevel2) {
                pLevel = getDocOutlineLevel(p);

                if (pLevel == 3) {
                    if (currentLevel3Content.length() != 0) {
                        TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                        segments.add(segment);
                        currentLevel3Content = new StringBuilder();
                    }
                    currentLevel3Content.append(paraText).append("\n");
                } else if (pLevel == 2 && !paraText.equals(content.split("\n")[0].trim())) {
                    break;
                } else {
                    if (currentLevel3Content.length() != 0 || pLevel == 2) {
                        currentLevel3Content.append(paraText).append("\n");
                    }
                }
            }
        }

        if (currentLevel3Content.length() != 0) {
            TextSegment segment = TextSegment.from(currentLevel3Content.toString());
            segments.add(segment);
        }
    }

    /**
     * 获取最低级别子标题列表
     */
    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 = getDocxOutlineLevel(paragraph, document);
                if (level > 0) {
                    // 维护当前路径
                    while (!currentPath.isEmpty()) {
                        XWPFParagraph lastPara = currentPath.get(currentPath.size() - 1);
                        int lastLevel = getDocxOutlineLevel(lastPara, document);
                        if (lastLevel <= 0) {
                            break;
                        }

                        if (lastLevel >= level) {
                            currentPath.remove(currentPath.size() - 1);
                        } else {
                            break;
                        }
                    }
                    currentPath.add(paragraph);
                    if (foundParent) {
                        if (level > parentLevel) { // 是子标题
                            if (isLeafNode(document, paragraph, level)) {
                                subTitles.add(text);
                            }
                        } 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 = getDocxOutlineLevel(nextP, doc);
        if (nextLevel > 0) {
            // 遇到同级或更高级别标题，是叶子节点
            return nextLevel <= level; // 存在更低级别的标题，不是叶子节点
        }
        return true;
    }

    /**
     * 获取二、三级标题列表
     */
    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();
                int level = getDocxOutlineLevel(paragraph, document);
                // 判断主标题（大纲级别3或4，即原来的heading 3或heading 4）
                if (level == 3 || level == 4) {
                    subTitles.append(text).append("\n");
                }
            }
        }
        return subTitles.toString();
    }

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

    /**
     * 检索知识库
     */
    private List<TextSegment> splitDocument(File transferFile) throws IOException {
        InputStream fileInputStream = new FileInputStream(transferFile);
        String filename = transferFile.getName().toLowerCase();
        if (filename.endsWith(".docx")) {
            List<TextSegment> segments = new ArrayList<>();
            try (XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream)) {
                StringBuilder currentLevel2Content = new StringBuilder();
                boolean inLevel2Section = false;

                for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
                    String text = paragraph.getText().trim();
                    if (text.isEmpty()) continue;

                    int paraLevel = getDocxOutlineLevel(paragraph, xwpfDocument);

                    if (paraLevel == 2) {
                        if (currentLevel2Content.length() != 0) {
                            int level2Length = currentLevel2Content.toString().getBytes().length;
                            if (level2Length <= 65535) {
                                TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                                segments.add(segment);
                            } else {
                                splitDocxByLevel3(currentLevel2Content.toString(), xwpfDocument, segments);
                            }
                            currentLevel2Content = new StringBuilder();
                        }
                        currentLevel2Content.append(text).append("\n");
                        inLevel2Section = true;
                    } else {
                        if (inLevel2Section) {
                            currentLevel2Content.append(text).append("\n");
                        }
                    }
                }

                if (currentLevel2Content.length() != 0) {
                    int level2Length = currentLevel2Content.toString().getBytes().length;
                    if (level2Length <= 65535) {
                        TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                        segments.add(segment);
                    } else {
                        splitDocxByLevel3(currentLevel2Content.toString(), xwpfDocument, segments);
                    }
                }
            }
            return segments;
        }
        else if (filename.endsWith(".doc")) {
            List<TextSegment> segments = new ArrayList<>();
            try (HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream)) {
                StringBuilder currentLevel2Content = new StringBuilder();
                boolean inLevel2Section = false;

                Range range = hwpfDocument.getRange();
                for (int i = 0; i < range.numParagraphs(); i++) {
                    Paragraph paragraph = range.getParagraph(i);
                    String text = paragraph.text().trim();
                    if (text.isEmpty()) continue;

                    int paraLevel = getDocOutlineLevel(paragraph);

                    if (paraLevel == 2) {
                        if (currentLevel2Content.length() != 0) {
                            int level2Length = currentLevel2Content.toString().getBytes().length;
                            if (level2Length <= 65535) {
                                TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                                segments.add(segment);
                            } else {
                                splitDocByLevel3(currentLevel2Content.toString(), hwpfDocument, segments);
                            }
                            currentLevel2Content = new StringBuilder();
                        }
                        currentLevel2Content.append(text).append("\n");
                        inLevel2Section = true;
                    } else {
                        if (inLevel2Section) {
                            currentLevel2Content.append(text).append("\n");
                        }
                    }
                }

                if (currentLevel2Content.length() != 0) {
                    int level2Length = currentLevel2Content.toString().getBytes().length;
                    if (level2Length <= 65535) {
                        TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                        segments.add(segment);
                    } else {
                        splitDocByLevel3(currentLevel2Content.toString(), hwpfDocument, segments);
                    }
                }
            }
            return segments;
        }

        else if (filename.endsWith(".pdf")) {
            Document document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
            return splitter.split(document);
        }
        else if (filename.endsWith(".txt")) {
            Document document = new TextDocumentParser().parse(fileInputStream);
            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
            return splitter.split(document);
        }
        else {
            throw new UnsupportedOperationException("不支持文件类型: " + filename);
        }
    }

    /**
     * 获取表结构信息
     */
    private JSONObject getTableStructure(String tableName) {
        JSONObject result = new JSONObject();

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("MySQL 驱动未找到", e);
        }

        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {

            DatabaseMetaData metaData = conn.getMetaData();

            ResultSet columns = metaData.getColumns(null, null, tableName, null);
            JSONArray columnsInfo = new JSONArray();

            while (columns.next()) {
                JSONObject column = new JSONObject();
                column.put("columnName", columns.getString("COLUMN_NAME"));
                column.put("dataType", columns.getString("TYPE_NAME"));
                column.put("columnSize", columns.getString("COLUMN_SIZE"));
                column.put("isNullable", columns.getString("IS_NULLABLE"));
                column.put("columnComment", columns.getString("REMARKS"));
                columnsInfo.add(column);
            }
            columns.close();

            ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
            JSONArray primaryKeyColumns = new JSONArray();

            while (primaryKeys.next()) {
                primaryKeyColumns.add(primaryKeys.getString("COLUMN_NAME"));
            }
            primaryKeys.close();

            result.put("tableName", tableName);
            result.put("columns", columnsInfo);
            result.put("primaryKey", primaryKeyColumns);

        } catch (SQLException e) {
            throw new RuntimeException("获取表结构失败: " + tableName, e);
        }

        return result;
    }

    /**
     * 获取所有表名列表（只包含cmc、sys和files开头的表）
     */
    private JSONArray getAllTableNames() {
        JSONArray tableNames = new JSONArray();

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("MySQL 驱动未找到", e);
        }

        try (Connection conn = DriverManager.getConnection(Solon.cfg().getProperty("cmc.mysqlService.jdbcUrl"),Solon.cfg().getProperty("cmc.mysqlService.username"), Solon.cfg().getProperty("cmc.mysqlService.password"))) {

            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});

            while (tables.next()) {
                String tableName = tables.getString("TABLE_NAME");
                String tableComment = tables.getString("REMARKS");
                // 只获取cmc、sys和files开头的表
                if (tableName.startsWith("cmc") || tableName.startsWith("sys") || tableName.startsWith("files")) {
                    JSONObject tableInfo = new JSONObject();
                    tableInfo.put("tableName", tableName);
                    tableInfo.put("tableComment", tableComment != null ? tableComment : "");
                    tableNames.add(tableInfo);
                }
            }
            tables.close();

        } catch (SQLException e) {
            throw new RuntimeException("获取表名列表失败", e);
        }

        return tableNames;
    }

}