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

import com.easyagents.agent.runtime.AgentRuntimeException;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.tool.AgentTool;
import io.agentscope.core.tool.ToolCallParam;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.stream.Stream;

/**
 * 与 AgentScope 1.x 文件读取 Schema 兼容的工作区安全工具。
 */
final class SafeReadFileTool {

    private final ViewTextFileTool viewTextFileTool;
    private final ListDirectoryTool listDirectoryTool;

    /**
     * 创建文件读取工具组。
     *
     * @param pathGuard 路径保护器
     * @param quotaGuard 配额保护器
     */
    SafeReadFileTool(WorkspacePathGuard pathGuard, WorkspaceQuotaGuard quotaGuard) {
        this.viewTextFileTool = new ViewTextFileTool(pathGuard, quotaGuard);
        this.listDirectoryTool = new ListDirectoryTool(pathGuard, quotaGuard);
    }

    /**
     * 获取查看文本文件工具。
     *
     * @return AgentScope 工具
     */
    AgentTool viewTextFileTool() {
        return viewTextFileTool;
    }

    /**
     * 获取列目录工具。
     *
     * @return AgentScope 工具
     */
    AgentTool listDirectoryTool() {
        return listDirectoryTool;
    }

    /**
     * 查看工作区文本文件。
     */
    private static final class ViewTextFileTool implements AgentTool {

        private final WorkspacePathGuard pathGuard;
        private final WorkspaceQuotaGuard quotaGuard;

        private ViewTextFileTool(WorkspacePathGuard pathGuard, WorkspaceQuotaGuard quotaGuard) {
            this.pathGuard = pathGuard;
            this.quotaGuard = quotaGuard;
        }

        /**
         * 获取工具名。
         *
         * @return `view_text_file`
         */
        @Override
        public String getName() {
            return AgentOperateToolAdapter.VIEW_TEXT_FILE_TOOL;
        }

        /**
         * 获取工具描述。
         *
         * @return 工具描述
         */
        @Override
        public String getDescription() {
            return "View UTF-8 text file content in the workspace with optional line ranges.";
        }

        /**
         * 获取与 AgentScope 1.x 兼容的参数 Schema。
         *
         * @return JSON Schema
         */
        @Override
        public Map<String, Object> getParameters() {
            Map<String, Object> properties = new LinkedHashMap<>();
            properties.put("file_path", Map.of("type", "string", "description", "The target file path"));
            properties.put("ranges", Map.of(
                    "type", "string",
                    "description", "Optional inclusive line range such as '1,100' or '-100,-1'"));
            return Map.of("type", "object", "properties", properties, "required", List.of("file_path"));
        }

        /**
         * 读取并格式化指定行范围。
         *
         * @param param Tool 调用参数
         * @return Tool 结果
         */
        @Override
        public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
            return Mono.fromCallable(() -> view(param)).subscribeOn(Schedulers.boundedElastic());
        }

