package com.gzzm.lobster.tool.builtin;

import com.gzzm.lobster.audit.AuditService;
import com.gzzm.lobster.common.ToolCategory;
import com.gzzm.lobster.common.ToolRiskLevel;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.oa.OaFileClient;
import com.gzzm.lobster.oa.OaFileInfo;
import com.gzzm.lobster.thread.ThreadRoom;
import com.gzzm.lobster.thread.ThreadService;
import com.gzzm.lobster.tool.*;
import com.gzzm.lobster.workspace.WorkspaceResource;
import com.gzzm.lobster.workspace.WorkspaceService;
import net.cyan.nest.annotation.Inject;

import java.util.*;

/**
 * OaFileTools —— Layer 5 OA 文件工具 /
 * Layer 5 OA file tools (list / read / write / metadata).
 */
public class OaFileTools {

    private static final int MAX_READ_CHARS = 16000;

    @Inject private OaFileClient oaFileClient;
    @Inject private ThreadService threadService;
    @Inject private WorkspaceService workspaceService;
    @Inject private AuditService auditService;

    public void registerTo(ToolRegistry registry) {
        registry.register(listDef(), this::list);
        registry.register(readDef(), this::read);
        registry.register(writeDef(), this::write);
        registry.register(metaDef(), this::meta);
    }

    private BuiltinToolDefinition listDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_list_files")
                .displayName("列出 OA 个人文件")
                .description("列出当前实名用户 OA 个人文件库中的文件。调用时透传用户身份。")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .prop("folderPath", "string", "目录路径（可选）")
                        .propInt("page", "页码，从 1")
                        .propInt("pageSize", "每页数量，默认 20，上限 50")
                        .build())
                .build();
    }

    private ToolResult list(ToolContext ctx, Map<String, Object> args) throws Exception {
        String folder = asStr(args.get("folderPath"));
        int page = Math.max(1, asInt(args.get("page"), 1));
        int pageSize = clamp(asInt(args.get("pageSize"), 20), 1, 50);
        List<OaFileInfo> list = oaFileClient.listPersonal(ctx.getUserContext(),
                folder, (page - 1) * pageSize, pageSize);
        List<Map<String, Object>> items = new ArrayList<>();
        for (OaFileInfo f : list) {
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("fileId", f.getFileId());
            row.put("displayName", f.getDisplayName());
            row.put("mimeType", f.getMimeType());
            row.put("size", f.getSize());
            row.put("folderPath", f.getFolderPath());
            row.put("updateTime", f.getUpdateTime());
            items.add(row);
        }
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.file.list", "oa_folder", folder, "ok", null);
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("items", items);
        data.put("total", items.size());
        return ToolResult.okData(data);
    }

    private BuiltinToolDefinition readDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_read_file")
                .displayName("读取 OA 文件")
                .description("读取一个 OA 文件的文本内容。大文件按字符范围分段。读取的同时登记到 workspace 资源。")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .prop("fileId", "string", "OA 文件 ID")
                        .propInt("offset", "起始字符索引，默认 0")
                        .propInt("limit", "最大字符数，默认 8000，上限 " + MAX_READ_CHARS)
                        .required("fileId")
                        .build())
                .build();
    }

    private ToolResult read(ToolContext ctx, Map<String, Object> args) throws Exception {
        String threadId = ctx.getThreadId();
        String fileId = asStr(args.get("fileId"));
        int offset = Math.max(0, asInt(args.get("offset"), 0));
        int limit = clamp(asInt(args.get("limit"), 8000), 1, MAX_READ_CHARS);
        UserContext user = ctx.getUserContext();
        ThreadRoom thread = threadService.requireOwnedThread(user, threadId);
        String full = oaFileClient.readAsText(user, fileId);
        if (full == null) {
            auditService.record(user, threadId, ctx.getRunId(), "oa.file.read", "oa_file", fileId, "error", null);
            return ToolResult.error("OA 文件不存在或无权限: " + fileId);
        }
        OaFileInfo meta = oaFileClient.getMetadata(user, fileId);
        String content = full.substring(Math.min(full.length(), offset),
                Math.min(full.length(), offset + limit));
        // 自动登记到 workspace（若未登记）
        try {
            workspaceService.registerOaFileReference(thread, user, fileId,
                    meta == null ? fileId : meta.getDisplayName(),
                    meta == null ? "text/plain" : meta.getMimeType(), "tool_read");
        } catch (Throwable ignore) { /* 允许重复登记失败 */ }
        auditService.record(user, threadId, ctx.getRunId(), "oa.file.read", "oa_file", fileId, "ok", null);
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("fileId", fileId);
        data.put("displayName", meta == null ? null : meta.getDisplayName());
        data.put("content", content);
        data.put("totalLength", full.length());
        data.put("truncated", offset + limit < full.length());
        return ToolResult.okData(data);
    }

    private BuiltinToolDefinition writeDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_write_file")
                .displayName("写入 OA 文件")
                .description("向当前用户 OA 文件库写入一个新文件。调用时透传用户身份，由 OA 侧裁决权限。")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.WRITE)
                .inputSchema(SchemaBuilder.obj()
                        .prop("folderPath", "string", "目标目录（可选，默认根）")
                        .prop("displayName", "string", "文件显示名")
                        .prop("content", "string", "文件文本内容")
                        .prop("mimeType", "string", "MIME 类型（可选）")
                        .required("displayName", "content")
                        .build())
                .build();
    }

    private ToolResult write(ToolContext ctx, Map<String, Object> args) throws Exception {
        UserContext user = ctx.getUserContext();
        String folder = asStr(args.get("folderPath"));
        String name = asStr(args.get("displayName"));
        String content = asStr(args.get("content"));
        String mime = asStr(args.get("mimeType"));
        String id = oaFileClient.writeNew(user, folder, name, content, mime);
        auditService.record(user, ctx.getThreadId(), ctx.getRunId(),
                "oa.file.write", "oa_file", id, "ok", null);
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("fileId", id);
        data.put("displayName", name);
        return ToolResult.ok("OA 文件已写入", data);
    }

    private BuiltinToolDefinition metaDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_get_file_metadata")
                .displayName("获取 OA 文件元信息")
                .description("获取 OA 文件元信息（文件名、大小、MIME、修改时间等）。")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .prop("fileId", "string", "OA 文件 ID")
                        .required("fileId")
                        .build())
                .build();
    }

    private ToolResult meta(ToolContext ctx, Map<String, Object> args) throws Exception {
        String fileId = asStr(args.get("fileId"));
        OaFileInfo info = oaFileClient.getMetadata(ctx.getUserContext(), fileId);
        if (info == null) return ToolResult.error("OA 文件不存在");
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("fileId", info.getFileId());
        data.put("displayName", info.getDisplayName());
        data.put("mimeType", info.getMimeType());
        data.put("size", info.getSize());
        data.put("folderPath", info.getFolderPath());
        data.put("updateTime", info.getUpdateTime());
        return ToolResult.okData(data);
    }

    private String asStr(Object o) { return o == null ? null : String.valueOf(o); }
    private int asInt(Object o, int def) {
        if (o == null) return def;
        if (o instanceof Number) return ((Number) o).intValue();
        try { return Integer.parseInt(String.valueOf(o)); } catch (Exception e) { return def; }
    }
    private int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }
}
