package com.easyagents.skill.util;

import com.easyagents.skill.exception.SkillValidationException;
import com.easyagents.skill.model.SkillDocument;
import com.easyagents.skill.model.SkillPackageLimits;
import com.easyagents.skill.model.SkillSourceLocation;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;

import java.io.StringReader;
import java.nio.charset.CharacterCodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

/**
 * SKILL.md 安全 frontmatter 解析与确定性序列化工具。
 */
public final class SkillFrontmatter {

    private static final String DOCUMENT_PATH = "SKILL.md";
    private static final Set<Tag> ALLOWED_TAGS = Set.of(
            Tag.MAP, Tag.SEQ, Tag.STR, Tag.BOOL, Tag.NULL,
            Tag.INT, Tag.FLOAT, Tag.TIMESTAMP
    );

    private SkillFrontmatter() {
    }

    /**
     * 解析 SKILL.md frontmatter，并兼容原有必填字段校验行为。
     *
     * @param content SKILL.md 原始内容
     * @return frontmatter 有序键值
     * @throws SkillValidationException 文档或必填字段不合法
     */
    public static Map<String, Object> parse(String content) {
        SkillDocument document = parseDocument(content, SkillPackageLimits.defaults());
        Map<String, Object> values = document.getFrontmatter().getValues();
        requireCoreField(values, "name");
        requireCoreField(values, "description");
        return values;
    }

    /**
     * 使用默认安全限额解析完整 SKILL.md。
     *
     * @param content SKILL.md 原始内容
     * @return 解析后的文档
     * @throws SkillValidationException 文档语法不合法
     */
    public static SkillDocument parseDocument(String content) {
        return parseDocument(content, SkillPackageLimits.defaults());
    }

    /**
     * 使用指定安全限额解析完整 SKILL.md。
     *
     * @param content SKILL.md 原始内容
     * @param limits 安全限额
     * @return 解析后的文档
     * @throws SkillValidationException 文档语法、类型或限额不合法
     */
    public static SkillDocument parseDocument(String content, SkillPackageLimits limits) {
        if (content == null || content.isBlank()) {
            throw error("SKILL_CONTENT_REQUIRED", 1, 1, "SKILL.md content is required.", null);
        }
        try {
            SkillUtf8.byteLength(content);
        } catch (CharacterCodingException e) {
            throw error("INVALID_UTF8", 1, 1,
                    "SKILL.md must be losslessly encodable as UTF-8.", e);
        }
        SkillPackageLimits effectiveLimits = limits == null ? SkillPackageLimits.defaults() : limits;
        FrontmatterSections sections = split(content);
        long frontmatterBytes;
        try {
            frontmatterBytes = SkillUtf8.byteLength(sections.yaml);
        } catch (CharacterCodingException e) {
            throw error("INVALID_UTF8", 2, 1,
                    "SKILL.md frontmatter must be losslessly encodable as UTF-8.", e);
        }
        if (frontmatterBytes > effectiveLimits.getMaxFrontmatterBytes()) {
            throw error("FRONTMATTER_TOO_LARGE", 1, 1,
                    "SKILL.md frontmatter exceeds " + effectiveLimits.getMaxFrontmatterBytes() + " bytes.", null);
        }

        LoaderOptions loaderOptions = new LoaderOptions();
        loaderOptions.setAllowDuplicateKeys(false);
        loaderOptions.setWarnOnDuplicateKeys(false);
        loaderOptions.setAllowRecursiveKeys(false);
        loaderOptions.setMaxAliasesForCollections(effectiveLimits.getMaxYamlAliases());
        loaderOptions.setNestingDepthLimit(effectiveLimits.getMaxYamlDepth());
        loaderOptions.setCodePointLimit(effectiveLimits.getMaxYamlCodePoints());
        loaderOptions.setTagInspector(tag -> isStandardTag(tag));

        try {
            Yaml yaml = new Yaml(new SafeConstructor(loaderOptions));
            Node rootNode = yaml.compose(new StringReader(sections.yaml));
            Object loaded = yaml.load(sections.yaml);
            if (!(loaded instanceof Map<?, ?> map)) {
                throw error("FRONTMATTER_ROOT_NOT_MAP", 2, 1,
                        "SKILL.md frontmatter root must be a YAML mapping.", null);
            }
            LinkedHashMap<String, Object> values = copyStringMap(map, 0, effectiveLimits.getMaxYamlDepth());
            return new SkillDocument(content, values, sections.body,
                    collectTopLevelLocations(rootNode), List.of());
        } catch (SkillValidationException e) {
            throw e;
        } catch (MarkedYAMLException e) {
            int line = e.getProblemMark() == null ? 2 : e.getProblemMark().getLine() + 2;
            int column = e.getProblemMark() == null ? 1 : e.getProblemMark().getColumn() + 1;
            throw error("INVALID_FRONTMATTER_YAML", line, column,
                    "Invalid SKILL.md frontmatter: " + safeProblem(e), e);
        } catch (YAMLException e) {
            throw error("INVALID_FRONTMATTER_YAML", 2, 1,
                    "Invalid SKILL.md frontmatter: " + safeProblem(e), e);
        }
    }

