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

import com.easyagents.agent.runtime.AgentRuntimeException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.regex.Pattern;

/**
 * 工作区路径安全边界。
 *
 * <p>调用方只能提交工作区相对路径。该类拒绝路径穿越、宿主绝对路径、符号链接、设备文件和
 * 其他非普通文件目标，并只向上层返回相对展示路径。
 */
public final class WorkspacePathGuard {

    private static final Pattern WINDOWS_ABSOLUTE_PATH = Pattern.compile("^[A-Za-z]:[\\\\/].*");
    private final Path workspaceRoot;

    /**
     * 创建路径保护器并确保工作区根目录存在。
     *
     * @param workspaceRoot 受信任的工作区绝对目录
     * @throws AgentRuntimeException 根目录无效或无法创建时抛出
     */
    public WorkspacePathGuard(Path workspaceRoot) {
        if (workspaceRoot == null || !workspaceRoot.isAbsolute()) {
            throw new WorkspaceToolException("WORKSPACE_CONFIG_INVALID",
                    "Workspace root must be an absolute path.", false);
        }
        try {
            Files.createDirectories(workspaceRoot.normalize());
            this.workspaceRoot = workspaceRoot.normalize().toRealPath();
            if (!Files.isDirectory(this.workspaceRoot, LinkOption.NOFOLLOW_LINKS)) {
                throw new WorkspaceToolException("WORKSPACE_CONFIG_INVALID",
                        "Workspace root is not a directory.", false);
            }
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_CONFIG_INVALID",
                    "Workspace root cannot be initialized.", false, error);
        }
    }

    /**
     * 获取仅供受信任 Runtime 内部使用的真实工作区根目录。
     *
     * @return 真实工作区根目录
     */
    Path root() {
        return workspaceRoot;
    }

    /**
     * 解析已存在的普通文件。
     *
     * @param relativePath 模型提交的工作区相对路径
     * @return 受控普通文件路径
     * @throws AgentRuntimeException 路径不安全、目标不存在或不是普通文件时抛出
     */
    public Path resolveExistingFile(String relativePath) {
        Path target = resolve(relativePath, false);
        if (!Files.exists(target, LinkOption.NOFOLLOW_LINKS)) {
            throw new WorkspaceToolException("FILE_NOT_FOUND", "Workspace file does not exist.", false);
        }
        if (!Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            throw new WorkspaceToolException("FILE_TYPE_INVALID",
                    "Workspace target is not a regular file.", false);
        }
        rejectHardLink(target);
        return target;
    }

    /**
     * 解析已存在的普通文件或目录，用于受控命令参数预检。
     *
     * @param relativePath 模型提交的工作区相对路径
     * @return 受控现有条目
     * @throws AgentRuntimeException 目标不安全、不存在或属于特殊文件时抛出
     */
    public Path resolveExistingEntry(String relativePath) {
        Path target = resolve(relativePath, true);
        if (Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            rejectHardLink(target);
            return target;
        }
        if (Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)) {
            return target;
        }
        throw new WorkspaceToolException("FILE_TYPE_INVALID",
                "Workspace target is not a regular file or directory.", false);
    }

    /**
     * 解析命令声明的工作区路径，允许尚不存在的创建目标和已存在的普通文件或目录。
     *
     * @param relativePath 命令路径参数
     * @return 受控工作区路径
     * @throws AgentRuntimeException 路径越界、包含链接或属于特殊文件时抛出
     */
    Path resolveCommandPath(String relativePath) {
        Path target = resolve(relativePath, true);
        if (!Files.exists(target, LinkOption.NOFOLLOW_LINKS)) {
            return target;
        }
        if (Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            rejectHardLink(target);
            return target;
        }
        if (Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)) {
            return target;
        }
        throw new WorkspaceToolException("FILE_TYPE_INVALID",
                "Shell target is not a regular file or directory.", false);
    }

    /**
     * 解析已存在的目录。
     *
     * @param relativePath 模型提交的工作区相对路径，`.` 表示工作区根
     * @return 受控目录路径
     * @throws AgentRuntimeException 路径不安全、目标不存在或不是目录时抛出
     */
    public Path resolveExistingDirectory(String relativePath) {
        Path target = resolve(relativePath, true);
        if (!Files.exists(target, LinkOption.NOFOLLOW_LINKS)) {
            throw new WorkspaceToolException("FILE_NOT_FOUND", "Workspace directory does not exist.", false);
        }
        if (!Files.isDirectory(target, LinkOption.NOFOLLOW_LINKS)) {
            throw new WorkspaceToolException("FILE_TYPE_INVALID",
                    "Workspace target is not a directory.", false);
        }
        return target;
    }

    /**
     * 解析可写入的文件路径，允许目标和父目录尚未创建。
     *
     * @param relativePath 模型提交的工作区相对路径
     * @return 受控文件路径
     * @throws AgentRuntimeException 路径不安全或现有目标不是普通文件时抛出
     */
    public Path resolveForWrite(String relativePath) {
        Path target = resolve(relativePath, false);
        if (target.equals(workspaceRoot)) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Workspace root cannot be used as a file target.", false);
        }
        if (Files.exists(target, LinkOption.NOFOLLOW_LINKS)
                && !Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            throw new WorkspaceToolException("FILE_TYPE_INVALID",
                    "Workspace target is not a regular file.", false);
        }
        if (Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            rejectHardLink(target);
        }
        return target;
    }

    /**
     * 安全创建目标文件的父目录。
     *
     * @param target 已由本保护器解析的目标路径
     * @throws AgentRuntimeException 父目录创建失败或出现符号链接时抛出
     */
    public void ensureParentDirectories(Path target) {
        requireInsideWorkspace(target);
        Path parent = target.getParent();
        if (parent == null || parent.equals(workspaceRoot)) {
            return;
        }
        Path relative = workspaceRoot.relativize(parent);
        Path current = workspaceRoot;
        try {
            for (Path segment : relative) {
                current = current.resolve(segment);
                if (Files.exists(current, LinkOption.NOFOLLOW_LINKS)) {
                    rejectSymbolicLink(current);
                    if (!Files.isDirectory(current, LinkOption.NOFOLLOW_LINKS)) {
                        throw new WorkspaceToolException("FILE_TYPE_INVALID",
                                "Workspace parent is not a directory.", false);
                    }
                    continue;
                }
                Files.createDirectory(current);
                rejectSymbolicLink(current);
            }
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                    "Workspace parent directory cannot be created.", true, error);
        }
    }

    /**
     * 再次校验目标路径的现有链路不包含符号链接，供原子提交前缩短竞态窗口。
     *
     * @param target 已解析目标
     * @throws AgentRuntimeException 路径越界或包含符号链接时抛出
     */
    public void revalidate(Path target) {
        requireInsideWorkspace(target);
        rejectExistingSymbolicLinks(target);
        if (Files.isRegularFile(target, LinkOption.NOFOLLOW_LINKS)) {
            rejectHardLink(target);
        }
    }

    /**
     * 将内部路径转换为不泄露宿主目录的工作区相对展示路径。
     *
     * @param target 工作区内路径
     * @return 使用正斜杠的相对路径，根目录返回 `.`
     */
    public String display(Path target) {
        requireInsideWorkspace(target);
        Path relative = workspaceRoot.relativize(target.normalize());
        if (relative.toString().isEmpty()) {
            return ".";
        }
        return relative.toString().replace(target.getFileSystem().getSeparator(), "/");
    }

    private Path resolve(String relativePath, boolean allowRoot) {
        validateRelativeInput(relativePath, allowRoot);
        Path submitted;
        try {
            submitted = Path.of(relativePath);
        } catch (RuntimeException error) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID", "Invalid workspace path.", false, error);
        }
        Path target = workspaceRoot.resolve(submitted).normalize();
        requireInsideWorkspace(target);
        rejectExistingSymbolicLinks(target);
        return target;
    }

    private void validateRelativeInput(String relativePath, boolean allowRoot) {
        if (relativePath == null || relativePath.isBlank() || relativePath.indexOf('\0') >= 0) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Workspace path is required and must not contain NUL.", false);
        }
        String trimmed = relativePath.trim();
        if (trimmed.startsWith("~") || WINDOWS_ABSOLUTE_PATH.matcher(trimmed).matches()) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Only workspace-relative paths are allowed.", false);
        }
        Path submitted;
        try {
            submitted = Path.of(trimmed);
        } catch (RuntimeException error) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID", "Invalid workspace path.", false, error);
        }
        if (submitted.isAbsolute()) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Only workspace-relative paths are allowed.", false);
        }
        for (Path segment : submitted) {
            if ("..".equals(segment.toString())) {
                throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                        "Workspace path traversal is not allowed.", false);
            }
        }
        if (!allowRoot && (".".equals(trimmed) || submitted.getNameCount() == 0)) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Workspace root cannot be used as a file target.", false);
        }
    }

    private void rejectExistingSymbolicLinks(Path target) {
        Path relative = workspaceRoot.relativize(target);
        Path current = workspaceRoot;
        for (Path segment : relative) {
            current = current.resolve(segment);
            if (!Files.exists(current, LinkOption.NOFOLLOW_LINKS)) {
                break;
            }
            rejectSymbolicLink(current);
        }
    }

    private void rejectSymbolicLink(Path path) {
        if (Files.isSymbolicLink(path)) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Symbolic links are not allowed in workspace paths.", false);
        }
    }

    private void rejectHardLink(Path path) {
        try {
            Object value = Files.getAttribute(path, "unix:nlink", LinkOption.NOFOLLOW_LINKS);
            if (value instanceof Number number && number.longValue() > 1) {
                throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                        "Hard-linked files are not allowed in workspace paths.", false);
            }
        } catch (UnsupportedOperationException ignored) {
            // 非 Unix 文件系统没有 unix:nlink 属性，仍保留 NOFOLLOW 与普通文件类型校验。
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                    "Workspace file link count cannot be inspected.", true, error);
        }
    }

    private void requireInsideWorkspace(Path target) {
        if (target == null || !target.normalize().startsWith(workspaceRoot)) {
            throw new WorkspaceToolException("WORKSPACE_PATH_INVALID",
                    "Workspace path escapes the configured root.", false);
        }
    }

}
