package com.microservices.zone.resource.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysFile;
import com.microservices.system.api.domain.SysFileInfo;
import com.microservices.system.api.utils.FeignUtils;
import com.microservices.zone.resource.domain.vo.AiSkillParseResultVo;
import com.microservices.zone.resource.domain.vo.AiUploadedResourceParseResultVo;
import com.microservices.zone.resource.service.IAiResourceParseService;
import com.microservices.zone.utils.ZoneConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Skill ZIP解析服务实现
 */
@Service
public class AiResourceParseServiceImpl implements IAiResourceParseService {

    private static final Logger logger = LoggerFactory.getLogger(AiResourceParseServiceImpl.class);
    private static final String SKILL_MD_FILE = "SKILL.md";
    private static final String README_MD_FILE = "README.md";

    private final RemoteFileService remoteFileService;

    public AiResourceParseServiceImpl(RemoteFileService remoteFileService) {
        this.remoteFileService = remoteFileService;
    }

    @Override
    public AiSkillParseResultVo parseSkillZip(MultipartFile zipFile) {
        // 1. 校验文件大小
        if (zipFile.getSize() > ZoneConstants.SKILL_ZIP_MAX_SIZE) {
            throw new ServiceException("ZIP包大小不能超过50MB");
        }

        // 2. 校验文件格式
        String originalFilename = zipFile.getOriginalFilename();
        if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".zip")) {
            throw new ServiceException("仅支持ZIP格式文件");
        }

        try (ZipArchiveInputStream zais = new ZipArchiveInputStream(zipFile.getInputStream(), "GBK", true)) {
            ZipArchiveEntry entry;
            List<String> skillMdPaths = new ArrayList<>();
            List<FileTreeNode> fileTreeNodes = new ArrayList<>();

            // 第一遍遍历：收集所有一级和二级目录下的 SKILL.md 路径
            while ((entry = zais.getNextEntry()) != null) {
                String entryName = entry.getName();
                if (entry.isDirectory()) {
                    continue;
                }
                // 防止路径遍历攻击
                if (entryName.contains("..")) {
                    throw new ServiceException("ZIP包中包含非法路径");
                }

                // 收集一级或二级目录下的 SKILL.md
                if (SKILL_MD_FILE.equalsIgnoreCase(new File(entryName).getName())) {
                    int slashCount = entryName.split("/").length - 1;
                    if (slashCount <= 1) {  // 一级或二级
                        skillMdPaths.add(entryName);
                    }
                }

                // 收集文件信息构建目录树
                fileTreeNodes.add(new FileTreeNode(entryName, entry.getSize()));
            }

            // 确定要读取的 SKILL.md 路径
            String targetSkillMdPath;
            if (skillMdPaths.isEmpty()) {
                throw new ServiceException("ZIP包中必须包含 SKILL.md 文件");
            } else {
                // 优先选择根目录的 SKILL.md
                targetSkillMdPath = null;
                for (String path : skillMdPaths) {
                    if (path.equalsIgnoreCase("SKILL.md")) {
                        targetSkillMdPath = path;
                        break;
                    }
                }
                // 如果根目录不存在，选择第一个二级目录的 SKILL.md（按字母顺序）
                if (targetSkillMdPath == null) {
                    Collections.sort(skillMdPaths, String.CASE_INSENSITIVE_ORDER);
                    targetSkillMdPath = skillMdPaths.get(0);
                }
            }

            // 第二遍遍历：读取目标 SKILL.md 的内容
            String skillMdContent;
            try (ZipArchiveInputStream zais2 = new ZipArchiveInputStream(zipFile.getInputStream(), "GBK", true)) {
                ZipArchiveEntry entry2;
                skillMdContent = null;
                while ((entry2 = zais2.getNextEntry()) != null) {
                    if (targetSkillMdPath.equals(entry2.getName())) {
                        skillMdContent = readEntryContent(zais2);
                        break;
                    }
                }
            }

            // 3. 校验 SKILL.md 内容
            if (skillMdContent == null) {
                throw new ServiceException("ZIP包中必须包含 SKILL.md 文件");
            }

            // 4. 校验 SKILL.md 内容长度
            if (skillMdContent.trim().length() < ZoneConstants.SKILL_MD_MIN_LENGTH) {
                throw new ServiceException("SKILL.md 内容不得少于" + ZoneConstants.SKILL_MD_MIN_LENGTH + "字");
            }

            // 5. 构建目录树 JSON
            JSONObject fileTreeJson = buildFileTreeJson(fileTreeNodes);

            AiSkillParseResultVo result = new AiSkillParseResultVo();

            // 从 YAML front matter 中提取 name 和 description 字段
            String name = extractName(skillMdContent);
            result.setName(name);

            String description = extractDescription(skillMdContent);
            result.setSummary(description);

            result.setSkillMdContent(skillMdContent);

            result.setFileTreeJson(fileTreeJson.toJSONString());

            // 解析成功后，自动上传ZIP文件到File微服务（上传失败则阻断整个接口）
            SysFile sysFile = FeignUtils.getReturnData(
                    remoteFileService.upload(zipFile, "ai-resource", "skills", SecurityConstants.INNER)
            );
            if (sysFile == null || StringUtils.isEmpty(sysFile.getFileIdentifier())) {
                throw new ServiceException("文件上传返回为空，请重试");
            }
            result.setFileId(sysFile.getFileId());

            return result;

        } catch (IOException e) {
            logger.error("ZIP包解析失败", e);
            throw new ServiceException("ZIP包解析失败：" + e.getMessage());
        }
    }

    private String readEntryContent(ZipArchiveInputStream zais) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int len;
        while ((len = zais.read(buffer)) > 0) {
            baos.write(buffer, 0, len);
        }
        return new String(baos.toByteArray(), StandardCharsets.UTF_8);
    }

    @Override
    public AiUploadedResourceParseResultVo parseUploadedZip(Long fileId) {
        if (fileId == null) {
            throw new ServiceException("文件ID不能为空");
        }
        // 1. 获取文件信息
        SysFileInfo sysFileInfo = FeignUtils.getReturnData(remoteFileService.getFileDetail(fileId, SecurityConstants.INNER));
        if (sysFileInfo == null) {
            throw new ServiceException("文件不存在");
        }

        // 2. 校验文件格式
        String fileSuffix = sysFileInfo.getFileSuffix();
        if (!"zip".equalsIgnoreCase(fileSuffix)) {
            throw new ServiceException("仅支持ZIP格式文件");
        }

        // 3. 构建绝对路径
        String absolutePath = sysFileInfo.getFilePath();
        File zipFile = new File(absolutePath);
        if (!zipFile.exists()) {
            throw new ServiceException("ZIP文件不存在：" + sysFileInfo.getFilePath());
        }

        // 4. 使用 ZipFile 解析（读取 Central Directory，支持随机访问，大文件秒级响应）
        try (ZipFile zf = new ZipFile(zipFile, "GBK", true)) {
            String readmeContent = null;
            List<FileTreeNode> fileTreeNodes = new ArrayList<>();

            Enumeration<ZipArchiveEntry> entries = zf.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if (entry.isDirectory()) {
                    continue;
                }
                if (entryName.contains("..")) {
                    throw new ServiceException("ZIP包中包含非法路径");
                }

                fileTreeNodes.add(new FileTreeNode(entryName, entry.getSize()));
            }

            // 5. 查找并读取 README.md
            readmeContent = findAndReadReadmeMd(zf, fileTreeNodes);

            // 6. 构建目录树 JSON
            JSONObject fileTreeJson = buildFileTreeJsonFast(fileTreeNodes);

            AiUploadedResourceParseResultVo result = new AiUploadedResourceParseResultVo();
            result.setReadmeContent(readmeContent);
            result.setFileTreeJson(fileTreeJson);
            result.setFileSizeInfo(sysFileInfo.getFileSizeInfo());
            return result;

        } catch (Exception e) {
            logger.error("ZIP包解析失败: fileId={}, path={}", fileId, absolutePath, e);
            throw new ServiceException("ZIP包解析失败：" + e.getMessage());
        }
    }

    private String findAndReadReadmeMd(ZipFile zf, List<FileTreeNode> nodes) throws IOException {
        // 优先查找根目录 README.md（忽略大小写）
        Enumeration<ZipArchiveEntry> entries = zf.getEntries();
        ZipArchiveEntry readmeEntry = null;

        // 查找根目录下的 README.md
        while (entries.hasMoreElements()) {
            ZipArchiveEntry entry = entries.nextElement();
            String fileName = new File(entry.getName()).getName();
            if (README_MD_FILE.equalsIgnoreCase(fileName) && entry.getName().indexOf('/') == -1) {
                readmeEntry = entry;
                break;
            }
        }

        if (readmeEntry != null && !readmeEntry.isDirectory()) {
            return readZipEntryContent(zf, readmeEntry);
        }

        // 其次查找顶层唯一文件夹下的 README.md（忽略大小写）
        String topFolder = findSingleTopFolder(nodes);
        if (topFolder != null) {
            entries = zf.getEntries();  // 重置枚举
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                String expectedPath = topFolder + "/" + README_MD_FILE;
                if (expectedPath.equalsIgnoreCase(entry.getName())) {
                    readmeEntry = entry;
                    break;
                }
            }

            if (readmeEntry != null && !readmeEntry.isDirectory()) {
                return readZipEntryContent(zf, readmeEntry);
            }
        }

        return null;
    }

    private String findSingleTopFolder(List<FileTreeNode> nodes) {
        Set<String> topFolders = new HashSet<>();
        for (FileTreeNode node : nodes) {
            int slashIndex = node.path.indexOf('/');
            if (slashIndex > 0) {
                topFolders.add(node.path.substring(0, slashIndex));
            }
        }
        return topFolders.size() == 1 ? topFolders.iterator().next() : null;
    }

    private String readZipEntryContent(ZipFile zf, ZipArchiveEntry entry) throws IOException {
        try (InputStream is = zf.getInputStream(entry)) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int len;
            while ((len = is.read(buffer)) > 0) {
                baos.write(buffer, 0, len);
            }
            return new String(baos.toByteArray(), StandardCharsets.UTF_8);
        }
    }

    private JSONObject buildFileTreeJsonFast(List<FileTreeNode> nodes) {
        JSONObject root = new JSONObject();
        root.put("name", "root");
        root.put("type", "folder");
        JSONArray rootChildren = new JSONArray();
        root.put("children", rootChildren);

        Map<String, JSONObject> folderCache = new LinkedHashMap<>();

        for (FileTreeNode node : nodes) {
            String[] parts = node.path.split("/");
            JSONArray currentChildren = rootChildren;
            StringBuilder pathBuilder = new StringBuilder();

            for (int i = 0; i < parts.length; i++) {
                String part = parts[i];
                if (StringUtils.isEmpty(part)) {
                    continue;
                }
                boolean isFile = (i == parts.length - 1);

                if (isFile) {
                    JSONObject fileNode = new JSONObject();
                    fileNode.put("name", part);
                    fileNode.put("type", "file");
                    fileNode.put("size", node.size);
                    currentChildren.add(fileNode);
                } else {
                    pathBuilder.append(part);
                    String cacheKey = pathBuilder.toString();
                    pathBuilder.append('/');

                    JSONObject existingFolder = folderCache.get(cacheKey);
                    if (existingFolder == null) {
                        existingFolder = new JSONObject();
                        existingFolder.put("name", part);
                        existingFolder.put("type", "folder");
                        existingFolder.put("children", new JSONArray());
                        currentChildren.add(existingFolder);
                        folderCache.put(cacheKey, existingFolder);
                    }
                    currentChildren = existingFolder.getJSONArray("children");
                }
            }
        }

        // 排序：文件夹在前，文件在后，同类型按字母顺序
        sortChildren(rootChildren);

        // 如果根目录只有一个子文件夹，直接返回该子文件夹
        JSONArray children = root.getJSONArray("children");
        if (children.size() == 1 && "folder".equals(children.getJSONObject(0).get("type"))) {
            return children.getJSONObject(0);
        }
        return root;
    }

    private JSONObject buildFileTreeJson(List<FileTreeNode> nodes) {
        // 构建树形结构的目录
        JSONObject root = new JSONObject();
        root.put("name", "root");
        root.put("type", "folder");
        JSONArray rootChildren = new JSONArray();
        root.put("children", rootChildren);

        for (FileTreeNode node : nodes) {
            String[] parts = node.path.split("/");
            JSONArray currentChildren = rootChildren;

            for (int i = 0; i < parts.length; i++) {
                String part = parts[i];
                if (StringUtils.isEmpty(part)) {
                    continue;
                }
                boolean isFile = (i == parts.length - 1);

                if (isFile) {
                    JSONObject fileNode = new JSONObject();
                    fileNode.put("name", part);
                    fileNode.put("type", "file");
                    fileNode.put("size", node.size);
                    currentChildren.add(fileNode);
                } else {
                    // 查找是否已存在该文件夹
                    JSONObject existingFolder = null;
                    for (int j = 0; j < currentChildren.size(); j++) {
                        JSONObject child = currentChildren.getJSONObject(j);
                        if (part.equals(child.get("name")) && "folder".equals(child.get("type"))) {
                            existingFolder = child;
                            break;
                        }
                    }

                    if (existingFolder == null) {
                        existingFolder = new JSONObject();
                        existingFolder.put("name", part);
                        existingFolder.put("type", "folder");
                        existingFolder.put("children", new JSONArray());
                        currentChildren.add(existingFolder);
                    }
                    currentChildren = existingFolder.getJSONArray("children");
                }
            }
        }

        // 排序：文件夹在前，文件在后，同类型按字母顺序
        sortChildren(rootChildren);

        // 如果根目录只有一个子文件夹，直接返回该子文件夹
        JSONArray children = root.getJSONArray("children");
        if (children.size() == 1 && "folder".equals(children.getJSONObject(0).get("type"))) {
            return children.getJSONObject(0);
        }
        return root;
    }

    private void sortChildren(JSONArray children) {
        if (children == null || children.isEmpty()) {
            return;
        }
        if (children.size() > 1) {
            children.sort((a, b) -> {
                JSONObject ja = (JSONObject) a;
                JSONObject jb = (JSONObject) b;
                boolean aIsFolder = "folder".equals(ja.getString("type"));
                boolean bIsFolder = "folder".equals(jb.getString("type"));
                if (aIsFolder != bIsFolder) {
                    return aIsFolder ? -1 : 1;
                }
                return ja.getString("name").compareToIgnoreCase(jb.getString("name"));
            });
        }
        for (int i = 0; i < children.size(); i++) {
            JSONObject child = children.getJSONObject(i);
            if ("folder".equals(child.getString("type"))) {
                sortChildren(child.getJSONArray("children"));
            }
        }
    }

    private static class FileTreeNode {
        final String path;
        final long size;

        FileTreeNode(String path, long size) {
            this.path = path;
            this.size = size;
        }
    }

    /**
     * 从 SKILL.md 内容中提取 description 字段
     *
     * @param skillMdContent SKILL.md 文件内容
     * @return description 内容，如果不存在则返回 null
     */
    private String extractDescription(String skillMdContent) {
        if (StringUtils.isEmpty(skillMdContent)) {
            return null;
        }

        String[] lines = skillMdContent.split("\\n");
        boolean inFrontMatter = false;
        boolean foundStart = false;
        boolean inMultiLineDesc = false;
        StringBuilder multiLineDesc = new StringBuilder();

        for (String line : lines) {
            String trimmed = line.trim();

            // 检测 front matter 开始
            if (!foundStart && trimmed.equals("---")) {
                foundStart = true;
                inFrontMatter = true;
                continue;
            }

            // 检测 front matter 结束
            if (foundStart && trimmed.equals("---")) {
                break;
            }

            // 在 front matter 中查找 description
            if (inFrontMatter && trimmed.startsWith("description:")) {
                String descValue = trimmed.substring("description:".length()).trim();

                // 检查是否为多行格式
                if (descValue.equals("|") || descValue.equals(">")) {
                    inMultiLineDesc = true;
                    continue;
                } else {
                    // 单行格式，处理引号包裹的情况
                    if (descValue.startsWith("\"") && descValue.endsWith("\"")) {
                        return descValue.substring(1, descValue.length() - 1);
                    }
                    if (descValue.startsWith("'") && descValue.endsWith("'")) {
                        return descValue.substring(1, descValue.length() - 1);
                    }
                    return descValue;
                }
            }

            // 处理多行 description 内容
            if (inMultiLineDesc) {
                // 遇到新的 YAML key（xxx: 格式），结束多行模式
                if (trimmed.matches("^[a-zA-Z_]+:.*")) {
                    break;
                }
                // 追加非空行内容，用空格连接
                if (!trimmed.isEmpty()) {
                    if (multiLineDesc.length() > 0) {
                        multiLineDesc.append(" ");
                    }
                    multiLineDesc.append(trimmed);
                }
            }
        }

        // 返回多行拼接结果
        if (multiLineDesc.length() > 0) {
            return multiLineDesc.toString();
        }

        return null;
    }

    /**
     * 从 SKILL.md 内容中提取 name 字段
     *
     * @param skillMdContent SKILL.md 文件内容
     * @return name 内容，如果不存在则返回 null
     */
    private String extractName(String skillMdContent) {
        if (StringUtils.isEmpty(skillMdContent)) {
            return null;
        }

        String[] lines = skillMdContent.split("\\n");
        boolean inFrontMatter = false;
        boolean foundStart = false;

        for (String line : lines) {
            String trimmed = line.trim();

            if (!foundStart && trimmed.equals("---")) {
                foundStart = true;
                inFrontMatter = true;
                continue;
            }

            if (foundStart && trimmed.equals("---")) {
                break;
            }

            if (inFrontMatter && trimmed.startsWith("name:")) {
                String name = trimmed.substring("name:".length()).trim();
                if (name.startsWith("\"") && name.endsWith("\"")) {
                    return name.substring(1, name.length() - 1);
                }
                if (name.startsWith("'") && name.endsWith("'")) {
                    return name.substring(1, name.length() - 1);
                }
                return name;
            }
        }

        return null;
    }
}
