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

import com.easyagents.agent.runtime.AgentRuntimeException;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.agent.runtime.tool.AgentToolVisibility;
import io.agentscope.core.tool.Toolkit;
import java.nio.file.Path;
import java.util.*;

/**
 * AgentScope 内置操作工具适配器。
 *
 * <p>该适配器将 Easy-Agents 的操作工具声明转换为与 AgentScope 1.x 工具名和 Schema 兼容的
 * 受控实现。Shell 人工审批继续通过 Easy-Agents {@code ToolHitlInterceptor} 处理，以保持
 * SSE 暂停、恢复和审计语义一致。
 */
public class AgentOperateToolAdapter {

    public static final String VIEW_TEXT_FILE_TOOL = "view_text_file";
    public static final String LIST_DIRECTORY_TOOL = "list_directory";
    public static final String WRITE_TEXT_FILE_TOOL = "write_text_file";
    public static final String INSERT_TEXT_FILE_TOOL = "insert_text_file";
    public static final String APPLY_PATCH_TOOL = "apply_patch";
    public static final String EXECUTE_SHELL_COMMAND_TOOL = "execute_shell_command";

    /**
     * 将操作工具声明注册到 Toolkit，并返回供 HITL 拦截器使用的工具声明。
     *
     * @param specs 操作工具声明
     * @param toolkit AgentScope Toolkit
     * @return 已启用操作工具对应的 AgentToolSpec
     */
    public List<AgentToolSpec> register(List<AgentOperateToolSpec> specs, Toolkit toolkit) {
        List<AgentToolSpec> toolSpecs = new ArrayList<>();
        if (specs == null || specs.isEmpty()) {
            return toolSpecs;
        }
        for (AgentOperateToolSpec spec : specs) {
            if (spec == null || !spec.isEnabled()) {
                continue;
            }
            registerOne(spec, toolkit, toolSpecs);
        }
        return toolSpecs;
    }

    /**
     * 解析已启用操作工具会注册到 AgentScope 的工具名称。
     *
     * @param specs 操作工具声明
     * @return 工具名称集合
     */
    public Set<String> enabledToolNames(List<AgentOperateToolSpec> specs) {
        Set<String> names = new LinkedHashSet<>();
        if (specs == null || specs.isEmpty()) {
            return names;
        }
        for (AgentOperateToolSpec spec : specs) {
            if (spec == null || !spec.isEnabled() || spec.getType() == null) {
                continue;
            }
            switch (spec.getType()) {
                case READ_FILE -> {
                    names.add(VIEW_TEXT_FILE_TOOL);
                    names.add(LIST_DIRECTORY_TOOL);
                }
                case WRITE_FILE -> {
                    names.add(WRITE_TEXT_FILE_TOOL);
                    names.add(INSERT_TEXT_FILE_TOOL);
                }
                case PATCH -> names.add(APPLY_PATCH_TOOL);
                case SHELL -> names.add(EXECUTE_SHELL_COMMAND_TOOL);
                default -> {
                }
            }
        }
        return names;
    }

