import { createHash, timingSafeEqual } from "node:crypto";
import type { RowDataPacket } from "mysql2/promise";

import { ensureOfficialMailSchema, getDbPool } from "@/lib/db";
import { hasActiveGrowthPlanByOwnerEmail } from "@/lib/mail-billing-access";
import { getMailcowInboundRoutingInventory } from "@/lib/mailcow";
import {
  evaluateInboundMessageSize,
  FREE_INCOMING_MESSAGE_LIMIT_BYTES,
  resolveInboundRecipientOwners,
  type InboundRecipientPlan,
} from "@/lib/mail-attachment-policy";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

type MailboxPlanRow = RowDataPacket & {
  address: string;
  owner_user_id: number;
};
type ManagedDomainRow = RowDataPacket & { domain: string };
type OwnerEmailRow = RowDataPacket & { id: number; email: string };

function authorized(header: string | null) {
  const expected = process.env.MAIL_INBOUND_POLICY_TOKEN?.trim();
  if (!expected || expected.length < 32 || !header?.startsWith("Bearer ")) return false;
  const supplied = header.slice("Bearer ".length).trim();
  const expectedHash = createHash("sha256").update(expected).digest();
  const suppliedHash = createHash("sha256").update(supplied).digest();
  return timingSafeEqual(expectedHash, suppliedHash);
}

function parseRecipients(value: unknown) {
  if (!Array.isArray(value) || value.length < 1 || value.length > 100) return null;
  const addresses = value.map((item) => String(item).trim().toLowerCase());
  if (addresses.some((address) =>
    address.length > 320 || !/^[^\s@]+@[^\s@]+$/.test(address))) return null;
  return [...new Set(addresses)];
}