    /**
     * 将有序 frontmatter 与 Markdown 正文确定性序列化为 SKILL.md。
     *
     * @param values frontmatter 有序键值
     * @param markdownBody Markdown 正文
     * @return 完整 SKILL.md
     * @throws SkillValidationException frontmatter 包含不安全类型
     */
    public static String serialize(Map<String, Object> values, String markdownBody) {
        LinkedHashMap<String, Object> safeValues = copyStringMap(
                values == null ? Map.of() : values, 0, SkillPackageLimits.defaults().getMaxYamlDepth());
        DumperOptions dumperOptions = new DumperOptions();
        dumperOptions.setAllowUnicode(true);
        dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
        dumperOptions.setIndent(2);
        dumperOptions.setIndicatorIndent(0);
        dumperOptions.setPrettyFlow(true);
        dumperOptions.setSplitLines(false);
        dumperOptions.setWidth(4_096);
        dumperOptions.setLineBreak(DumperOptions.LineBreak.UNIX);
        dumperOptions.setDereferenceAliases(true);

        LoaderOptions loaderOptions = new LoaderOptions();
        loaderOptions.setAllowDuplicateKeys(false);
        Representer representer = new Representer(dumperOptions);
        representer.setTimeZone(TimeZone.getTimeZone("UTC"));
        Yaml yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions, loaderOptions);
        String yamlContent = yaml.dump(safeValues);
        String body = markdownBody == null ? "" : markdownBody;
        return "---\n" + yamlContent + "---\n" + body;
    }

    private static FrontmatterSections split(String content) {
        int yamlStart;
        if (content.startsWith("---\n")) {
            yamlStart = 4;
        } else if (content.startsWith("---\r\n")) {
            yamlStart = 5;
        } else {
            throw error("FRONTMATTER_START_REQUIRED", 1, 1,
                    "SKILL.md must start with a frontmatter delimiter.", null);
        }

        int lineStart = yamlStart;
        while (lineStart <= content.length()) {
            int lineEnd = content.indexOf('\n', lineStart);
            int contentEnd = lineEnd < 0 ? content.length() : lineEnd;
            String line = content.substring(lineStart, contentEnd);
            if (line.endsWith("\r")) {
                line = line.substring(0, line.length() - 1);
            }
            if ("---".equals(line)) {
                int bodyStart = lineEnd < 0 ? content.length() : lineEnd + 1;
                return new FrontmatterSections(content.substring(yamlStart, lineStart),
                        content.substring(bodyStart));
            }
            if (lineEnd < 0) {
                break;
            }
            lineStart = lineEnd + 1;
        }
        throw error("FRONTMATTER_NOT_CLOSED", 1, 1,
                "SKILL.md frontmatter is not closed.", null);
    }

    private static LinkedHashMap<String, Object> copyStringMap(Map<?, ?> source, int depth, int maxDepth) {
        if (depth > maxDepth) {
            throw error("YAML_DEPTH_LIMIT", 2, 1,
                    "SKILL.md frontmatter exceeds the YAML nesting depth limit.", null);
        }
        LinkedHashMap<String, Object> target = new LinkedHashMap<>();
        for (Map.Entry<?, ?> entry : source.entrySet()) {
            if (!(entry.getKey() instanceof String key) || key.isBlank()) {
                throw error("INVALID_FRONTMATTER_KEY", 2, 1,
                        "SKILL.md frontmatter keys must be non-blank strings.", null);
            }
            target.put(key, copyValue(entry.getValue(), depth + 1, maxDepth));
        }
        return target;
    }

    private static Object copyValue(Object value, int depth, int maxDepth) {
        if (value == null || value instanceof String || value instanceof Boolean || value instanceof Number) {
            return value;
        }
        if (value instanceof Date date) {
            return new Date(date.getTime());
        }
        if (value instanceof Map<?, ?> map) {
            return copyStringMap(map, depth, maxDepth);
        }
        if (value instanceof List<?> list) {
            if (depth > maxDepth) {
                throw error("YAML_DEPTH_LIMIT", 2, 1,
                        "SKILL.md frontmatter exceeds the YAML nesting depth limit.", null);
            }
            List<Object> target = new ArrayList<>(list.size());
            for (Object item : list) {
                target.add(copyValue(item, depth + 1, maxDepth));
            }
            return target;
        }
        throw error("UNSUPPORTED_FRONTMATTER_TYPE", 2, 1,
                "Unsupported SKILL.md frontmatter value type: " + value.getClass().getName(), null);
    }

    private static boolean isStandardTag(Tag tag) {
        return tag != null && ALLOWED_TAGS.contains(tag);
    }

    /**
     * 收集顶层 frontmatter key 在完整 SKILL.md 中的一基位置。
     *
     * @param rootNode YAML 根节点
     * @return 按源码顺序排列的字段位置
     */
    private static Map<String, SkillSourceLocation> collectTopLevelLocations(Node rootNode) {
        LinkedHashMap<String, SkillSourceLocation> locations = new LinkedHashMap<>();
        if (!(rootNode instanceof MappingNode mappingNode)) {
            return locations;
        }
        for (NodeTuple tuple : mappingNode.getValue()) {
            if (!(tuple.getKeyNode() instanceof ScalarNode scalarNode)
                    || scalarNode.getStartMark() == null) {
                continue;
            }
            locations.putIfAbsent(scalarNode.getValue(), new SkillSourceLocation(
                    scalarNode.getStartMark().getLine() + 2,
                    scalarNode.getStartMark().getColumn() + 1));
        }
        return locations;
    }

    private static void requireCoreField(Map<String, Object> values, String key) {
        Object value = values.get(key);
        if (value == null || value.toString().isBlank()) {
            throw error("MISSING_" + key.toUpperCase(), 2, 1,
                    "SKILL.md frontmatter " + key + " is required.", null);
        }
    }

    private static String safeProblem(Exception exception) {
        String message = exception.getMessage();
        if (message == null || message.isBlank()) {
            return exception.getClass().getSimpleName();
        }
        int lineBreak = message.indexOf('\n');
        return lineBreak < 0 ? message : message.substring(0, lineBreak);
    }

    private static SkillValidationException error(String code, Integer line, Integer column,
                                                  String message, Throwable cause) {
        return new SkillValidationException(code, DOCUMENT_PATH, line, column, message, cause);
    }

    private record FrontmatterSections(String yaml, String body) {
    }
}