    private void registerOne(AgentOperateToolSpec spec, Toolkit toolkit, List<AgentToolSpec> toolSpecs) {
        AgentOperateToolType type = spec.getType();
        if (type == null) {
            throw new AgentRuntimeException("Agent operate tool type is required.");
        }
        WorkspacePathGuard pathGuard = createPathGuard(spec);
        WorkspaceQuotaGuard quotaGuard = new WorkspaceQuotaGuard(
                pathGuard, spec.getWorkspaceQuotaLimits(), spec.getWorkspaceQuotaHook());
        switch (type) {
            case READ_FILE -> {
                assertNoToolConflict(toolkit, VIEW_TEXT_FILE_TOOL);
                assertNoToolConflict(toolkit, LIST_DIRECTORY_TOOL);
                SafeReadFileTool readFileTool = new SafeReadFileTool(pathGuard, quotaGuard);
                toolkit.registerAgentTool(readFileTool.viewTextFileTool());
                toolkit.registerAgentTool(readFileTool.listDirectoryTool());
                toolSpecs.add(toolSpec(spec, VIEW_TEXT_FILE_TOOL, "View text file content.", false));
                toolSpecs.add(toolSpec(spec, LIST_DIRECTORY_TOOL, "List files and directories.", false));
            }
            case WRITE_FILE -> {
                assertNoToolConflict(toolkit, WRITE_TEXT_FILE_TOOL);
                assertNoToolConflict(toolkit, INSERT_TEXT_FILE_TOOL);
                SafeWriteFileTool writeFileTool = new SafeWriteFileTool(pathGuard, quotaGuard);
                toolkit.registerAgentTool(writeFileTool.writeTextFileTool());
                toolkit.registerAgentTool(writeFileTool.insertTextFileTool());
                toolSpecs.add(toolSpec(spec, WRITE_TEXT_FILE_TOOL, "Write or replace text file content.", false));
                toolSpecs.add(toolSpec(spec, INSERT_TEXT_FILE_TOOL, "Insert text into a file.", false));
            }
            case PATCH -> {
                assertNoToolConflict(toolkit, APPLY_PATCH_TOOL);
                toolkit.registerAgentTool(new ApplyPatchTool(
                        pathGuard, quotaGuard, spec.getPatchMaxSize(),
                        spec.getPatchMaxFiles(), spec.getPatchMaxAffectedBytes()));
                toolSpecs.add(toolSpec(spec, APPLY_PATCH_TOOL, "Apply a workspace text patch.", false));
            }
            case SHELL -> {
                assertNoToolConflict(toolkit, EXECUTE_SHELL_COMMAND_TOOL);
                ControlledShellTool shellTool = new ControlledShellTool(pathGuard, quotaGuard, spec);
                toolkit.registerAgentTool(shellTool);
                AgentToolSpec shellToolSpec = toolSpec(
                        spec, EXECUTE_SHELL_COMMAND_TOOL, "Execute shell command.", true);
                if (shellToolSpec.isApprovalRequired()) {
                    // 命令级审批策略服从 Agent 的 Shell 审批开关；关闭后仅保留安全校验。
                    shellToolSpec.setApprovalPolicy(shellTool::approvalEvaluation);
                }
                toolSpecs.add(shellToolSpec);
            }
            default -> throw new AgentRuntimeException("Unsupported agent operate tool type: " + type);
        }
    }

    private WorkspacePathGuard createPathGuard(AgentOperateToolSpec spec) {
        String baseDir = spec.getBaseDir();
        if (baseDir == null || baseDir.isBlank()) {
            throw new AgentRuntimeException("Agent operate tool baseDir is required.");
        }
        if (!Path.of(baseDir).isAbsolute()) {
            throw new AgentRuntimeException("Agent operate tool baseDir must be an absolute path.");
        }
        try {
            return new WorkspacePathGuard(Path.of(baseDir).toAbsolutePath().normalize());
        } catch (RuntimeException error) {
            if (error instanceof AgentRuntimeException runtimeError) {
                throw runtimeError;
            }
            throw new AgentRuntimeException("Agent operate tool baseDir is invalid.", error);
        }
    }

    private AgentToolSpec toolSpec(AgentOperateToolSpec operateSpec,
                                   String toolName,
                                   String description,
                                   boolean defaultApprovalRequired) {
        boolean approvalRequired = operateSpec.getApprovalRequired() == null
                ? defaultApprovalRequired : operateSpec.getApprovalRequired();
        AgentToolSpec toolSpec = new AgentToolSpec();
        toolSpec.setName(toolName);
        toolSpec.setDescription(description);
        toolSpec.setCategory(AgentToolCategory.CUSTOM);
        toolSpec.setVisibility(AgentToolVisibility.VISIBLE);
        toolSpec.setApprovalRequired(approvalRequired);
        toolSpec.setApprovalRequest(approvalRequest(operateSpec, approvalRequired));
        toolSpec.setMetadata(metadata(operateSpec, approvalRequired));
        return toolSpec;
    }

    private AgentToolApprovalRequest approvalRequest(AgentOperateToolSpec operateSpec, boolean approvalRequired) {
        AgentToolApprovalRequest request = operateSpec.getApprovalRequest();
        if (request != null) {
            return request;
        }
        AgentToolApprovalRequest defaultRequest = new AgentToolApprovalRequest();
        if (approvalRequired) {
            defaultRequest.setApprovalPrompt("请确认是否允许智能体执行该操作工具。");
        }
        return defaultRequest;
    }

    private Map<String, Object> metadata(AgentOperateToolSpec spec, boolean approvalRequired) {
        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("operateTool", true);
        metadata.put("operateToolType", spec.getType().name());
        if (spec.getType() == AgentOperateToolType.SHELL && approvalRequired) {
            metadata.put("forceApprovalCommands", List.of("rm"));
            metadata.put("forceApprovalCommandArgument", "command");
        }
        return metadata;
    }

    private void assertNoToolConflict(Toolkit toolkit, String toolName) {
        if (toolkit.getTool(toolName) != null) {
            throw new AgentRuntimeException("Agent operate tool conflicts with existing tool: " + toolName);
        }
    }
}
