/**
 * HTTP route: POST /api/email/inbound
 *
 * Receives inbound emails from the Genspark backend, builds MsgContext
 * with OriginatingChannel="email", and dispatches to the agent using
 * the proper OpenClaw plugin runtime API (same as Feishu/Slack/Telegram).
 *
 * Auth: X-Email-Worker-Secret header.
 */

import type { IncomingMessage, ServerResponse } from "http";
import { getEmailRuntime } from "./runtime.js";
import { storeThreadInfo } from "./thread-store.js";

const EMAIL_SEND_SECRET = process.env.OPENCLAW_EMAIL_SEND_SECRET || "";

type InboundPayload = {
  from: string;
  to: string;
  subject: string;
  body: string;
  message_id: string;
  session_key: string;
  to_header?: string; // Full To header (all recipients)
  cc?: string; // Cc header
  bcc?: string; // Bcc header (rarely present — stripped by most MTAs)
  date?: string; // Original email Date header
  attachments?: Array<{ filename: string; content_type: string; url: string }>;
};

function readBody(req: IncomingMessage): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    req.on("data", (chunk: Buffer) => chunks.push(chunk));
    req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
    req.on("error", reject);
  });
}

function respond(res: ServerResponse, status: number, body: Record<string, unknown>) {
  res.writeHead(status, { "Content-Type": "application/json" });
  res.end(JSON.stringify(body));
}

/**
 * Register POST /api/email/inbound HTTP route on the Gateway.
 */
export function registerEmailInboundRoute(api: any): void {
  api.registerHttpRoute({
    path: "/api/email/inbound",
    auth: "plugin", // Plugin-level auth (we verify secret ourselves)
    match: "exact",
    handler: async (req: IncomingMessage, res: ServerResponse) => {
      if (req.method !== "POST") {
        respond(res, 405, { error: "Method not allowed" });
        return;
      }

      // Verify shared secret (required — reject if not configured or mismatch)
      const secret = req.headers["x-email-worker-secret"] as string | undefined;
      if (!EMAIL_SEND_SECRET) {
        respond(res, 500, { error: "OPENCLAW_EMAIL_SEND_SECRET not configured" });
        return;
      }
      if (secret !== EMAIL_SEND_SECRET) {
        respond(res, 403, { error: "Invalid secret" });
        return;
      }

      let payload: InboundPayload;
      try {
        const raw = await readBody(req);
        payload = JSON.parse(raw);
      } catch {
        respond(res, 400, { error: "Invalid JSON" });
        return;
      }

      const { from, subject, body, message_id, session_key } = payload;
      if (!from || !body) {
        respond(res, 400, { error: "Missing required fields: from, body" });
        return;
      }

      // Respond immediately, dispatch asynchronously.
      // Agent processing can take 10-60s; don't block the HTTP response.
      respond(res, 200, { status: "accepted" });

      dispatchEmailToAgent({
        from,
        subject: subject || "(no subject)",
        body,
        messageId: message_id || "",
        sessionKey: session_key || "main",
        toHeader: payload.to_header || "",
        cc: payload.cc || "",
        bcc: payload.bcc || "",
        date: payload.date || "",
        attachments: payload.attachments,
        logger: api.logger,
      }).catch((err) => {
        const msg = err instanceof Error ? err.message : String(err);
        api.logger.error(`[email-channel] Inbound dispatch error: ${msg}`);
      });
    },
  });

  api.logger.info("[email-channel] Registered HTTP route: POST /api/email/inbound");
}

/**
 * Dispatch an inbound email to the agent using the OpenClaw plugin runtime.
 *
 * Follows the same pattern as Feishu's handleFeishuMessage:
 * 1. Build MsgContext via finalizeInboundContext()
 * 2. Create reply dispatcher (noop — outbound handled by routeReply + sendText)
 * 3. Dispatch via withReplyDispatcher + dispatchReplyFromConfig
 */
