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

import com.easyagents.agent.runtime.AgentRuntimeException;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * 测试工作区路径边界。
 */
public class WorkspacePathGuardTest {

    @Test
    public void shouldRejectAbsoluteTraversalAndTildePaths() throws IOException {
        WorkspacePathGuard guard = guard();

        assertRejectedWithout(() -> guard.resolveForWrite("/etc/passwd"), "/etc/passwd");
        assertRejected(() -> guard.resolveForWrite("../escape.txt"));
        assertRejected(() -> guard.resolveForWrite("~/secret.txt"));
        assertRejected(() -> guard.resolveForWrite("C:\\Windows\\system.ini"));
    }

    @Test
    public void shouldRejectSymbolicLinkEscape() throws IOException {
        Path root = Files.createTempDirectory("workspace-path-");
        Path outside = Files.createTempDirectory("workspace-outside-");
        try {
            Files.createSymbolicLink(root.resolve("link"), outside);
        } catch (UnsupportedOperationException error) {
            Assume.assumeNoException(error);
        }
        WorkspacePathGuard guard = new WorkspacePathGuard(root.toAbsolutePath());

        assertRejected(() -> guard.resolveForWrite("link/secret.txt"));
    }

    @Test
    public void shouldRejectHardLinkedFileOnUnix() throws IOException {
        Assume.assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("unix"));
        Path root = Files.createTempDirectory("workspace-hardlink-");
        Files.writeString(root.resolve("source.txt"), "secret");
        Files.createLink(root.resolve("alias.txt"), root.resolve("source.txt"));
        WorkspacePathGuard guard = new WorkspacePathGuard(root.toAbsolutePath());

        assertRejected(() -> guard.resolveExistingFile("alias.txt"));
    }

    @Test
    public void shouldRejectSymlinkReplacementBetweenResolveAndAtomicCommit() throws IOException {
        Path root = Files.createTempDirectory("workspace-replacement-");
        Path outside = Files.createTempFile("workspace-replacement-outside-", ".txt");
        Files.writeString(outside, "outside");
        WorkspacePathGuard guard = new WorkspacePathGuard(root.toAbsolutePath());
        Path target = guard.resolveForWrite("target.txt");
        try {
            Files.createSymbolicLink(target, outside);
        } catch (UnsupportedOperationException error) {
            Assume.assumeNoException(error);
        }

        assertRejected(() -> WorkspaceTextFiles.atomicWrite(
                guard, target, "changed".getBytes(StandardCharsets.UTF_8)));

        Assert.assertEquals("outside", Files.readString(outside));
    }

    @Test
    public void shouldRejectHardLinkReplacementBetweenResolveAndAtomicCommitOnUnix() throws IOException {
        Assume.assumeTrue(FileSystems.getDefault().supportedFileAttributeViews().contains("unix"));
        Path root = Files.createTempDirectory("workspace-hardlink-replacement-");
        Path outside = Files.createTempFile("workspace-hardlink-outside-", ".txt");
        Files.writeString(outside, "outside");
        WorkspacePathGuard guard = new WorkspacePathGuard(root.toAbsolutePath());
        Path target = guard.resolveForWrite("target.txt");
        Files.createLink(target, outside);

        assertRejected(() -> WorkspaceTextFiles.atomicWrite(
                guard, target, "changed".getBytes(StandardCharsets.UTF_8)));

        Assert.assertEquals("outside", Files.readString(outside));
    }

    @Test
    public void shouldDisplayOnlyRelativePath() throws IOException {
        Path root = Files.createTempDirectory("workspace-display-");
        WorkspacePathGuard guard = new WorkspacePathGuard(root.toAbsolutePath());
        Path target = guard.resolveForWrite("output/report.txt");

        Assert.assertEquals(".", guard.display(root.toRealPath()));
        Assert.assertEquals("output/report.txt", guard.display(target));
        Assert.assertFalse(guard.display(target).contains(root.toString()));
    }

    private WorkspacePathGuard guard() throws IOException {
        return new WorkspacePathGuard(Files.createTempDirectory("workspace-path-").toAbsolutePath());
    }

    private void assertRejected(Runnable action) {
        try {
            action.run();
            Assert.fail("Expected AgentRuntimeException");
        } catch (AgentRuntimeException expected) {
            Assert.assertFalse(expected.getMessage().contains("/Users/"));
        }
    }

    private void assertRejectedWithout(Runnable action, String forbiddenText) {
        try {
            action.run();
            Assert.fail("Expected AgentRuntimeException");
        } catch (AgentRuntimeException expected) {
            Assert.assertFalse(expected.getMessage().contains(forbiddenText));
        }
    }
}
