package com.easyagents.agent.runtime.tool.operate;

import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.tool.ToolCallParam;
import org.junit.Assert;
import org.junit.Test;

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

/**
 * 测试有界补丁工具。
 */
public class ApplyPatchToolTest {

    @Test
    public void shouldApplyMultiFileAddUpdateDeletePatch() throws IOException {
        Fixture fixture = fixture();
        Files.writeString(fixture.root().resolve("existing.md"), "old\n");
        Files.writeString(fixture.root().resolve("remove.md"), "remove\n");
        String patch = """
                *** Begin Patch
                *** Update File: existing.md
                @@
                -old
                +new
                *** Add File: created.md
                +created
                *** Delete File: remove.md
                *** End Patch""";

        ToolResultBlock result = call(fixture.tool(), patch);

        Assert.assertTrue(text(result).contains("3 file(s)"));
        Assert.assertEquals("new\n", Files.readString(fixture.root().resolve("existing.md")));
        Assert.assertEquals("created\n", Files.readString(fixture.root().resolve("created.md")));
        Assert.assertFalse(Files.exists(fixture.root().resolve("remove.md")));
    }

    @Test
    public void shouldApplyStandardUnifiedDiff() throws IOException {
        Fixture fixture = fixture();
        Files.writeString(fixture.root().resolve("standard.txt"), "before\n");
        String patch = """
                --- a/standard.txt
                +++ b/standard.txt
                @@ -1 +1 @@
                -before
                +after
                """;

        ToolResultBlock result = call(fixture.tool(), patch);

        Assert.assertTrue(text(result).contains("successfully"));
        Assert.assertEquals("after\n", Files.readString(fixture.root().resolve("standard.txt")));
    }

    @Test
    public void shouldRejectAmbiguousContextWithoutChangingFile() throws IOException {
        Fixture fixture = fixture();
        Files.writeString(fixture.root().resolve("ambiguous.txt"), "same\nother\nsame\n");
        String patch = """
                *** Begin Patch
                *** Update File: ambiguous.txt
                @@
                -same
                +changed
                *** End Patch""";

        ToolResultBlock result = call(fixture.tool(), patch);

        Assert.assertTrue(text(result).contains("PATCH_CONFLICT"));
        Assert.assertEquals("same\nother\nsame\n", Files.readString(fixture.root().resolve("ambiguous.txt")));
    }

    @Test
    public void shouldRejectPathEscapeBeforeChangingAnyFile() throws IOException {
        Fixture fixture = fixture();
        Files.writeString(fixture.root().resolve("safe.txt"), "safe\n");
        String patch = """
                *** Begin Patch
                *** Update File: safe.txt
                @@
                -safe
                +changed
                *** Add File: ../escape.txt
                +escape
                *** End Patch""";

        ToolResultBlock result = call(fixture.tool(), patch);

        Assert.assertTrue(text(result).contains("WORKSPACE_PATH_INVALID"));
        Assert.assertEquals("safe\n", Files.readString(fixture.root().resolve("safe.txt")));
    }

    @Test
    public void shouldRejectDeleteDiffThatDoesNotMatchCompleteFile() throws IOException {
        Fixture fixture = fixture();
        Files.writeString(fixture.root().resolve("delete.txt"), "expected\nextra\n");
        String patch = """
                --- a/delete.txt
                +++ /dev/null
                @@ -1 +0,0 @@
                -expected
                """;

        ToolResultBlock result = call(fixture.tool(), patch);

        Assert.assertTrue(text(result).contains("PATCH_CONFLICT"));
        Assert.assertEquals("expected\nextra\n", Files.readString(fixture.root().resolve("delete.txt")));
    }

    private Fixture fixture() throws IOException {
        Path root = Files.createTempDirectory("apply-patch-tool-").toAbsolutePath();
        WorkspacePathGuard pathGuard = new WorkspacePathGuard(root);
        WorkspaceQuotaGuard quotaGuard = new WorkspaceQuotaGuard(
                pathGuard,
                new WorkspaceQuotaLimits(1024 * 1024, 1024 * 1024, 100, 1024 * 1024),
                WorkspaceQuotaHook.noop());
        return new Fixture(root, new ApplyPatchTool(pathGuard, quotaGuard, 1024 * 1024, 10, 1024 * 1024));
    }

    private ToolResultBlock call(ApplyPatchTool tool, String patch) {
        return tool.callAsync(ToolCallParam.builder().input(Map.of("patch", patch)).build()).block();
    }

    private String text(ToolResultBlock result) {
        return ((TextBlock) result.getOutput().get(0)).getText();
    }

    /**
     * Patch 测试夹具。
     *
     * @param root 工作区根
     * @param tool Patch 工具
     */
    private record Fixture(Path root, ApplyPatchTool tool) {
    }
}