async function dispatchEmailToAgent(params: {
  from: string;
  subject: string;
  body: string;
  messageId: string;
  sessionKey: string;
  toHeader: string;
  cc: string;
  bcc: string;
  date: string;
  attachments?: Array<{ filename: string; content_type: string; url: string }>;
  logger: any;
}): Promise<void> {
  const core = getEmailRuntime();
  const cfg = core.config?.loadConfig?.();

  if (!cfg) {
    throw new Error("OpenClaw config not available (runtime.config.loadConfig failed)");
  }

  const { from, subject, body, messageId, sessionKey, toHeader, cc, bcc, date, attachments } = params;

  // Build the message text that the agent sees.
  // Include To/Cc/Bcc so the agent knows if it was a direct recipient or CC'd.
  let messageBody = `[Email received]\nFrom: ${from}\nTo: ${toHeader || process.env.OPENCLAW_EMAIL_ADDRESS || ""}`;
  if (cc) {
    messageBody += `\nCc: ${cc}`;
  }
  if (bcc) {
    messageBody += `\nBcc: ${bcc}`;
  }
  messageBody += `\nSubject: ${subject}\n\n${body}`;

  // Append attachment info to the message
  const imageUrls: string[] = [];
  if (attachments && attachments.length > 0) {
    messageBody += "\n\nAttachments:";
    for (const att of attachments) {
      messageBody += `\n  - ${att.filename} (${att.content_type}): ${att.url}`;
      // Collect image URLs for vision model support
      if (att.content_type.startsWith("image/")) {
        imageUrls.push(att.url);
      }
    }
  }

  // Build media payload for vision-capable models
  const mediaPayload: Record<string, unknown> = {};
  if (imageUrls.length > 0) {
    mediaPayload.ImageUrl = imageUrls[0];
    mediaPayload.ImageUrls = imageUrls;
  }

  // Build MsgContext with email routing info
  const ctx = core.channel.reply.finalizeInboundContext({
    Body: messageBody,
    BodyForAgent: messageBody,
    RawBody: body,
    CommandBody: messageBody,
    From: from,
    To: process.env.OPENCLAW_EMAIL_ADDRESS || "",
    SessionKey: sessionKey,
    AccountId: "default",
    ChatType: "direct",
    SenderName: from,
    SenderId: from,
    // Provider/Surface must differ from OriginatingChannel for routeReply to
    // trigger. We use "webchat" as the surface (message enters via HTTP), while
    // OriginatingChannel="email" tells Gateway to route the reply via email.
    Provider: "webchat",
    Surface: "webchat",
    MessageSid: messageId,
    Timestamp: Date.now(),
    WasMentioned: true,
    CommandAuthorized: true,
    ExplicitDeliverRoute: true,
    OriginatingChannel: "email",
    OriginatingTo: from,
    // Use a short hash key as MessageThreadId to avoid ENAMETOOLONG errors.
    // OpenClaw uses this value in session filenames — URL-encoded Chinese
    // subjects + long Outlook Message-IDs can exceed the 255-byte limit.
    // The outbound handler looks up the hash to recover the original
    // subject (for "Re: ..." line) and messageId (for In-Reply-To header).
    MessageThreadId: await storeThreadInfo(subject, messageId, {
      originalBody: body,
      originalFrom: from,
      originalDate: date || new Date().toISOString(),
    }),
    ...mediaPayload,
  });

  params.logger.info(
    `[email-channel] Dispatching email: from=${from} subject=${subject} session=${sessionKey}`,
  );

  // Create a noop dispatcher — outbound delivery is handled by
  // routeReply("email") → channel.outbound.sendText() → backend MailChannels.
  // We don't need to send replies directly from here.
  const noopDispatcher = {
    sendToolResult: () => false,
    sendBlockReply: () => false,
    sendFinalReply: () => false,
    waitForIdle: async () => {},
    getQueuedCounts: () => ({ tool: 0, block: 0, final: 0 }),
    markComplete: () => {},
  };

  await core.channel.reply.withReplyDispatcher({
    dispatcher: noopDispatcher,
    run: () =>
      core.channel.reply.dispatchReplyFromConfig({
        ctx,
        cfg,
        dispatcher: noopDispatcher,
      }),
  });

  params.logger.info(`[email-channel] Email dispatched to agent: session=${sessionKey}`);
}