        private ToolResultBlock view(ToolCallParam param) {
            try {
                String filePath = requiredString(param, "file_path");
                String ranges = optionalString(param, "ranges");
                Path target = pathGuard.resolveExistingFile(filePath);
                WorkspaceTextFiles.RangedLines rangedLines = WorkspaceTextFiles.readUtf8Lines(
                        target, ranges, quotaGuard.maxReadSize());
                quotaGuard.validateRangeRead(target, rangedLines.readBytes());
                StringBuilder content = new StringBuilder();
                for (int index = 0; index < rangedLines.lines().size(); index++) {
                    content.append(rangedLines.startLine() + index).append(": ")
                            .append(rangedLines.lines().get(index)).append('\n');
                }
                int endLine = rangedLines.lines().isEmpty()
                        ? rangedLines.startLine() - 1
                        : rangedLines.startLine() + rangedLines.lines().size() - 1;
                return ToolResultBlock.text("The content of " + pathGuard.display(target)
                        + " in lines [" + rangedLines.startLine() + ", " + endLine + "]:\n```\n"
                        + content + "```");
            } catch (AgentRuntimeException error) {
                return WorkspaceToolResults.error(error);
            } catch (RuntimeException error) {
                return WorkspaceToolResults.error(
                        new AgentRuntimeException("Unexpected workspace read failure.", error));
            }
        }
    }

    /**
     * 列出工作区单层目录内容。
     */
    private static final class ListDirectoryTool implements AgentTool {

        private final WorkspacePathGuard pathGuard;
        private final WorkspaceQuotaGuard quotaGuard;

        private ListDirectoryTool(WorkspacePathGuard pathGuard, WorkspaceQuotaGuard quotaGuard) {
            this.pathGuard = pathGuard;
            this.quotaGuard = quotaGuard;
        }

        /**
         * 获取工具名。
         *
         * @return `list_directory`
         */
        @Override
        public String getName() {
            return AgentOperateToolAdapter.LIST_DIRECTORY_TOOL;
        }

        /**
         * 获取工具描述。
         *
         * @return 工具描述
         */
        @Override
        public String getDescription() {
            return "List one level of files and directories using workspace-relative paths.";
        }

        /**
         * 获取与 AgentScope 1.x 兼容的参数 Schema。
         *
         * @return JSON Schema
         */
        @Override
        public Map<String, Object> getParameters() {
            return Map.of(
                    "type", "object",
                    "properties", Map.of("dir_path", Map.of(
                            "type", "string", "description", "The target directory path")),
                    "required", List.of("dir_path"));
        }

        /**
         * 列出单层目录。
         *
         * @param param Tool 调用参数
         * @return Tool 结果
         */
        @Override
        public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
            return Mono.fromCallable(() -> list(param)).subscribeOn(Schedulers.boundedElastic());
        }

        private ToolResultBlock list(ToolCallParam param) {
            try {
                Path directory = pathGuard.resolveExistingDirectory(requiredString(param, "dir_path"));
                quotaGuard.validateCurrentUsage();
                int limit = quotaGuard.maxDirectoryEntries();
                Comparator<Path> displayOrder = Comparator.comparing(pathGuard::display);
                PriorityQueue<Path> retained = new PriorityQueue<>(limit, displayOrder.reversed());
                long entryCount = 0;
                try (Stream<Path> stream = Files.list(directory)) {
                    for (Path entry : (Iterable<Path>) stream::iterator) {
                        entryCount++;
                        if (retained.size() < limit) {
                            retained.add(entry);
                        } else if (displayOrder.compare(entry, retained.peek()) < 0) {
                            retained.poll();
                            retained.add(entry);
                        }
                    }
                } catch (IOException error) {
                    throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                            "Workspace directory cannot be listed.", true, error);
                }
                List<Path> entries = new ArrayList<>(retained);
                entries.sort(displayOrder);
                StringBuilder result = new StringBuilder("Contents of directory ")
                        .append(pathGuard.display(directory)).append(":\n");
                boolean truncated = entryCount > limit;
                for (Path entry : entries) {
                    String type;
                    long size = 0;
                    if (Files.isSymbolicLink(entry)) {
                        type = "blocked-symlink";
                    } else if (Files.isDirectory(entry, LinkOption.NOFOLLOW_LINKS)) {
                        type = "directory";
                    } else if (Files.isRegularFile(entry, LinkOption.NOFOLLOW_LINKS)) {
                        type = "file";
                        try {
                            size = Files.size(entry);
                        } catch (IOException error) {
                            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                                    "Workspace entry size cannot be inspected.", true, error);
                        }
                    } else {
                        type = "blocked-non-regular";
                    }
                    result.append(type).append('\t').append(pathGuard.display(entry));
                    if ("file".equals(type)) {
                        result.append('\t').append(size).append(" bytes");
                    }
                    result.append('\n');
                }
                if (truncated) {
                    result.append("Truncated: true; limit=")
                            .append(limit).append('\n');
                }
                return ToolResultBlock.text(result.toString());
            } catch (AgentRuntimeException error) {
                return WorkspaceToolResults.error(error);
            } catch (RuntimeException error) {
                return WorkspaceToolResults.error(
                        new AgentRuntimeException("Unexpected workspace listing failure.", error));
            }
        }
    }

    private static String requiredString(ToolCallParam param, String name) {
        Object value = param == null ? null : param.getInput().get(name);
        if (!(value instanceof String text) || text.isBlank()) {
            throw new WorkspaceToolException("INVALID_ARGUMENT",
                    "Missing required string parameter: " + name, false);
        }
        return text;
    }

    private static String optionalString(ToolCallParam param, String name) {
        Object value = param == null ? null : param.getInput().get(name);
        if (value == null) {
            return null;
        }
        if (!(value instanceof String text)) {
            throw new WorkspaceToolException("INVALID_ARGUMENT",
                    "Invalid string parameter: " + name, false);
        }
        return text;
    }

}
