package com.easyagents.skill.codec;

import com.easyagents.skill.exception.SkillPackageException;
import com.easyagents.skill.factory.SkillFactory;
import com.easyagents.skill.model.Skill;
import com.easyagents.skill.model.SkillPackage;
import com.easyagents.skill.model.SkillPackageLayout;
import com.easyagents.skill.model.SkillPackageLimits;
import com.easyagents.skill.model.SkillResource;
import com.easyagents.skill.model.SkillResourceKind;
import com.easyagents.skill.store.SkillContentStage;
import com.easyagents.skill.store.SkillContentStore;
import com.easyagents.skill.store.memory.InMemorySkillContentStore;
import com.easyagents.skill.util.SkillHashes;
import com.easyagents.skill.util.SkillPaths;
import org.apache.commons.compress.archivers.zip.UnixStat;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.junit.Assert;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * ZipSkillPackageCodec 安全边界与 round-trip 测试。
 */
public class ZipSkillPackageCodecTest {

    /**
     * 导出计划只保留已有文本源，不能再次持有整包文本 byte 数组。
     *
     * @throws ClassNotFoundException 内部输出计划类型缺失
     */
    @Test
    public void outputPlanDoesNotRetainTextByteArrays() throws ClassNotFoundException {
        Class<?> outputFile = Class.forName(ZipSkillPackageCodec.class.getName() + "$OutputFile");

        Assert.assertFalse(Arrays.stream(outputFile.getDeclaredFields())
                .anyMatch(field -> field.getType() == byte[].class));
    }

    /**
     * 非 ZIP 输入与带任意前导数据的归档被拒绝。
     */
    @Test
    public void rejectInvalidZipSignature() {
        assertPackageCode("INVALID_ZIP", utf8("not-a-zip"), SkillPackageReadOptions.defaults());
        byte[] zip = zip(files("skill-a/SKILL.md", utf8(skillMd("skill-a"))));
        byte[] withPreamble = new byte[zip.length + 1];
        withPreamble[0] = 1;
        System.arraycopy(zip, 0, withPreamble, 1, zip.length);
        assertPackageCode("INVALID_ZIP", withPreamble, SkillPackageReadOptions.defaults());
    }

