package com.agent.management.engine;

import com.agent.management.config.SkillProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * {@link SkillFileSyncer} 单元测试。
 *
 * <p>覆盖镜像同步的跳过规则：
 * <ul>
 *   <li>技能根目录 SKILL.md（合法主入口）正常复制；</li>
 *   <li>{@code _versions/} 子目录整树跳过；</li>
 *   <li>非根子目录直接含 SKILL.md 时整树跳过（任意深度，常见于 AI 生成残留）；</li>
 *   <li>目标侧残留清除；</li>
 *   <li>普通子目录与文件正常复制。</li>
 * </ul>
 */
class SkillFileSyncerTest {

    @TempDir
    Path tempDir;

    private Path sourceRoot;
    private Path targetRoot;
    private SkillFileSyncer syncer;

    @BeforeEach
    void setUp() throws IOException {
        sourceRoot = tempDir.resolve("uploads-skills");
        targetRoot = tempDir.resolve("hermes-home-skills");
        Files.createDirectories(sourceRoot);

        SkillProperties props = new SkillProperties();
        props.setBasePath(sourceRoot.toString());
        syncer = new SkillFileSyncer(props);
    }

    /** 创建技能根目录 + SKILL.md */
    private void createSkill(String folderName, String skillMdContent) throws IOException {
        Path skillDir = sourceRoot.resolve(folderName);
        Files.createDirectories(skillDir);
        Files.writeString(skillDir.resolve("SKILL.md"), skillMdContent);
    }

    /** 在技能内创建子目录（可选拓展子文件） */
    private Path mkSubDir(String skillFolder, String subRel) throws IOException {
        Path p = sourceRoot.resolve(skillFolder).resolve(subRel);
        Files.createDirectories(p);
        return p;
    }

    private void write(Path dir, String fileName, String content) throws IOException {
        Files.writeString(dir.resolve(fileName), content);
    }

    // ==================== 测试用例 ====================

    @Test
    void copiesRootSkillMdAndRegularFiles() throws IOException {
        createSkill("foo", "name: foo\n");
        write(sourceRoot.resolve("foo"), "notes.md", "hello");

        int copied = syncer.syncTo(targetRoot);

        assertThat(copied).isEqualTo(1);
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
        assertThat(targetRoot.resolve("foo/notes.md")).exists();
    }

    @Test
    void skipsVersionsSubtree() throws IOException {
        createSkill("foo", "name: foo\n");
        Path versions = mkSubDir("foo", "_versions/v1");
        write(versions, "SKILL.md", "name: foo-old\n");

        syncer.syncTo(targetRoot);

        assertThat(targetRoot.resolve("foo/_versions")).doesNotExist();
    }

    @Test
    void skipsChildDirContainingSkillMd_generatedSkills() throws IOException {
        createSkill("foo", "name: foo\n");
        // 模拟 AI 生成的残留：foo/generated_skills/SKILL.md
        Path genDir = mkSubDir("foo", "generated_skills");
        write(genDir, "SKILL.md", "name: generated\n");
        write(genDir, "extra.txt", "keep me?");

        syncer.syncTo(targetRoot);

        // generated_skills/ 整树不复制
        assertThat(targetRoot.resolve("foo/generated_skills")).doesNotExist();
        // 但技能自身的 SKILL.md 仍正常
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
    }

    @Test
    void skipsChildDirContainingSkillMd_tmpSmokeGenerated() throws IOException {
        createSkill("foo", "name: foo\n");
        Path genDir = mkSubDir("foo", "tmp_smoke_generated");
        write(genDir, "SKILL.md", "name: smoke\n");

        syncer.syncTo(targetRoot);

        assertThat(targetRoot.resolve("foo/tmp_smoke_generated")).doesNotExist();
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
    }

    @Test
    void skipsNestedDirContainingSkillMd_atDeeperLevel() throws IOException {
        // 结构: foo/subdir/nested/SKILL.md，subdir 自身不含 SKILL.md
        // 期望：跳过 subdir/nested/（nested 整树），但 subdir/ 自身被创建为目录
        createSkill("foo", "name: foo\n");
        Path nested = mkSubDir("foo", "subdir/nested");
        write(nested, "SKILL.md", "name: nested\n");
        write(nested, "aux.txt", "aux");

        syncer.syncTo(targetRoot);

        // nested/ 整树被跳过
        assertThat(targetRoot.resolve("foo/subdir/nested")).doesNotExist();
        // subdir/ 本身仍创建（因为 preVisitDirectory 在 subdir 处返回 CONTINUE）
        assertThat(targetRoot.resolve("foo/subdir")).exists().isDirectory();
        // 技能根 SKILL.md 仍正常
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
    }

    @Test
    void regularSubdirWithoutSkillMdIsCopied() throws IOException {
        createSkill("foo", "name: foo\n");
        Path sub = mkSubDir("foo", "docs");
        write(sub, "guide.md", "guide");
        write(sub, "examples.py", "print('hi')");

        syncer.syncTo(targetRoot);

        assertThat(targetRoot.resolve("foo/docs/guide.md")).exists();
        assertThat(targetRoot.resolve("foo/docs/examples.py")).exists();
    }

    @Test
    void cleansOrphanedTargetDirs() throws IOException {
        // 目标侧已存在一个源侧没有的目录，同步后应被清除
        Path orphan = targetRoot.resolve("deleted-skill");
        Files.createDirectories(orphan);
        Files.writeString(orphan.resolve("SKILL.md"), "name: deleted\n");

        createSkill("foo", "name: foo\n");

        syncer.syncTo(targetRoot);

        assertThat(orphan).doesNotExist();
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
    }

    @Test
    void mirrorsSourceFileDeletionWithinSkill() throws IOException {
        // 首次同步：foo 含 SKILL.md + obsolete.md
        createSkill("foo", "name: foo\n");
        write(sourceRoot.resolve("foo"), "obsolete.md", "will be deleted");

        syncer.syncTo(targetRoot);
        assertThat(targetRoot.resolve("foo/obsolete.md")).exists();

        // 删除源侧 obsolete.md，再次同步
        Files.delete(sourceRoot.resolve("foo").resolve("obsolete.md"));
        syncer.syncTo(targetRoot);

        // 目标侧 obsolete.md 应被镜像清除（先删后拷语义）
        assertThat(targetRoot.resolve("foo/obsolete.md")).doesNotExist();
        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
    }

    @Test
    void multipleSkillsAllChecked() throws IOException {
        createSkill("foo", "name: foo\n");
        createSkill("bar", "name: bar\n");
        // foo 下有残留
        Path genFoo = mkSubDir("foo", "generated_skills");
        write(genFoo, "SKILL.md", "name: gen\n");
        // bar 下正常
        Path docsBar = mkSubDir("bar", "docs");
        write(docsBar, "x.md", "x");

        syncer.syncTo(targetRoot);

        assertThat(targetRoot.resolve("foo/SKILL.md")).exists();
        assertThat(targetRoot.resolve("foo/generated_skills")).doesNotExist();
        assertThat(targetRoot.resolve("bar/SKILL.md")).exists();
        assertThat(targetRoot.resolve("bar/docs/x.md")).exists();
    }

    @Test
    void returnsZeroWhenSourceMissing() throws IOException {
        // sourceRoot 不存在（删掉）
        sourceRoot.toFile().delete();

        int copied = syncer.syncTo(targetRoot);

        assertThat(copied).isZero();
    }
}
