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.oa.OaMailClient;
import com.gzzm.lobster.oa.OaMailDraft;
import com.gzzm.lobster.tool.BuiltinToolDefinition;
import com.gzzm.lobster.tool.SchemaBuilder;
import com.gzzm.lobster.tool.SideEffectLevel;
import com.gzzm.lobster.tool.ToolContext;
import com.gzzm.lobster.tool.ToolRegistry;
import com.gzzm.lobster.tool.ToolResult;
import net.cyan.nest.annotation.Inject;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** Built-in tools for OA mail. */
public class OaMailTools {

    @Inject private OaMailClient oaMailClient;
    @Inject private AuditService auditService;

    public void registerTo(ToolRegistry registry) {
        registry.register(queryReceiverDef(), this::queryReceivers);
        registry.register(listDef(), this::list);
        registry.register(detailDef(), this::detail);
        registry.register(saveDraftDef(), this::saveDraft);
        registry.register(sendDef(), this::send);
    }

    private BuiltinToolDefinition queryReceiverDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_mail_query_receivers")
                .displayName("Query OA mail receivers")
                .description("Query OA email receivers by name, spelling, login, or email fragment. Use returned value fields as mail recipients.")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .prop("word", "string", "Receiver search word")
                        .propInt("maxResults", "Maximum receivers to return, 1-20. Default is 10.")
                        .required("word")
                        .build())
                .build();
    }

    private ToolResult queryReceivers(ToolContext ctx, Map<String, Object> args) throws Exception {
        String word = asStr(args.get("word"));
        if (isBlank(word)) return ToolResult.error("word is required");
        int max = clamp(asInt(args.get("maxResults"), 10), 1, 20);
        List<Map<String, Object>> receivers = oaMailClient.queryReceivers(ctx.getUserContext(), word, max);
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.mail.receiver.query", "oa_mail_receiver", word, "ok", null);
        Map<String, Object> data = new LinkedHashMap<String, Object>();
        data.put("receivers", receivers);
        return ToolResult.ok("Found " + receivers.size() + " receiver(s)", data);
    }

    private BuiltinToolDefinition listDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_mail_list")
                .displayName("List OA mails")
                .description("List current user's OA mailbox. Supported type values: received, sended, draft.")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .propEnum("type", "Mailbox type", "received", "sended", "draft")
                        .prop("query", "string", "Optional local filter by title, sender, or receiver")
                        .propBool("unreadOnly", "Only list unread received mails. Valid only when type is received.")
                        .propInt("page", "Page number from 1. Default is 1.")
                        .propInt("pageSize", "Page size, 1-50. Default is 10.")
                        .build())
                .build();
    }

    private ToolResult list(ToolContext ctx, Map<String, Object> args) throws Exception {
        String type = asStr(args.get("type"));
        int page = Math.max(1, asInt(args.get("page"), 1));
        int pageSize = clamp(asInt(args.get("pageSize"), 10), 1, 50);
        boolean unreadOnly = asBool(args.get("unreadOnly"), false);
        Map<String, Object> data = oaMailClient.listMails(ctx.getUserContext(), type, asStr(args.get("query")),
                unreadOnly, page, pageSize);
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.mail.list", "oa_mailbox", type == null ? "received" : type, "ok", null);
        Object items = data.get("items");
        int size = items instanceof List ? ((List<?>) items).size() : 0;
        return ToolResult.ok("Listed " + size + " mail(s)", data);
    }

    private BuiltinToolDefinition detailDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_mail_get")
                .displayName("Get OA mail detail")
                .description("Get an OA mail detail by mailId. Received mails can be marked as read.")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.READ_ONLY)
                .inputSchema(SchemaBuilder.obj()
                        .prop("mailId", "string", "OA mail ID")
                        .propBool("markRead", "Whether to mark received mail as read. Default is false.")
                        .required("mailId")
                        .build())
                .build();
    }

    private ToolResult detail(ToolContext ctx, Map<String, Object> args) throws Exception {
        Long mailId = asLong(args.get("mailId"));
        if (mailId == null) return ToolResult.error("mailId is required");
        boolean markRead = asBool(args.get("markRead"), false);
        Map<String, Object> data = oaMailClient.getMail(ctx.getUserContext(), ctx.getThreadId(), mailId, markRead);
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.mail.get", "oa_mail", String.valueOf(mailId), "ok", null);
        return ToolResult.ok("Read mail " + mailId, data);
    }

    private BuiltinToolDefinition saveDraftDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_mail_save_draft")
                .displayName("Save OA mail draft")
                .description("Save an OA mail draft for the current user. Recipient fields accept OA receiver values such as \"Name\"<1601@local>.")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.WRITE)
                .sideEffect(SideEffectLevel.WRITE_EXTERNAL)
                .inputSchema(mailWriteSchema(false))
                .build();
    }

    private ToolResult saveDraft(ToolContext ctx, Map<String, Object> args) throws Exception {
        OaMailDraft draft = draftFromArgs(args);
        Map<String, Object> data = oaMailClient.saveDraft(ctx.getUserContext(), draft);
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.mail.draft.save", "oa_mail", String.valueOf(data.get("mailId")), "ok", null);
        return ToolResult.ok("Saved draft " + String.valueOf(data.get("mailId")), data);
    }

    private BuiltinToolDefinition sendDef() {
        return BuiltinToolDefinition.builder()
                .name("oa_mail_send")
                .displayName("Send OA mail")
                .description("Send OA mail as the current user. Query receivers first when names are ambiguous.")
                .category(ToolCategory.OA)
                .risk(ToolRiskLevel.WRITE)
                .sideEffect(SideEffectLevel.WRITE_EXTERNAL)
                .requireConfirm(true)
                .inputSchema(mailWriteSchema(true))
                .build();
    }

    private ToolResult send(ToolContext ctx, Map<String, Object> args) throws Exception {
        OaMailDraft draft = draftFromArgs(args);
        Map<String, Object> data = oaMailClient.sendMail(ctx.getUserContext(), draft);
        auditService.record(ctx.getUserContext(), ctx.getThreadId(), ctx.getRunId(),
                "oa.mail.send", "oa_mail", String.valueOf(data.get("bodyId")), "ok", null);
        return ToolResult.ok("Mail sent", data);
    }

    private Map<String, Object> mailWriteSchema(boolean requireTo) {
        SchemaBuilder b = SchemaBuilder.obj()
                .prop("mailId", "string", "Existing draft mail ID to update before saving. Optional.")
                .prop("to", "string", "To recipients, semicolon separated. Use receiver value format.")
                .prop("cc", "string", "CC recipients, semicolon separated")
                .prop("bcc", "string", "BCC recipients, semicolon separated")
                .prop("title", "string", "Mail title")
                .prop("content", "string", "Mail HTML or plain text content")
                .propBool("urgent", "Whether the mail is urgent")
                .propBool("notify", "Whether to force notification. Default false.")
                .propArray("attachmentRefs", "Optional workspace resource, artifact, or OA file refs to attach",
                        stringSchema());
        if (requireTo) b.required("to", "title", "content");
        else b.required("title", "content");
        return b.build();
    }

    private Map<String, Object> stringSchema() {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("type", "string");
        return m;
    }

    private OaMailDraft draftFromArgs(Map<String, Object> args) {
        OaMailDraft d = new OaMailDraft();
        d.setMailId(asLong(args.get("mailId")));
        d.setTo(asStr(args.get("to")));
        d.setCc(asStr(args.get("cc")));
        d.setBcc(asStr(args.get("bcc")));
        d.setTitle(asStr(args.get("title")));
        d.setContent(asStr(args.get("content")));
        d.setUrgent(asBoolObject(args.get("urgent")));
        d.setNotify(asBoolObject(args.get("notify")));
        d.setAttachmentRefs(asStringList(args.get("attachmentRefs")));
        return d;
    }

    @SuppressWarnings("unchecked")
    private List<String> asStringList(Object o) {
        if (!(o instanceof List)) return null;
        List<String> out = new ArrayList<String>();
        for (Object item : (List<Object>) o) {
            if (item == null) continue;
            String s = String.valueOf(item).trim();
            if (!s.isEmpty()) out.add(s);
        }
        return out;
    }

    private String asStr(Object o) { return o == null ? null : String.valueOf(o); }
    private boolean isBlank(String s) { return s == null || s.trim().isEmpty(); }
    private int asInt(Object o, int def) {
        Integer v = asIntObject(o);
        return v == null ? def : v;
    }
    private Integer asIntObject(Object o) {
        if (o == null) return null;
        if (o instanceof Number) return ((Number) o).intValue();
        try { return Integer.valueOf(String.valueOf(o)); } catch (Exception e) { return null; }
    }
    private Long asLong(Object o) {
        if (o == null) return null;
        if (o instanceof Number) return ((Number) o).longValue();
        try { return Long.valueOf(String.valueOf(o)); } catch (Exception e) { return null; }
    }
    private boolean asBool(Object o, boolean def) {
        Boolean b = asBoolObject(o);
        return b == null ? def : b;
    }
    private Boolean asBoolObject(Object o) {
        if (o == null) return null;
        if (o instanceof Boolean) return (Boolean) o;
        return Boolean.valueOf(String.valueOf(o));
    }
    private int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }
}