    /**
     * 单目录包导入通用资源。
     */
    @Test
    public void decodeWrappedSingleSkillWithGenericResources() {
        InMemorySkillContentStore store = new InMemorySkillContentStore();
        SkillPackageReadResult result = decode(new ZipSkillPackageCodec(store), zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/rules.md", utf8("# Rules"),
                "skill-a/scripts/run.rb", utf8("puts 'ok'"),
                "skill-a/examples/sample.json", utf8("{\"ok\":true}"),
                "skill-a/docs/notes.txt", utf8("notes"),
                "skill-a/assets/logo.bin", new byte[]{0, 1, 2}
        )));

        Assert.assertEquals(SkillPackageLayout.SINGLE_DIRECTORY,
                result.getSkillPackage().getLayout());
        Skill skill = result.getSkillPackage().getSkills().get(0);
        Assert.assertEquals("skill-a", skill.getPackageRoot());
        Assert.assertEquals(5, skill.getResources().size());
        Assert.assertTrue(skill.getResources().stream().anyMatch(resource ->
                resource.getKind() == SkillResourceKind.EXAMPLE));
        Assert.assertTrue(skill.getResources().stream().anyMatch(resource ->
                resource.getKind() == SkillResourceKind.OTHER));
        SkillResource binary = skill.getResources().stream()
                .filter(resource -> resource.getKind() == SkillResourceKind.ASSET)
                .findFirst().orElseThrow();
        Assert.assertTrue(store.exists(binary.getContentRef()));
    }

    /**
     * 根目录直压单 Skill 被正确识别。
     */
    @Test
    public void decodeRootSkillPackage() {
        SkillPackageReadResult result = decode(new ZipSkillPackageCodec(), zip(files(
                "SKILL.md", utf8(skillMd("root-skill")),
                "references/a.md", utf8("# A")
        )));

        Assert.assertEquals(SkillPackageLayout.ROOT_SKILL, result.getSkillPackage().getLayout());
        Assert.assertEquals("root-skill", result.getSkillPackage().getSkills().get(0).getPackageRoot());
    }

    /**
     * macOS Finder 生成的 AppleDouble 与目录元数据不会影响根目录或单目录 Skill 导入。
     */
    @Test
    public void ignoreMacOsMetadataForSingleSkillPackages() {
        SkillPackageReadResult rootResult = decode(new ZipSkillPackageCodec(), zip(files(
                "SKILL.md", utf8(skillMd("root-skill")),
                "references/a.md", utf8("# A"),
                "__MACOSX/._SKILL.md", utf8("metadata"),
                "__MACOSX/references/._a.md", utf8("metadata"),
                ".DS_Store", utf8("metadata")
        )));
        Assert.assertEquals(SkillPackageLayout.ROOT_SKILL, rootResult.getSkillPackage().getLayout());
        Assert.assertEquals(1, rootResult.getSkillPackage().getSkills().get(0).getResources().size());

        SkillPackageReadResult wrappedResult = decode(new ZipSkillPackageCodec(), zip(files(
                "docx/SKILL.md", utf8(skillMd("docx")),
                "docx/references/a.md", utf8("# A"),
                "__MACOSX/docx/._SKILL.md", utf8("metadata"),
                "__MACOSX/docx/references/._a.md", utf8("metadata"),
                "docx/.DS_Store", utf8("metadata")
        )));
        Assert.assertEquals(SkillPackageLayout.SINGLE_DIRECTORY,
                wrappedResult.getSkillPackage().getLayout());
        Assert.assertEquals(1, wrappedResult.getSkillPackage().getSkills().get(0).getResources().size());
    }

    /**
     * 多目录包按稳定目录顺序导入。
     */
    @Test
    public void decodeMultipleSkills() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        SkillPackageReadResult result = decode(codec, zip(files(
                "skill-b/SKILL.md", utf8(skillMd("skill-b")),
                "skill-a/SKILL.md", utf8(skillMd("skill-a"))
        )));

        Assert.assertEquals(SkillPackageLayout.MULTI_DIRECTORY, result.getSkillPackage().getLayout());
        Assert.assertEquals(List.of("skill-a", "skill-b"), result.getSkillPackage().getSkills().stream()
                .map(Skill::getName).toList());

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        SkillPackageWriteResult writeResult = codec.encode(
                result.getSkillPackage(), output, SkillPackageWriteOptions.defaults());
        Assert.assertEquals(SkillPackageLayout.MULTI_DIRECTORY, writeResult.getLayout());
        Assert.assertEquals(List.of("skill-a", "skill-b"), decode(codec, output.toByteArray())
                .getSkillPackage().getSkills().stream().map(Skill::getName).toList());
    }

    /**
     * 多个标准 Skill 目录被同一个父目录包装后仍按独立 Skill 解码。
     */
    @Test
    public void decodeMultipleSkillsWithParentDirectory() {
        SkillPackageReadResult result = decode(new ZipSkillPackageCodec(), zip(files(
                "skill-bundle/skill-b/SKILL.md", utf8(skillMd("skill-b")),
                "skill-bundle/skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-bundle/skill-a/references/a.md", utf8("# A"),
                "__MACOSX/skill-bundle/skill-a/._SKILL.md", utf8("metadata"),
                "skill-bundle/.DS_Store", utf8("metadata")
        )));

        Assert.assertEquals(SkillPackageLayout.MULTI_DIRECTORY, result.getSkillPackage().getLayout());
        Assert.assertEquals(List.of("skill-a", "skill-b"), result.getSkillPackage().getSkills().stream()
                .map(Skill::getName).toList());
        Assert.assertEquals(List.of("skill-a", "skill-b"), result.getSkillPackage().getSkills().stream()
                .map(Skill::getPackageRoot).toList());
        Assert.assertEquals(1, result.getSkillPackage().getSkills().get(0).getResources().size());
    }

    /**
     * 父目录包装布局中的散落文件不归属于任何 Skill 时拒绝。
     */
    @Test
    public void rejectUnownedFileInParentDirectory() {
        assertPackageCode("UNOWNED_ROOT_FILE", zip(files(
                "skill-bundle/skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-bundle/skill-b/SKILL.md", utf8(skillMd("skill-b")),
                "skill-bundle/README.md", utf8("orphan")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * Skill 目录来自不同层级或不同父目录时拒绝，避免产生含糊归属。
     */
    @Test
    public void rejectMixedSkillDirectoryParents() {
        assertPackageCode("MIXED_PACKAGE_LAYOUT", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-bundle/skill-b/SKILL.md", utf8(skillMd("skill-b"))
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 嵌套 frontmatter 与未知字段在 Codec 中保留。
     */
    @Test
    public void preserveNestedFrontmatter() {
        String markdown = "---\nname: nested-skill\ndescription: Nested metadata\n"
                + "metadata:\n  owner: team-a\n  levels:\n    - 1\n    - 2\n---\n# Nested\n";

        Skill skill = decode(new ZipSkillPackageCodec(), zip(files(
                "nested-skill/SKILL.md", utf8(markdown)
        ))).getSkillPackage().getSkills().get(0);

        Assert.assertTrue(skill.getDocument().getFrontmatter().get("metadata") instanceof Map);
        Assert.assertEquals(markdown, skill.getSkillContent());
    }

    /**
     * 根 SKILL.md 与包装 Skill 目录混用时拒绝。
     */
    @Test
    public void rejectMixedPackageLayout() {
        assertPackageCode("MIXED_PACKAGE_LAYOUT", zip(files(
                "SKILL.md", utf8(skillMd("root-skill")),
                "other-skill/SKILL.md", utf8(skillMd("other-skill"))
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 包装布局中的根散落文件被拒绝。
     */
    @Test
    public void rejectUnownedRootFile() {
        assertPackageCode("UNOWNED_ROOT_FILE", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "README.md", utf8("orphan")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 路径穿越被拒绝。
     */
    @Test
    public void rejectPathTraversal() {
        assertPackageCode("UNSAFE_ENTRY_PATH", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/../escape.txt", utf8("escape")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 大小写冲突路径被拒绝。
     */
    @Test
    public void rejectCaseConflictingPaths() {
        assertPackageCode("PATH_COLLISION", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/A.md", utf8("A"),
                "skill-a/references/a.md", utf8("B")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 普通文件不能同时作为另一个文件的祖先路径，且不受 entry 顺序影响。
     */
    @Test
    public void rejectFileAndDescendantPathConflictsInBothOrders() {
        assertPackageCode("PATH_HIERARCHY_CONFLICT", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references", utf8("file"),
                "skill-a/references/a.md", utf8("descendant")
        )), SkillPackageReadOptions.defaults());
        assertPackageCode("PATH_HIERARCHY_CONFLICT", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("descendant"),
                "skill-a/references", utf8("file")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * Unicode NFC 等价路径被拒绝。
     */
    @Test
    public void rejectUnicodeEquivalentPaths() {
        assertPackageCode("DUPLICATE_ENTRY", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/\u00e9.md", utf8("A"),
                "skill-a/references/e\u0301.md", utf8("B")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * 非法 UTF-8 文本被拒绝并指出文件路径。
     */
    @Test
    public void rejectInvalidUtf8() {
        byte[] bytes = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", new byte[]{(byte) 0xC3, 0x28}
        ));
        try {
            decode(new ZipSkillPackageCodec(), bytes);
            Assert.fail("Invalid UTF-8 should fail.");
        } catch (SkillPackageException e) {
            Assert.assertEquals("INVALID_UTF8", e.getCode());
            Assert.assertEquals("skill-a/references/a.md", e.getPath());
        }
    }

    /**
     * ZIP entry 原始文件名字节不是严格 UTF-8 时拒绝导入。
     */
    @Test
    public void rejectInvalidUtf8EntryName() {
        assertPackageCode("INVALID_UTF8_ENTRY_NAME", invalidUtf8EntryNameZip(),
                SkillPackageReadOptions.defaults());
    }

    /**
     * 中央目录 CRC 与实际内容不一致时拒绝。
     */
    @Test
    public void rejectCrcMismatch() {
        byte[] original = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a"))
        ));

        assertPackageCode("CRC_MISMATCH", tamperFirstCentralDirectoryCrc(original),
                SkillPackageReadOptions.defaults());
    }

    /**
     * Unix 符号链接 entry 被拒绝。
     */
    @Test
    public void rejectUnixSymlink() {
        assertPackageCode("SYMLINK_ENTRY", symlinkZip("skill-a/assets/link"),
                SkillPackageReadOptions.defaults());
        assertPackageCode("SYMLINK_ENTRY", symlinkZip("__MACOSX/._link"),
                SkillPackageReadOptions.defaults());
    }

    /**
     * 目录和待忽略系统 entry 也必须先通过路径安全校验。
     */
    @Test
    public void rejectUnsafeDirectoryBeforeIgnoringEntries() {
        assertPackageCode("UNSAFE_ENTRY_PATH", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a"))
        ), "../../"), SkillPackageReadOptions.defaults());
        assertPackageCode("UNSAFE_ENTRY_PATH", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "__MACOSX/../../escape", utf8("escape")
        )), SkillPackageReadOptions.defaults());
    }

    /**
     * entry 数、文本大小和压缩比限额均在读取前生效。
     */
    @Test
    public void enforcePackageLimits() {
        byte[] twoEntries = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("A")
        ));
        assertPackageCode("ENTRY_COUNT_LIMIT", twoEntries, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxEntryCount(1).build()));

        byte[] largeText = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("x".repeat(300))
        ));
        assertPackageCode("TEXT_FILE_SIZE_LIMIT", largeText, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxTextFileBytes(128).build()));

        byte[] compressible = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/bomb.md", utf8("a".repeat(20_000))
        ));
        assertPackageCode("COMPRESSION_RATIO_LIMIT", compressible, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxCompressionRatio(2D).build()));
    }

    /**
     * 中央目录预扫描不能信任 EOCD 的较小声明值，且必须在构造 ZipFile 前拒绝超量条目。
     */
    @Test
    public void preflightCountsActualCentralDirectoryEntries() {
        assertPackageCode("ENTRY_COUNT_LIMIT", syntheticCentralDirectoryZip(3, 1),
                new SkillPackageReadOptions(
                        SkillPackageLimits.builder().maxEntryCount(2).build()));
    }

    /**
     * 未超限时，EOCD 声明数量与中央目录实际数量不一致必须作为非法 ZIP 拒绝。
     */
    @Test
    public void rejectInconsistentCentralDirectoryEntryCount() {
        assertPackageCode("INVALID_ZIP", syntheticCentralDirectoryZip(3, 1),
                new SkillPackageReadOptions(
                        SkillPackageLimits.builder().maxEntryCount(4).build()));
    }

    /**
     * ZIP64 中央目录也必须按实际条目数在 ZipFile 对象化前执行上限。
     */
    @Test
    public void preflightCountsActualZip64CentralDirectoryEntries() {
        assertPackageCode("ENTRY_COUNT_LIMIT", syntheticZip64CentralDirectoryZip(3, 1),
                new SkillPackageReadOptions(
                        SkillPackageLimits.builder().maxEntryCount(2).build()));
    }

    /**
     * 中央目录末尾被截断时，即使 EOCD 边界同步缩短也必须拒绝。
     */
    @Test
    public void rejectTruncatedCentralDirectory() {
        byte[] truncated = truncateLastCentralDirectoryByte(
                syntheticCentralDirectoryZip(3, 3));

        assertPackageCode("INVALID_ZIP", truncated,
                new SkillPackageReadOptions(
                        SkillPackageLimits.builder().maxEntryCount(4).build()));
    }

    /**
     * 总量、二进制、路径深度、路径长度和压缩输入限制均生效。
     */
    @Test
    public void enforceAdditionalPackageLimits() {
        byte[] totalSize = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("x".repeat(120))
        ));
        assertPackageCode("TOTAL_SIZE_LIMIT", totalSize, new SkillPackageReadOptions(
                SkillPackageLimits.builder()
                        .maxTextFileBytes(128)
                        .maxBinaryFileBytes(128)
                        .maxTotalUncompressedBytes(180)
                        .build()));

        byte[] binarySize = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/assets/a.bin", new byte[]{1, 2, 3}
        ));
        assertPackageCode("BINARY_FILE_SIZE_LIMIT", binarySize, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxBinaryFileBytes(2).build()));

        byte[] deepPath = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/nested/a.md", utf8("A")
        ));
        assertPackageCode("PATH_DEPTH_LIMIT", deepPath, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxPathDepth(2).build()));

        byte[] longPath = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/this-name-is-far-too-long.md", utf8("A")
        ));
        assertPackageCode("PATH_LENGTH_LIMIT", longPath, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxPathLength(30).build()));

        byte[] compressedInput = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a"))
        ));
        assertPackageCode("COMPRESSED_SIZE_LIMIT", compressedInput, new SkillPackageReadOptions(
                SkillPackageLimits.builder().maxCompressedPackageBytes(64).build()));
    }

    /**
     * 后续校验失败时兼容存储也不会提前提交资产。
     */
    @Test
    public void rollbackStagedContentOnFailure() {
        TrackingContentStore store = new TrackingContentStore(0);
        byte[] bytes = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/assets/data.bin", utf8("abc"),
                "skill-b/SKILL.md", utf8(skillMd("Invalid Name"))
        ));

        try {
            decode(new ZipSkillPackageCodec(store), bytes);
            Assert.fail("Invalid multi-Skill package should fail.");
        } catch (SkillPackageException expected) {
            Assert.assertEquals(1, store.stageCalls);
            Assert.assertEquals(0, store.stagedCount);
            Assert.assertEquals(0, store.committedCount);
        }
    }

    /**
     * 只读预检对有效二进制资源也只返回哈希身份，并回滚暂存内容。
     */
    @Test
    public void reportOnlyRollsBackValidBinaryContentWithoutCommit() {
        TrackingContentStore store = new TrackingContentStore(0);
        SkillPackageReadResult result = new ZipSkillPackageCodec(store).decode(
                new ByteArrayInputStream(zip(files(
                        "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                        "skill-a/assets/data.bin", new byte[]{1, 2, 3}
                ))), SkillPackageReadOptions.reportOnly());

        SkillResource resource = result.getSkillPackage().getSkills().get(0).getResources().get(0);
        Assert.assertFalse(result.getValidationReport().hasErrors());
        Assert.assertEquals(1, store.stageCalls);
        Assert.assertEquals(1, store.rollbackCalls);
        Assert.assertEquals(0, store.commitAttempts);
        Assert.assertEquals(0, store.stagedCount);
        Assert.assertEquals(0, store.committedCount);
        Assert.assertFalse(store.exists(resource.getContentRef()));
    }

    /**
     * 只读预检对可解析的无效包返回完整报告，并回滚二进制暂存内容。
     */
    @Test
    public void reportOnlyReturnsValidationErrorsAndRollsBackBinaryContent() {
        TrackingContentStore store = new TrackingContentStore(0);
        SkillPackageReadResult result = new ZipSkillPackageCodec(store).decode(
                new ByteArrayInputStream(zip(files(
                        "invalid-skill/SKILL.md", utf8(skillMd("Invalid Name")),
                        "invalid-skill/assets/data.bin", new byte[]{1, 2, 3}
                ))), new SkillPackageReadOptions(
                        SkillPackageLimits.defaults(), SkillPackageReadMode.REPORT_ONLY));

        Assert.assertTrue(result.getValidationReport().hasErrors());
        Assert.assertTrue(result.getValidationReport().getIssues().stream().anyMatch(issue ->
                "INVALID_NAME".equals(issue.getCode())));
        Assert.assertEquals(1, store.stageCalls);
        Assert.assertEquals(1, store.rollbackCalls);
        Assert.assertEquals(0, store.commitAttempts);
        Assert.assertEquals(0, store.stagedCount);
        Assert.assertEquals(0, store.committedCount);
    }

    /**
     * 资源的文本或二进制表示由文件类型决定，不受顶层语义目录约束。
     */
    @Test
    public void decodeContentModeIndependentlyFromSemanticDirectory() throws Exception {
        InMemorySkillContentStore store = new InMemorySkillContentStore();
        byte[] opaqueScript = new byte[]{0, (byte) 0xFF, 1};
        SkillPackageReadResult result = decode(new ZipSkillPackageCodec(store), zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/assets/template.md", utf8("# Template"),
                "skill-a/scripts/helper.bin", opaqueScript
        )));

        List<SkillResource> resources = result.getSkillPackage().getSkills().get(0).getResources();
        SkillResource template = resources.stream()
                .filter(resource -> "assets/template.md".equals(resource.getPath()))
                .findFirst().orElseThrow();
        SkillResource helper = resources.stream()
                .filter(resource -> "scripts/helper.bin".equals(resource.getPath()))
                .findFirst().orElseThrow();

        Assert.assertTrue(template.isText());
        Assert.assertEquals("# Template", template.getTextContent());
        Assert.assertFalse(helper.isText());
        try (InputStream input = store.open(helper.getContentRef())) {
            Assert.assertArrayEquals(opaqueScript, input.readAllBytes());
        }
    }

    /**
     * 预检回滚首次失败时，异常清理路径会重试尚未释放的 stage。
     */
    @Test
    public void retryTransientReportOnlyRollbackFailure() {
        FlakyRollbackContentStore store = new FlakyRollbackContentStore();

        try {
            new ZipSkillPackageCodec(store).decode(
                    new ByteArrayInputStream(zip(files(
                            "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                            "skill-a/assets/data.bin", new byte[]{1, 2, 3}
                    ))), SkillPackageReadOptions.reportOnly());
            Assert.fail("Transient rollback failure should remain visible to the caller.");
        } catch (SkillPackageException expected) {
            Assert.assertEquals("SKILL_CONTENT_ROLLBACK_ERROR", expected.getCode());
            Assert.assertEquals(2, store.rollbackAttempts);
            Assert.assertTrue(store.rollbackCompleted);
        }
    }

    /**
     * 多 Skill 预检报告为每个相对路径补齐各自根目录。
     */
    @Test
    public void reportOnlyPrefixesValidationPathsForMultipleSkills() {
        SkillPackageReadResult result = new ZipSkillPackageCodec().decode(
                new ByteArrayInputStream(zip(files(
                        "skill-a/SKILL.md", utf8(invalidDescriptionSkillMd("skill-a")),
                        "skill-b/SKILL.md", utf8(invalidDescriptionSkillMd("skill-b"))
                ))), SkillPackageReadOptions.reportOnly());

        Assert.assertTrue(result.getValidationReport().getIssues().stream().anyMatch(issue ->
                "DESCRIPTION_REQUIRED".equals(issue.getCode())
                        && "skill-a/SKILL.md".equals(issue.getPath())));
        Assert.assertTrue(result.getValidationReport().getIssues().stream().anyMatch(issue ->
                "DESCRIPTION_REQUIRED".equals(issue.getCode())
                        && "skill-b/SKILL.md".equals(issue.getPath())));
    }

    /**
     * 多 Skill 标准导出的聚合错误也必须包含各自输出根目录。
     */
    @Test
    public void exportPrefixesValidationPathsForMultipleSkills() {
        Skill first = SkillFactory.create(skillMd("skill-a"));
        first.setPackageRoot("skill-a");
        first.getDocument().removeFrontmatter("description");
        first.setDocument(first.getDocument());
        Skill second = SkillFactory.create(skillMd("skill-b"));
        second.setPackageRoot("skill-b");
        second.getDocument().removeFrontmatter("description");
        second.setDocument(second.getDocument());
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        try {
            new ZipSkillPackageCodec().encode(
                    new SkillPackage(SkillPackageLayout.MULTI_DIRECTORY,
                            List.of(first, second)),
                    output, SkillPackageWriteOptions.defaults());
            Assert.fail("Invalid multi-Skill export should fail.");
        } catch (SkillPackageException exception) {
            Assert.assertTrue(exception.getReport().getIssues().stream().anyMatch(issue ->
                    "DESCRIPTION_REQUIRED".equals(issue.getCode())
                            && "skill-a/SKILL.md".equals(issue.getPath())));
            Assert.assertTrue(exception.getReport().getIssues().stream().anyMatch(issue ->
                    "DESCRIPTION_REQUIRED".equals(issue.getCode())
                            && "skill-b/SKILL.md".equals(issue.getPath())));
            Assert.assertEquals(0, output.size());
        }
    }

    /**
     * 包装 Skill 的 YAML 解析错误也必须返回完整包路径。
     */
    @Test
    public void prefixWrappedSkillPathForMalformedYaml() {
        try {
            new ZipSkillPackageCodec().decode(new ByteArrayInputStream(zip(files(
                    "skill-a/SKILL.md", utf8(
                            "---\nname: skill-a\ndescription: [\n---\n# Invalid\n")
            ))), SkillPackageReadOptions.reportOnly());
            Assert.fail("Malformed wrapped SKILL.md should fail.");
        } catch (SkillPackageException exception) {
            Assert.assertNotNull(exception.getReport());
            Assert.assertTrue(exception.getReport().getIssues().stream().anyMatch(issue ->
                    "skill-a/SKILL.md".equals(issue.getPath())));
        }
    }

    /**
     * 只读预检仍拒绝路径穿越等不可解析的结构安全错误。
     */
    @Test
    public void reportOnlyStillRejectsStructuralSecurityErrors() {
        assertPackageCode("UNSAFE_ENTRY_PATH", zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/../escape.txt", utf8("escape")
        )), SkillPackageReadOptions.reportOnly());
    }

    /**
     * 部分 commit 失败时释放已提交引用并回滚其余暂存内容。
     */
    @Test
    public void compensatePartiallyCommittedContent() {
        TrackingContentStore store = new TrackingContentStore(2);
        byte[] bytes = zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/assets/a.bin", utf8("a"),
                "skill-a/assets/b.bin", utf8("b")
        ));

        try {
            decode(new ZipSkillPackageCodec(store), bytes);
            Assert.fail("Second commit should fail.");
        } catch (SkillPackageException expected) {
            Assert.assertEquals(2, store.stageCalls);
            Assert.assertEquals(0, store.stagedCount);
            Assert.assertEquals(0, store.committedCount);
        }
    }

    /**
     * 全包校验成功后统一提交资产。
     */
    @Test
    public void commitStagedContentAfterValidation() {
        CountingContentStore store = new CountingContentStore();

        decode(new ZipSkillPackageCodec(store), zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/assets/data.bin", utf8("abc")
        )));

        Assert.assertEquals(1, store.putCount);
    }

    /**
     * 编码输出稳定，且 decode-encode-decode 语义等价。
     */
    @Test
    public void stableEncodeAndSemanticRoundTrip() {
        InMemorySkillContentStore store = new InMemorySkillContentStore();
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec(store);
        SkillPackageReadResult first = decode(codec, zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("# A"),
                "skill-a/assets/data.bin", new byte[]{1, 2, 3}
        )));
        ByteArrayOutputStream firstZip = new ByteArrayOutputStream();
        SkillPackageWriteResult firstWrite = codec.encode(first.getSkillPackage(), firstZip,
                SkillPackageWriteOptions.defaults());
        ByteArrayOutputStream secondZip = new ByteArrayOutputStream();
        SkillPackageWriteResult secondWrite = codec.encode(first.getSkillPackage(), secondZip,
                SkillPackageWriteOptions.defaults());

        Assert.assertArrayEquals(firstZip.toByteArray(), secondZip.toByteArray());
        Assert.assertEquals(firstWrite.getPackageHash(), secondWrite.getPackageHash());
        Assert.assertEquals(SkillPackageLayout.SINGLE_DIRECTORY, firstWrite.getLayout());
        Assert.assertEquals(SkillHashes.sha256Hex(firstZip.toByteArray()), firstWrite.getPackageHash());
        SkillPackageReadResult roundTrip = decode(codec, firstZip.toByteArray());
        Skill original = first.getSkillPackage().getSkills().get(0);
        Skill decoded = roundTrip.getSkillPackage().getSkills().get(0);
        Assert.assertEquals(original.getDocument().getFrontmatter().getValues(),
                decoded.getDocument().getFrontmatter().getValues());
        Assert.assertEquals(original.getSkillContent(), decoded.getSkillContent());
        Assert.assertEquals(original.getResources().stream().map(resource -> resource.getPath()).toList(),
                decoded.getResources().stream().map(resource -> resource.getPath()).toList());
        Assert.assertEquals(original.getResources().stream().map(resource -> resource.getContentHash()).toList(),
                decoded.getResources().stream().map(resource -> resource.getContentHash()).toList());
        Assert.assertTrue(allEntriesHaveStableTimestamp(firstZip.toByteArray()));
    }

    /**
     * Codec 对高压缩比合法文本自适应使用 STORED，保证同一限额下可回读。
     */
    @Test
    public void highCompressionTextRoundTripsWithSameLimits() {
        String content = skillMd("skill-a") + "a".repeat(20_000);
        Skill skill = SkillFactory.create(content);
        SkillPackage skillPackage = new SkillPackage(
                SkillPackageLayout.SINGLE_DIRECTORY, List.of(skill));
        SkillPackageLimits limits = SkillPackageLimits.builder()
                .maxCompressionRatio(2D)
                .build();
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        codec.encode(skillPackage, output, new SkillPackageWriteOptions(limits));
        SkillPackageReadResult decoded = codec.decode(
                new ByteArrayInputStream(output.toByteArray()), new SkillPackageReadOptions(limits));

        Assert.assertEquals(ZipEntry.STORED, zipEntryMethod(output.toByteArray(), "skill-a/SKILL.md"));
        Assert.assertEquals(content, decoded.getSkillPackage().getSkills().get(0).getSkillContent());
    }

    /**
     * 读写 options 的自定义宽松限额必须贯穿默认结构化校验器。
     */
    @Test
    public void customOptionsLimitsReachValidatorDuringDecodeAndEncode() {
        String longPath = "skill-a/references/" + "a".repeat(520) + ".md";
        SkillPackageLimits limits = SkillPackageLimits.builder()
                .maxPathLength(600)
                .build();
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        SkillPackageReadResult imported = codec.decode(
                new ByteArrayInputStream(zip(files(
                        "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                        longPath, utf8("A")
                ))), new SkillPackageReadOptions(limits));
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        codec.encode(imported.getSkillPackage(), output, new SkillPackageWriteOptions(limits));
        SkillPackageReadResult roundTrip = codec.decode(
                new ByteArrayInputStream(output.toByteArray()), new SkillPackageReadOptions(limits));

        Assert.assertEquals(longPath.substring("skill-a/".length()),
                roundTrip.getSkillPackage().getSkills().get(0).getResources().get(0).getPath());
    }

    /**
     * 写出端必须对自动生成的 Skill 根目录和 SKILL.md 路径应用调用方限额。
     */
    @Test
    public void enforceOutputLimitsOnGeneratedSkillEntryPath() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        Skill skill = SkillFactory.create(skillMd("skill-a"));
        String skillPath = "skill-a/SKILL.md";

        assertExportIssue(codec, skill, new SkillPackageWriteOptions(
                        SkillPackageLimits.builder().maxPathLength(5).build()),
                "PATH_LENGTH_LIMIT", skillPath);
        assertExportIssue(codec, skill, new SkillPackageWriteOptions(
                        SkillPackageLimits.builder().maxPathDepth(1).build()),
                "PATH_DEPTH_LIMIT", skillPath);
    }

    /**
     * Codec 内建的 YAML 与包大小安全校验不可绕过。
     */
    @Test
    public void enforceMandatoryYamlAndPackageLimits() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        Skill malformedYaml = SkillFactory.create(skillMd("skill-a"));
        malformedYaml.setSkillContent("---\nname: skill-a\ndescription: [\n---\n# Invalid\n");
        assertExportIssue(codec, malformedYaml, SkillPackageWriteOptions.defaults(),
                "INVALID_FRONTMATTER_YAML", SkillPaths.SKILL_FILE);

        Skill oversized = SkillFactory.create(skillMd("skill-a") + "x".repeat(300));
        SkillPackageLimits limits = SkillPackageLimits.builder()
                .maxTextFileBytes(128)
                .build();
        assertExportIssue(codec, oversized, new SkillPackageWriteOptions(limits),
                "TEXT_FILE_SIZE_LIMIT", SkillPaths.SKILL_FILE);
    }

    /**
     * SKILL.md 与普通文本资源中的孤立 UTF-16 surrogate 均以结构化错误拒绝。
     */
    @Test
    public void rejectMalformedUtf16SurrogateDuringExport() {
        String isolatedSurrogate = String.valueOf((char) 0xD800);
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        Skill invalidDocument = SkillFactory.create(skillMd("skill-a"));
        invalidDocument.setSkillContent(skillMd("skill-a") + isolatedSurrogate);
        assertExportIssue(codec, invalidDocument, SkillPackageWriteOptions.defaults(),
                "INVALID_UTF8", SkillPaths.SKILL_FILE);

        Skill invalidResource = SkillFactory.create(skillMd("skill-a"));
        SkillResource resource = new SkillResource();
        resource.setPath("references/invalid.md");
        resource.setKind(SkillResourceKind.REFERENCE);
        resource.setMediaType("text/markdown");
        resource.setTextContent(isolatedSurrogate);
        resource.setContentHash("0".repeat(64));
        resource.setSize(1);
        invalidResource.setResources(List.of(resource));
        assertExportIssue(codec, invalidResource, SkillPackageWriteOptions.defaults(),
                "INVALID_UTF8", "references/invalid.md");

        Skill invalidPath = SkillFactory.create(skillMd("skill-a"));
        SkillResource pathResource = new SkillResource();
        String malformedPath = "references/" + isolatedSurrogate + ".md";
        pathResource.setPath(malformedPath);
        pathResource.setKind(SkillResourceKind.REFERENCE);
        pathResource.setMediaType("text/markdown");
        pathResource.setTextContent("valid");
        pathResource.setContentHash(SkillHashes.sha256Hex(utf8("valid")));
        pathResource.setSize(utf8("valid").length);
        invalidPath.setResources(List.of(pathResource));
        assertExportIssue(codec, invalidPath, SkillPackageWriteOptions.defaults(),
                "INVALID_UTF8_PATH", malformedPath);
    }

    /**
     * 缺失资源路径在标准导出前返回结构化报告，且不能写出部分 ZIP。
     */
    @Test
    public void rejectMissingResourcePathBeforeExportWritesBytes() {
        Skill skill = SkillFactory.create(skillMd("skill-a"));
        SkillResource resource = new SkillResource();
        resource.setKind(SkillResourceKind.REFERENCE);
        resource.setMediaType("text/markdown");
        resource.setTextContent("missing path");
        resource.setContentHash(SkillHashes.sha256Hex(utf8("missing path")));
        resource.setSize(utf8("missing path").length);
        skill.setResources(List.of(resource));

        assertExportIssue(new ZipSkillPackageCodec(), skill,
                SkillPackageWriteOptions.defaults(), "RESOURCE_PATH_REQUIRED", null);
    }

    /**
     * 历史下划线名称可导入草稿，但不能标准导出。
     */
    @Test
    public void rejectLegacyNameOnStandardExport() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        SkillPackage skillPackage = decode(codec, zip(files(
                "legacy_skill/SKILL.md", utf8(skillMd("legacy_skill"))
        ))).getSkillPackage();

        try {
            codec.encode(skillPackage, new ByteArrayOutputStream(), SkillPackageWriteOptions.defaults());
            Assert.fail("Legacy name should not be exported.");
        } catch (SkillPackageException e) {
            Assert.assertNotNull(e.getReport());
            Assert.assertTrue(e.getReport().getIssues().stream().anyMatch(issue ->
                    "NON_CANONICAL_NAME".equals(issue.getCode())
                            && issue.getSeverity()
                            == com.easyagents.skill.validation.SkillValidationSeverity.ERROR));
        }
    }

    /**
     * 目录名与 frontmatter 名称不一致时返回结构化错误。
     */
    @Test
    public void rejectRootNameMismatch() {
        try {
            decode(new ZipSkillPackageCodec(), zip(files(
                    "folder-name/SKILL.md", utf8(skillMd("different-name"))
            )));
            Assert.fail("Mismatched root should fail.");
        } catch (SkillPackageException e) {
            Assert.assertNotNull(e.getReport());
            Assert.assertTrue(e.getReport().getIssues().stream().anyMatch(issue ->
                    "ROOT_NAME_MISMATCH".equals(issue.getCode())));
        }
    }

    /**
     * 导出先聚合校验错误，不因缺失 SKILL.md 内容触发空指针。
     */
    @Test
    public void aggregateInvalidSkillBeforePreparingOutput() {
        Skill skill = SkillFactory.create(skillMd("skill-a"));
        skill.setSkillContent(null);
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        try {
            new ZipSkillPackageCodec().encode(
                    new SkillPackage(SkillPackageLayout.SINGLE_DIRECTORY, List.of(skill)), output,
                    SkillPackageWriteOptions.defaults());
            Assert.fail("Invalid Skill should fail.");
        } catch (SkillPackageException e) {
            Assert.assertNotNull(e.getReport());
            Assert.assertTrue(e.getReport().getIssues().stream().anyMatch(issue ->
                    "SKILL_CONTENT_REQUIRED".equals(issue.getCode())));
            Assert.assertEquals(0, output.size());
        }
    }

    /**
     * 普通文本资源导出也执行单文件限制。
     */
    @Test
    public void enforceTextLimitDuringExport() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        SkillPackage skillPackage = decode(codec, zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a")),
                "skill-a/references/a.md", utf8("x".repeat(300))
        ))).getSkillPackage();
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        try {
            codec.encode(skillPackage, output, new SkillPackageWriteOptions(
                    SkillPackageLimits.builder().maxTextFileBytes(128).build()));
            Assert.fail("Oversized text export should fail.");
        } catch (SkillPackageException e) {
            Assert.assertTrue(e.getReport().getIssues().stream().anyMatch(issue ->
                    "TEXT_FILE_SIZE_LIMIT".equals(issue.getCode())));
            Assert.assertEquals(0, output.size());
        }
    }

    /**
     * 输出压缩包超限返回明确错误码。
     */
    @Test
    public void reportCompressedOutputLimit() {
        ZipSkillPackageCodec codec = new ZipSkillPackageCodec();
        SkillPackage skillPackage = decode(codec, zip(files(
                "skill-a/SKILL.md", utf8(skillMd("skill-a"))
        ))).getSkillPackage();

        try {
            codec.encode(skillPackage, new ByteArrayOutputStream(), new SkillPackageWriteOptions(
                    SkillPackageLimits.builder().maxCompressedPackageBytes(64).build()));
            Assert.fail("Compressed output limit should fail.");
        } catch (SkillPackageException e) {
            Assert.assertEquals("COMPRESSED_SIZE_LIMIT", e.getCode());
        }
    }

    /**
     * 二进制存储内容在写 ZIP 前完成 size/hash 预检。
     */
    @Test
    public void preflightBinaryContentBeforeWritingArchive() {
        byte[] expected = utf8("abc");
        String hash = SkillHashes.sha256Hex(expected);
        SkillResource resource = new SkillResource();
        resource.setPath("assets/data.bin");
        resource.setKind(SkillResourceKind.ASSET);
        resource.setMediaType("application/octet-stream");
        resource.setContentRef("sha256:" + hash);
        resource.setContentHash(hash);
        resource.setSize(expected.length);
        Skill skill = SkillFactory.create(skillMd("skill-a"));
        skill.setResources(List.of(resource));
        ByteArrayOutputStream output = new ByteArrayOutputStream();

        try {
            new ZipSkillPackageCodec(new CorruptReadContentStore()).encode(
                    new SkillPackage(SkillPackageLayout.SINGLE_DIRECTORY, List.of(skill)), output,
                    SkillPackageWriteOptions.defaults());
            Assert.fail("Corrupt stored content should fail preflight.");
        } catch (SkillPackageException e) {
            Assert.assertEquals("RESOURCE_HASH_MISMATCH", e.getCode());
            Assert.assertEquals(0, output.size());
        }
    }

    private static SkillPackageReadResult decode(ZipSkillPackageCodec codec, byte[] bytes) {
        return codec.decode(new ByteArrayInputStream(bytes), SkillPackageReadOptions.defaults());
    }

    private static void assertExportIssue(ZipSkillPackageCodec codec, Skill skill,
                                          SkillPackageWriteOptions options,
                                          String code, String path) {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try {
            codec.encode(new SkillPackage(SkillPackageLayout.SINGLE_DIRECTORY, List.of(skill)),
                    output, options);
            Assert.fail("Skill export should fail with " + code + ".");
        } catch (SkillPackageException e) {
            Assert.assertNotNull(e.getReport());
            Assert.assertTrue(e.getReport().getIssues().stream().anyMatch(issue ->
                    code.equals(issue.getCode()) && Objects.equals(path, issue.getPath())));
            Assert.assertEquals(0, output.size());
        }
    }

    private static void assertPackageCode(String code, byte[] bytes, SkillPackageReadOptions options) {
        try {
            new ZipSkillPackageCodec().decode(new ByteArrayInputStream(bytes), options);
            Assert.fail("Package should fail with " + code + ".");
        } catch (SkillPackageException e) {
            Assert.assertEquals(code, e.getCode());
        }
    }

    private static Map<String, byte[]> files(Object... keyValues) {
        Map<String, byte[]> files = new LinkedHashMap<>();
        for (int index = 0; index < keyValues.length; index += 2) {
            files.put((String) keyValues[index], (byte[]) keyValues[index + 1]);
        }
        return files;
    }

    private static byte[] zip(Map<String, byte[]> files, String... directories) {
        try {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            try (ZipOutputStream zipOutput = new ZipOutputStream(bytes, StandardCharsets.UTF_8)) {
                for (String directory : directories) {
                    zipOutput.putNextEntry(new ZipEntry(directory));
                    zipOutput.closeEntry();
                }
                for (Map.Entry<String, byte[]> file : files.entrySet()) {
                    zipOutput.putNextEntry(new ZipEntry(file.getKey()));
                    zipOutput.write(file.getValue());
                    zipOutput.closeEntry();
                }
            }
            return bytes.toByteArray();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 构造仅用于中央目录预扫描测试的 ZIP；本地头无有效 entry，旧 ZipFile 路径无法安全读取。
     *
     * @param actualEntries 中央目录实际条目数
     * @param declaredEntries EOCD 声明条目数
     * @return 合成 ZIP 字节
     */
    private static byte[] syntheticCentralDirectoryZip(int actualEntries, int declaredEntries) {
        int localHeaderSize = 30;
        int nameSize = 2;
        int centralEntrySize = 46 + nameSize;
        int centralSize = actualEntries * centralEntrySize;
        ByteBuffer buffer = ByteBuffer.allocate(localHeaderSize + centralSize + 22)
                .order(ByteOrder.LITTLE_ENDIAN);
        buffer.putInt(0x04034B50);
        buffer.position(localHeaderSize);
        writeSyntheticCentralDirectoryEntries(buffer, actualEntries, nameSize);
        buffer.putInt(0x06054B50);
        buffer.putShort((short) 0);
        buffer.putShort((short) 0);
        buffer.putShort((short) declaredEntries);
        buffer.putShort((short) declaredEntries);
        buffer.putInt(centralSize);
        buffer.putInt(localHeaderSize);
        buffer.putShort((short) 0);
        return buffer.array();
    }

    /**
     * 构造带 ZIP64 EOCD 的中央目录预扫描样本。
     *
     * @param actualEntries 中央目录实际条目数
     * @param declaredEntries ZIP64 EOCD 声明条目数
     * @return 合成 ZIP64 字节
     */
    private static byte[] syntheticZip64CentralDirectoryZip(
            int actualEntries, int declaredEntries) {
        int localHeaderSize = 30;
        int nameSize = 2;
        int centralEntrySize = 46 + nameSize;
        int centralSize = actualEntries * centralEntrySize;
        int zip64EndRecordSize = 56;
        int zip64LocatorSize = 20;
        int endRecordSize = 22;
        int zip64EndRecordOffset = localHeaderSize + centralSize;
        ByteBuffer buffer = ByteBuffer.allocate(localHeaderSize + centralSize
                        + zip64EndRecordSize + zip64LocatorSize + endRecordSize)
                .order(ByteOrder.LITTLE_ENDIAN);
        buffer.putInt(0x04034B50);
        buffer.position(localHeaderSize);
        writeSyntheticCentralDirectoryEntries(buffer, actualEntries, nameSize);

        buffer.putInt(0x06064B50);
        buffer.putLong(44L);
        buffer.putShort((short) 45);
        buffer.putShort((short) 45);
        buffer.putInt(0);
        buffer.putInt(0);
        buffer.putLong(declaredEntries);
        buffer.putLong(declaredEntries);
        buffer.putLong(centralSize);
        buffer.putLong(localHeaderSize);

        buffer.putInt(0x07064B50);
        buffer.putInt(0);
        buffer.putLong(zip64EndRecordOffset);
        buffer.putInt(1);

        buffer.putInt(0x06054B50);
        buffer.putShort((short) 0);
        buffer.putShort((short) 0);
        buffer.putShort((short) 0xFFFF);
        buffer.putShort((short) 0xFFFF);
        buffer.putInt(0xFFFFFFFF);
        buffer.putInt(0xFFFFFFFF);
        buffer.putShort((short) 0);
        return buffer.array();
    }

    /**
     * 写入只包含最小必要字段的中央目录条目。
     *
     * @param buffer 目标小端缓冲区
     * @param entryCount 条目数
     * @param nameSize 文件名长度
     */
    private static void writeSyntheticCentralDirectoryEntries(
            ByteBuffer buffer, int entryCount, int nameSize) {
        for (int index = 0; index < entryCount; index++) {
            buffer.putInt(0x02014B50);
            buffer.putShort((short) 20);
            buffer.putShort((short) 20);
            buffer.position(buffer.position() + 20);
            buffer.putShort((short) nameSize);
            buffer.putShort((short) 0);
            buffer.putShort((short) 0);
            buffer.position(buffer.position() + 12);
            buffer.put((byte) 'e');
            buffer.put((byte) ('0' + index));
        }
    }

    /**
     * 删除中央目录最后一个字节并同步 EOCD 的目录大小，模拟结构性截断。
     *
     * @param source 无注释的传统 ZIP 样本
     * @return 中央目录被截断的 ZIP 字节
     */
    private static byte[] truncateLastCentralDirectoryByte(byte[] source) {
        int endRecordSize = 22;
        int endRecordOffset = source.length - endRecordSize;
        int removedOffset = endRecordOffset - 1;
        ByteBuffer sourceBuffer = ByteBuffer.wrap(source).order(ByteOrder.LITTLE_ENDIAN);
        int centralSize = sourceBuffer.getInt(endRecordOffset + 12);
        byte[] truncated = new byte[source.length - 1];
        System.arraycopy(source, 0, truncated, 0, removedOffset);
        System.arraycopy(source, removedOffset + 1, truncated, removedOffset,
                source.length - removedOffset - 1);
        ByteBuffer.wrap(truncated).order(ByteOrder.LITTLE_ENDIAN)
                .putInt(endRecordOffset - 1 + 12, centralSize - 1);
        return truncated;
    }

    private static byte[] symlinkZip(String path) {
        try {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            try (ZipArchiveOutputStream output = new ZipArchiveOutputStream(bytes)) {
                ZipArchiveEntry skillEntry = new ZipArchiveEntry("skill-a/SKILL.md");
                skillEntry.setUnixMode(UnixStat.FILE_FLAG | 0644);
                output.putArchiveEntry(skillEntry);
                output.write(utf8(skillMd("skill-a")));
                output.closeArchiveEntry();

                ZipArchiveEntry symlink = new ZipArchiveEntry(path);
                symlink.setUnixMode(UnixStat.LINK_FLAG | 0777);
                output.putArchiveEntry(symlink);
                output.write(utf8("target"));
                output.closeArchiveEntry();
                output.finish();
            }
            return bytes.toByteArray();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static byte[] invalidUtf8EntryNameZip() {
        try {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            try (ZipArchiveOutputStream output = new ZipArchiveOutputStream(bytes)) {
                output.setEncoding(StandardCharsets.ISO_8859_1.name());
                output.setUseLanguageEncodingFlag(false);
                output.setCreateUnicodeExtraFields(ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER);

                ZipArchiveEntry skillEntry = new ZipArchiveEntry("skill-a/SKILL.md");
                output.putArchiveEntry(skillEntry);
                output.write(utf8(skillMd("skill-a")));
                output.closeArchiveEntry();

                ZipArchiveEntry invalidName = new ZipArchiveEntry("skill-a/references/\u00ff.md");
                output.putArchiveEntry(invalidName);
                output.write(utf8("invalid name"));
                output.closeArchiveEntry();
                output.finish();
            }
            return bytes.toByteArray();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static byte[] tamperFirstCentralDirectoryCrc(byte[] source) {
        byte[] bytes = Arrays.copyOf(source, source.length);
        for (int index = 0; index <= bytes.length - 20; index++) {
            if (bytes[index] == 0x50 && bytes[index + 1] == 0x4B
                    && bytes[index + 2] == 0x01 && bytes[index + 3] == 0x02) {
                bytes[index + 16] ^= 0x01;
                return bytes;
            }
        }
        throw new IllegalStateException("ZIP central directory was not found.");
    }

    private static boolean allEntriesHaveStableTimestamp(byte[] zip) {
        List<Long> timestamps = new ArrayList<>();
        try (ZipInputStream input = new ZipInputStream(new ByteArrayInputStream(zip), StandardCharsets.UTF_8)) {
            ZipEntry entry;
            while ((entry = input.getNextEntry()) != null) {
                timestamps.add(entry.getTime());
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        return !timestamps.isEmpty() && timestamps.stream().distinct().count() == 1;
    }

    private static int zipEntryMethod(byte[] zip, String path) {
        try (ZipInputStream input = new ZipInputStream(
                new ByteArrayInputStream(zip), StandardCharsets.UTF_8)) {
            ZipEntry entry;
            while ((entry = input.getNextEntry()) != null) {
                if (path.equals(entry.getName())) {
                    return entry.getMethod();
                }
            }
            throw new IllegalStateException("ZIP entry was not found: " + path);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static byte[] utf8(String value) {
        return value.getBytes(StandardCharsets.UTF_8);
    }

    private static String skillMd(String name) {
        return "---\nname: " + name + "\ndescription: Use this Skill for package tests\n---\n# " + name + "\n";
    }

    private static String invalidDescriptionSkillMd(String name) {
        return "---\nname: " + name + "\ndescription: ''\n---\n# " + name + "\n";
    }

    private static final class CountingContentStore extends InMemorySkillContentStore {

        private int putCount;

        @Override
        public String put(byte[] bytes) {
            putCount++;
            return SkillHashes.sha256Ref(bytes);
        }

        @Override
        public InputStream open(String contentRef) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean exists(String contentRef) {
            return false;
        }
    }

    private static final class TrackingContentStore implements SkillContentStore {

        private final InMemorySkillContentStore delegate = new InMemorySkillContentStore();
        private final int failOnCommit;
        private int stageCalls;
        private int stagedCount;
        private int committedCount;
        private int commitAttempts;
        private int rollbackCalls;

        private TrackingContentStore(int failOnCommit) {
            this.failOnCommit = failOnCommit;
        }

        @Override
        public String put(byte[] bytes) {
            return delegate.put(bytes);
        }

        @Override
        public SkillContentStage stage(InputStream inputStream, long maxBytes) {
            stageCalls++;
            stagedCount++;
            return delegate.stage(inputStream, maxBytes);
        }

        @Override
        public String commit(SkillContentStage stage) {
            commitAttempts++;
            if (failOnCommit > 0 && commitAttempts == failOnCommit) {
                throw new IllegalStateException("simulated commit failure");
            }
            String contentRef = delegate.commit(stage);
            stagedCount--;
            committedCount++;
            return contentRef;
        }

        @Override
        public void rollback(SkillContentStage stage) {
            rollbackCalls++;
            delegate.rollback(stage);
            stagedCount--;
        }

        @Override
        public void retain(String contentRef) {
            delegate.retain(contentRef);
            committedCount++;
        }

        @Override
        public void release(String contentRef) {
            delegate.release(contentRef);
            committedCount--;
        }

        @Override
        public InputStream open(String contentRef) {
            return delegate.open(contentRef);
        }

        @Override
        public boolean exists(String contentRef) {
            return delegate.exists(contentRef);
        }
    }

    private static final class CorruptReadContentStore extends InMemorySkillContentStore {

        @Override
        public String put(byte[] bytes) {
            return SkillHashes.sha256Ref(bytes);
        }

        @Override
        public InputStream open(String contentRef) {
            return new ByteArrayInputStream(utf8("xyz"));
        }

        @Override
        public boolean exists(String contentRef) {
            return true;
        }
    }

    private static final class FlakyRollbackContentStore extends InMemorySkillContentStore {

        private int rollbackAttempts;
        private boolean rollbackCompleted;

        @Override
        public void rollback(SkillContentStage stage) {
            rollbackAttempts++;
            if (rollbackAttempts == 1) {
                throw new IllegalStateException("simulated transient rollback failure");
            }
            super.rollback(stage);
            rollbackCompleted = true;
        }
    }
}