export async function POST(request: Request) {
  if ((process.env.MAIL_INBOUND_POLICY_TOKEN?.trim().length ?? 0) < 32) {
    return Response.json({ error: "policy-not-configured" }, { status: 503 });
  }
  if (!authorized(request.headers.get("authorization"))) {
    return Response.json({ error: "unauthorized" }, { status: 401 });
  }

  let rawBody: string;
  try {
    rawBody = await request.text();
  } catch {
    return Response.json({ error: "invalid-policy-input" }, { status: 400 });
  }
  if (Buffer.byteLength(rawBody) > 8192) {
    return Response.json({ error: "request-too-large" }, { status: 413 });
  }
  let body: { recipients?: unknown; sizeBytes?: unknown } | null;
  try {
    body = JSON.parse(rawBody || "null") as typeof body;
  } catch {
    return Response.json({ error: "invalid-policy-input" }, { status: 400 });
  }
  const recipients = parseRecipients(body?.recipients);
  const sizeBytes = body?.sizeBytes;
  if (!recipients || typeof sizeBytes !== "number" || !Number.isSafeInteger(sizeBytes) || sizeBytes < 0) {
    return Response.json({ error: "invalid-policy-input" }, { status: 400 });
  }
  if (sizeBytes <= FREE_INCOMING_MESSAGE_LIMIT_BYTES) {
    return Response.json({ action: "accept", limitBytes: null, recipient: null });
  }

  try {
    await ensureOfficialMailSchema();
    const pool = getDbPool();
    const [mailboxRows] = await pool.query<MailboxPlanRow[]>(
      `SELECT LOWER(m.email) AS address,
         COALESCE(team.owner_user_id, m.user_id) AS owner_user_id
       FROM mailboxes m
       LEFT JOIN managed_team_mailboxes team
         ON LOWER(team.email) = LOWER(m.email) AND team.status = 'active'
       WHERE LOWER(m.email) IN (${recipients.map(() => "?").join(", ")})
         AND m.status = 'active'`,
      recipients,
    );
    const domains = [...new Set(recipients.map((address) => address.slice(address.lastIndexOf("@") + 1)))];
    const [domainRows] = await pool.query<ManagedDomainRow[]>(
      `SELECT LOWER(domain) AS domain FROM domains
       WHERE LOWER(domain) IN (${domains.map(() => "?").join(", ")})
         AND mailcow_cleanup_at IS NULL`,
      domains,
    );
    const managedDomains = new Set(domainRows.map((row) => row.domain));
    const mailboxOwners = new Map(mailboxRows.map((row) => [row.address, row.owner_user_id]));
    const unknownRecipients = recipients.filter((address) => !mailboxOwners.has(address));
    const routing = unknownRecipients.length > 0
      ? await getMailcowInboundRoutingInventory()
      : null;
    const domainLookup = new Map<string, boolean>(domains.map((domain) => [domain, managedDomains.has(domain)]));
    const mailboxLookup = new Map<string, number | null>(mailboxOwners);
    const resolvedOwners = new Map<string, number[]>();

    for (const address of recipients) {
      const directOwner = mailboxOwners.get(address);
      if (directOwner !== undefined) {
        resolvedOwners.set(address, [directOwner]);
        continue;
      }
      if (!routing) throw new Error("inbound-recipient-routing-unavailable");
      resolvedOwners.set(address, await resolveInboundRecipientOwners({
        address,
        aliases: routing.aliases,
        aliasDomains: routing.aliasDomains,
        isManagedDomain: async (domain) => {
          if (domainLookup.has(domain)) return domainLookup.get(domain) === true;
          const [rows] = await pool.query<ManagedDomainRow[]>(
            `SELECT LOWER(domain) AS domain FROM domains
             WHERE LOWER(domain) = ? AND mailcow_cleanup_at IS NULL LIMIT 1`,
            [domain],
          );
          const managed = rows.length > 0;
          domainLookup.set(domain, managed);
          return managed;
        },
        lookupMailboxOwner: async (targetAddress) => {
          if (mailboxLookup.has(targetAddress)) return mailboxLookup.get(targetAddress) ?? null;
          const [rows] = await pool.query<MailboxPlanRow[]>(
            `SELECT LOWER(m.email) AS address,
               COALESCE(team.owner_user_id, m.user_id) AS owner_user_id
             FROM mailboxes m
             LEFT JOIN managed_team_mailboxes team
               ON LOWER(team.email) = LOWER(m.email) AND team.status = 'active'
             WHERE LOWER(m.email) = ? AND m.status = 'active' LIMIT 1`,
            [targetAddress],
          );
          const ownerId = rows[0]?.owner_user_id ?? null;
          mailboxLookup.set(targetAddress, ownerId);
          return ownerId;
        },
      }));
    }

    const ownerIds = [...new Set([...resolvedOwners.values()].flat())];
    const [ownerRows] = ownerIds.length > 0
      ? await pool.query<OwnerEmailRow[]>(
        `SELECT id, email FROM users WHERE id IN (${ownerIds.map(() => "?").join(", ")})`,
        ownerIds,
      )
      : [[] as OwnerEmailRow[]];
    const ownerEmails = new Map(ownerRows.map((row) => [row.id, row.email]));
    const tiers = new Map<number, InboundRecipientPlan["tier"]>();
    await Promise.all(ownerIds.map(async (ownerId) => {
      const ownerEmail = ownerEmails.get(ownerId);
      if (!ownerEmail) throw new Error("inbound-owner-missing");
      tiers.set(ownerId, (await hasActiveGrowthPlanByOwnerEmail(ownerEmail)) ? "growth" : "free");
    }));
    const plans: InboundRecipientPlan[] = [];

    for (const address of recipients) {
      for (const ownerUserId of resolvedOwners.get(address) ?? []) {
        plans.push({ address, tier: tiers.get(ownerUserId) ?? "free" });
      }
    }

    return Response.json(evaluateInboundMessageSize({ recipients: plans, sizeBytes }), {
      headers: { "Cache-Control": "no-store" },
    });
  } catch (error) {
    console.error("[inbound-size-policy] lookup failed", error instanceof Error ? error.message : error);
    return Response.json({ error: "policy-unavailable" }, { status: 503 });
  }
}
