import { NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";
import { z } from "zod";
import { Webhook } from "svix";
import { Resend } from "resend";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { InboxSettings, InboxEmail } from "../../data/entities.js";
import { parseInboundEmail } from "../../lib/emailParser.js";
import { checkRateLimit } from "../../lib/rateLimiter.js";
import { emitInboxOpsEvent } from "../../events.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("inbox_ops").child({ component: "webhook" });
const metadata = {
  POST: { requireAuth: false }
};
const MAX_PAYLOAD_SIZE = 2 * 1024 * 1024;
const REPLAY_WINDOW_MS = 5 * 60 * 1e3;
function isTimestampFresh(timestamp) {
  if (!timestamp) return false;
  const timestampMs = parseInt(timestamp, 10) * 1e3;
  if (isNaN(timestampMs)) return false;
  return Math.abs(Date.now() - timestampMs) <= REPLAY_WINDOW_MS;
}
function verifyHmacSignature(payload, signature, timestamp, secret) {
  if (!signature || !timestamp || !secret) return false;
  if (!isTimestampFresh(timestamp)) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${payload}`).digest("hex");
  const received = signature.startsWith("sha256=") ? signature.slice("sha256=".length) : signature;
  try {
    if (received.length !== expected.length) return false;
    return timingSafeEqual(
      Buffer.from(received, "hex"),
      Buffer.from(expected, "hex")
    );
  } catch {
    return false;
  }
}
function verifyHmacAgainstAny(payload, signature, timestamp, secrets) {
  for (const secret of secrets) {
    if (secret && verifyHmacSignature(payload, signature, timestamp, secret)) return true;
  }
  return false;
}
function secretFingerprint(secret) {
  return createHmac("sha256", "inbox_ops:rate_limit:salt").update(secret).digest("hex").slice(0, 32);
}
function verifySvixSignature(rawBody, headers, secret) {
  try {
    const wh = new Webhook(secret);
    wh.verify(rawBody, {
      "svix-id": headers["svix-id"],
      "svix-timestamp": headers["svix-timestamp"],
      "svix-signature": headers["svix-signature"]
    });
    return true;
  } catch {
    return false;
  }
}
async function fetchResendEmail(emailId) {
  const apiKey = process.env.RESEND_API_KEY;
  if (!apiKey) throw new Error("RESEND_API_KEY is required to fetch Resend inbound emails");
  const resend = new Resend(apiKey);
  const { data, error } = await resend.emails.receiving.get(emailId);
  if (error || !data) {
    throw new Error(`Failed to fetch Resend email ${emailId}: ${error?.message || "unknown error"}`);
  }
  return {
    from: data.from,
    to: data.to,
    subject: data.subject,
    text: data.text ?? void 0,
    html: data.html ?? void 0,
    messageId: data.message_id,
    replyTo: data.reply_to?.[0] ?? void 0,
    inReplyTo: data.headers?.["in-reply-to"] ?? void 0,
    references: data.headers?.["references"]?.split(/\s+/).filter(Boolean)
  };
}
async function verifyAndParse(req, rawBody) {
  const customSig = req.headers.get("x-webhook-signature");
  const svixId = req.headers.get("svix-id");
  if (customSig) {
    const webhookSecret = process.env.INBOX_OPS_WEBHOOK_SECRET;
    if (!webhookSecret) {
      logger.error("INBOX_OPS_WEBHOOK_SECRET not configured");
      return { ok: false, response: NextResponse.json({ error: "Service unavailable" }, { status: 503 }) };
    }
    const timestamp = req.headers.get("x-webhook-timestamp") || "";
    if (!isTimestampFresh(timestamp)) {
      return { ok: false, response: NextResponse.json({ error: "Invalid signature" }, { status: 400 }) };
    }
    let payload;
    try {
      payload = JSON.parse(rawBody);
    } catch {
      return { ok: false, response: NextResponse.json({ error: "Invalid JSON" }, { status: 400 }) };
    }
    return { ok: true, data: { kind: "custom", payload, signature: customSig, timestamp } };
  }
  if (svixId) {
    const signingSecret = process.env.RESEND_WEBHOOK_SIGNING_SECRET;
    if (!signingSecret) {
      logger.error("RESEND_WEBHOOK_SIGNING_SECRET not configured");
      return { ok: false, response: NextResponse.json({ error: "Service unavailable" }, { status: 503 }) };
    }
    const headers = {
      "svix-id": svixId,
      "svix-timestamp": req.headers.get("svix-timestamp") || "",
      "svix-signature": req.headers.get("svix-signature") || ""
    };
    if (!verifySvixSignature(rawBody, headers, signingSecret)) {
      return { ok: false, response: NextResponse.json({ error: "Invalid signature" }, { status: 400 }) };
    }
    let envelope;
    try {
      envelope = JSON.parse(rawBody);
    } catch {
      return { ok: false, response: NextResponse.json({ error: "Invalid JSON" }, { status: 400 }) };
    }
    if (envelope.type !== "email.received") {
      return { ok: false, response: NextResponse.json({ ok: true }) };
    }
    const emailId = envelope.data?.email_id;
    if (!emailId) {
      return { ok: false, response: NextResponse.json({ error: "Missing email_id in Resend payload" }, { status: 400 }) };
    }
    const emailFields = await fetchResendEmail(emailId);
    const to = emailFields.to || envelope.data?.to || [];
    return { ok: true, data: { kind: "resend", emailFields, to } };
  }
  return { ok: false, response: NextResponse.json({ error: "Missing signature headers" }, { status: 400 }) };
}
async function POST(req) {
  const hasCustomSecret = Boolean(process.env.INBOX_OPS_WEBHOOK_SECRET);
  const hasResendSecret = Boolean(process.env.RESEND_WEBHOOK_SIGNING_SECRET);
  if (!hasCustomSecret && !hasResendSecret) {
    logger.error("Neither INBOX_OPS_WEBHOOK_SECRET nor RESEND_WEBHOOK_SIGNING_SECRET is configured");
    return NextResponse.json({ error: "Service unavailable" }, { status: 503 });
  }
  const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
  if (contentLength > MAX_PAYLOAD_SIZE) {
    return NextResponse.json({ error: "Payload too large" }, { status: 413 });
  }
  let rawBody;
  try {
    rawBody = await req.text();
  } catch {
    return NextResponse.json({ error: "Bad request" }, { status: 400 });
  }
  if (rawBody.length > MAX_PAYLOAD_SIZE) {
    return NextResponse.json({ error: "Payload too large" }, { status: 413 });
  }
  const verified = await verifyAndParse(req, rawBody);
  if (!verified.ok) {
    return verified.response;
  }
  let toAddress;
  let emailInput;
  if (verified.data.kind === "custom") {
    const payload = verified.data.payload;
    toAddress = extractToAddress(payload);
    emailInput = {
      from: payload.from,
      to: payload.to,
      subject: payload.subject,
      text: payload.text,
      html: payload.html,
      messageId: payload.messageId,
      replyTo: payload.replyTo,
      inReplyTo: payload.inReplyTo,
      references: payload.references
    };
  } else {
    const { emailFields, to } = verified.data;
    toAddress = to.length > 0 ? to[0].toLowerCase() : null;
    emailInput = {
      from: emailFields.from,
      to: emailFields.to,
      subject: emailFields.subject,
      text: emailFields.text,
      html: emailFields.html,
      messageId: emailFields.messageId,
      replyTo: emailFields.replyTo,
      inReplyTo: emailFields.inReplyTo,
      references: emailFields.references
    };
  }
  if (!toAddress) {
    return NextResponse.json({ error: "Missing recipient address" }, { status: 400 });
  }
  const container = await createRequestContainer();
  let cache = null;
  try {
    cache = container.resolve("cache");
  } catch {
  }
  const globalBucketKey = verified.data.kind === "custom" ? `webhook:secret:${secretFingerprint(process.env.INBOX_OPS_WEBHOOK_SECRET || "")}` : "webhook:resend";
  const globalRateCheck = await checkRateLimit(cache, globalBucketKey);
  if (!globalRateCheck.allowed) {
    return rateLimitedResponse(globalRateCheck.retryAfterSeconds);
  }
  const em = container.resolve("em").fork();
  const settings = await findOneWithDecryption(
    em,
    InboxSettings,
    {
      inboxAddress: toAddress.toLowerCase(),
      isActive: true,
      deletedAt: null
    }
  );
  if (verified.data.kind === "custom") {
    const globalSecret = process.env.INBOX_OPS_WEBHOOK_SECRET || "";
    const inboxSecret = settings?.webhookSecret || "";
    const acceptableSecrets = inboxSecret ? [inboxSecret] : [globalSecret];
    const signatureValid = verifyHmacAgainstAny(
      rawBody,
      verified.data.signature,
      verified.data.timestamp,
      acceptableSecrets
    );
    if (!signatureValid) {
      return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
    }
  }
  if (!settings) {
    return NextResponse.json({ ok: true });
  }
  const tenantRateCheck = await checkRateLimit(
    cache,
    `webhook:tenant:${settings.tenantId}`,
    settings.tenantId
  );
  if (!tenantRateCheck.allowed) {
    return rateLimitedResponse(tenantRateCheck.retryAfterSeconds);
  }
  const parsed = parseInboundEmail(emailInput);
  const isDuplicate = await checkDuplicate(em, settings, parsed.messageId, parsed.contentHash);
  if (isDuplicate) {
    try {
      await emitInboxOpsEvent("inbox_ops.email.deduplicated", {
        tenantId: settings.tenantId,
        organizationId: settings.organizationId,
        toAddress
      });
    } catch (eventError) {
      logger.error("Failed to emit deduplicated event", { err: eventError });
    }
    return NextResponse.json({ ok: true });
  }
  const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || "204800", 10);
  const cleanedText = parsed.cleanedText.slice(0, maxTextSize);
  const email = em.create(InboxEmail, {
    messageId: parsed.messageId,
    contentHash: parsed.contentHash,
    forwardedByAddress: parsed.from.email,
    forwardedByName: parsed.from.name || null,
    toAddress,
    subject: parsed.subject,
    replyTo: parsed.replyTo,
    inReplyTo: parsed.inReplyTo,
    emailReferences: parsed.references,
    rawText: parsed.rawText,
    rawHtml: parsed.rawHtml,
    cleanedText,
    threadMessages: parsed.threadMessages,
    detectedLanguage: parsed.detectedLanguage,
    receivedAt: /* @__PURE__ */ new Date(),
    status: "received",
    isActive: true,
    organizationId: settings.organizationId,
    tenantId: settings.tenantId
  });
  em.persist(email);
  await em.flush();
  try {
    await emitInboxOpsEvent("inbox_ops.email.received", {
      emailId: email.id,
      tenantId: settings.tenantId,
      organizationId: settings.organizationId,
      forwardedByAddress: parsed.from.email,
      subject: parsed.subject
    });
  } catch (eventError) {
    logger.error("Failed to emit email.received event", { err: eventError });
  }
  return NextResponse.json({ ok: true });
}
function rateLimitedResponse(retryAfterSeconds) {
  return NextResponse.json(
    { error: "Rate limit exceeded" },
    {
      status: 429,
      headers: retryAfterSeconds ? { "Retry-After": String(retryAfterSeconds) } : void 0
    }
  );
}
function extractToAddress(payload) {
  const to = payload.to;
  if (typeof to === "string") {
    const match = to.match(/<([^>]+)>/);
    return match ? match[1].toLowerCase() : to.trim().toLowerCase();
  }
  if (Array.isArray(to) && to.length > 0) {
    const first = to[0];
    if (typeof first === "string") {
      const match = first.match(/<([^>]+)>/);
      return match ? match[1].toLowerCase() : first.trim().toLowerCase();
    }
  }
  return null;
}
async function checkDuplicate(em, settings, messageId, contentHash) {
  const encScope = {
    tenantId: settings.tenantId,
    organizationId: settings.organizationId
  };
  const whereBase = {
    organizationId: settings.organizationId,
    tenantId: settings.tenantId,
    deletedAt: null
  };
  if (messageId) {
    const byMessageId = await findOneWithDecryption(em, InboxEmail, { ...whereBase, messageId }, void 0, encScope);
    if (byMessageId) return true;
  }
  if (contentHash) {
    const byHash = await findOneWithDecryption(em, InboxEmail, { ...whereBase, contentHash }, void 0, encScope);
    if (byHash) return true;
  }
  return false;
}
const openApi = {
  tag: "InboxOps",
  summary: "Inbound email webhook",
  methods: {
    POST: {
      summary: "Receive forwarded email from provider webhook",
      description: "Public endpoint \u2014 validated by provider HMAC signature or Resend/Svix signature. Rate limited per tenant.",
      requestBody: {
        contentType: "application/json",
        schema: z.object({
          from: z.string().optional(),
          to: z.union([z.string(), z.array(z.string())]).optional(),
          subject: z.string().optional(),
          text: z.string().optional(),
          html: z.string().optional(),
          messageId: z.string().optional(),
          replyTo: z.string().optional(),
          inReplyTo: z.string().optional(),
          references: z.union([z.string(), z.array(z.string())]).optional()
        })
      },
      responses: [
        { status: 200, description: "Email received and queued for processing" },
        { status: 400, description: "Invalid payload or signature" },
        { status: 413, description: "Payload too large" },
        { status: 429, description: "Rate limit exceeded" },
        { status: 503, description: "Webhook secret not configured" }
      ]
    }
  }
};
export {
  POST,
  metadata,
  openApi
};
//# sourceMappingURL=inbound.js.map
