import "server-only";
import http from "node:http";
import https from "node:https";

type MailcowJson =
  | Record<string, unknown>
  | Array<Record<string, unknown> | string | number | boolean | null>
  | string
  | number
  | boolean
  | null;

export type MailcowProvisioningResult = {
  dkimPublicKey: string;
  dkimSelector: string;
};

type MailcowConfig = {
  apiKey: string;
  allowSelfSigned: boolean;
  baseUrl: string;
  businessMailboxQuotaMb: number;
  domainAliasLimit: number;
  domainMailboxLimit: number;
  domainMaxMailboxQuotaMb: number;
  domainQuotaMb: number;
  dkimKeySize: number;
  dkimSelector: string;
  freeMailboxQuotaMb: number;
  growthMailboxQuotaMb: number;
  requestTimeoutMs: number;
};

type MailcowDomainRecord = {
  active?: string | number | boolean;
  def_new_mailbox_quota?: string | number | null;
  def_quota_for_mbox?: string | number | null;
  domain_name?: string;
  max_new_mailbox_quota?: string | number | null;
  max_quota_for_mbox?: string | number | null;
};

type MailcowMailboxProtocolAttributes = {
  dav_access?: string | number | boolean;
  eas_access?: string | number | boolean;
  imap_access?: string | number | boolean;
  pop3_access?: string | number | boolean;
  sieve_access?: string | number | boolean;
  smtp_access?: string | number | boolean;
};

type MailcowMailboxRecord = MailcowMailboxProtocolAttributes & {
  active?: string | number | boolean;
  attributes?: MailcowMailboxProtocolAttributes;
  percent_in_use?: string | number | null;
  quota?: string | number | null;
  quota_used?: string | number | null;
  username?: string;
};

type MailcowAppPasswordRecord = {
  active?: string | number | boolean;
  dav_access?: string | number | boolean;
  eas_access?: string | number | boolean;
  id?: string | number;
  imap_access?: string | number | boolean;
  mailbox?: string;
  name?: string;
  pop3_access?: string | number | boolean;
  sieve_access?: string | number | boolean;
  smtp_access?: string | number | boolean;
};

type MailcowMailboxProtocol = "dav" | "eas" | "imap" | "pop3" | "sieve" | "smtp";

const MAILCOW_MAILBOX_PROTOCOL_FIELDS = [
  ["imap", "imap_access"],
  ["pop3", "pop3_access"],
  ["smtp", "smtp_access"],
  ["sieve", "sieve_access"],
  ["eas", "eas_access"],
  ["dav", "dav_access"],
] as const satisfies ReadonlyArray<readonly [MailcowMailboxProtocol, keyof MailcowMailboxProtocolAttributes]>;

const OFFICIAL_MAIL_POP3_APP_PASSWORD_NAME = "OfficialMail POP3 only";
const OFFICIAL_MAIL_NATIVE_APP_PASSWORD_NAME = "OfficialMail native app";
const OFFICIAL_MAIL_EXTERNAL_IMAP_PASSWORD_NAME = "OfficialMail external IMAP";

type MailcowAliasRecord = {
  active?: string | number | boolean;
  address?: string;
  goto?: string;
  id: number | string;
};

export type MailcowInboundAlias = {
  active: boolean;
  address: string;
  goto: string;
};

export type MailcowInboundAliasDomain = {
  active: boolean;
  aliasDomain: string;
  targetDomain: string;
};

type MailcowDkimRecord = {
  dkim_selector?: string;
  pubkey?: string;
};

export type MailcowMailboxQuotaSnapshot = {
  isUnlimited: boolean;
  limitBytes: number | null;
  usagePercent: number | null;
  usedBytes: number | null;
};

export type MailcowProvisioningInventory = {
  domainNames: string[];
  mailboxEmails: string[];
};

export type MailcowPlanQuotaConfig = {
  businessMailboxQuotaMb: number;
  domainMaxMailboxQuotaMb: number;
  freeMailboxQuotaMb: number;
  growthMailboxQuotaMb: number;
};

export type MailcowQuotaUpdateResult = "missing" | "unchanged" | "updated";

export type MailcowMailboxRenameResult = {
  alreadyRenamed: boolean;
};

export type MailcowPasswordPolicy = {
  minLength: number;
  requiresLetters: boolean;
  requiresLowerAndUpper: boolean;
  requiresNumbers: boolean;
  requiresSpecialCharacters: boolean;
};

export type MailcowPostfixLogEntry = {
  message: string;
  time: number | string | null;
};

// Mailcow ignores quota=0 on domain edits, so use a high cap for app-level "no domain limit".
const UNLIMITED_DOMAIN_QUOTA_MB = 10 * 1024 * 1024;
const UNLIMITED_DOMAIN_MAILBOX_LIMIT = 10_000;
const DKIM_GENERATION_TIMEOUT_MS = 120_000;

function readRequiredEnv(name: string, fallback?: string) {
  const value = (process.env[name] ?? fallback ?? "").trim();

  if (!value) {
    throw new Error("mailcow-config-missing");
  }

  return value;
}

function readIntegerEnv(name: string, fallback: number) {
  const rawValue = process.env[name];

  if (!rawValue) {
    return fallback;
  }

  const parsed = Number(rawValue);

  if (!Number.isInteger(parsed) || parsed <= 0) {
    throw new Error("mailcow-config-invalid");
  }

  return parsed;
}

function readNonNegativeIntegerEnv(name: string, fallback: number) {
  const rawValue = process.env[name];

  if (!rawValue) {
    return fallback;
  }

  const parsed = Number(rawValue);

  if (!Number.isInteger(parsed) || parsed < 0) {
    throw new Error("mailcow-config-invalid");
  }

  return parsed;
}

function readBooleanEnv(name: string, fallback = false) {
  const rawValue = process.env[name];

  if (!rawValue) {
    return fallback;
  }

  switch (rawValue.trim().toLowerCase()) {
    case "1":
    case "true":
    case "yes":
    case "on":
      return true;
    case "0":
    case "false":
    case "no":
    case "off":
      return false;
    default:
      throw new Error("mailcow-config-invalid");
  }
}

function getMailcowConfig(): MailcowConfig {
  const legacyMailboxQuotaMb = readIntegerEnv("MAILCOW_MAILBOX_QUOTA_MB", 2048);
  const freeMailboxQuotaMb = readIntegerEnv(
    "MAILCOW_FREE_MAILBOX_QUOTA_MB",
    legacyMailboxQuotaMb,
  );
  const growthMailboxQuotaMb = readIntegerEnv(
    "MAILCOW_GROWTH_MAILBOX_QUOTA_MB",
    10 * 1024,
  );
  const businessMailboxQuotaMb = readIntegerEnv(
    "MAILCOW_BUSINESS_MAILBOX_QUOTA_MB",
    100 * 1024,
  );
  const largestPlanQuotaMb = Math.max(
    freeMailboxQuotaMb,
    growthMailboxQuotaMb,
    businessMailboxQuotaMb,
  );
  const domainMaxMailboxQuotaMb = readIntegerEnv(
    "MAILCOW_DOMAIN_MAX_MAILBOX_QUOTA_MB",
    largestPlanQuotaMb,
  );

  if (domainMaxMailboxQuotaMb < largestPlanQuotaMb) {
    throw new Error("mailcow-domain-max-mailbox-quota-too-low");
  }

  const configuredDomainMailboxLimit = readNonNegativeIntegerEnv(
    "MAILCOW_DOMAIN_MAILBOX_LIMIT",
    0,
  );
  const domainMailboxLimit =
    configuredDomainMailboxLimit === 0
      ? UNLIMITED_DOMAIN_MAILBOX_LIMIT
      : configuredDomainMailboxLimit;
  const configuredDomainQuotaMb = readNonNegativeIntegerEnv("MAILCOW_DOMAIN_QUOTA_MB", 0);
  const domainQuotaMb =
    configuredDomainQuotaMb === 0
      ? UNLIMITED_DOMAIN_QUOTA_MB
      : configuredDomainQuotaMb;

  return {
    apiKey: readRequiredEnv("MAILCOW_API_KEY"),
    allowSelfSigned: readBooleanEnv("MAILCOW_ALLOW_SELF_SIGNED", false),
    baseUrl: readRequiredEnv("MAILCOW_BASE_URL", process.env.NEXT_PUBLIC_MAIL_APP_URL),
    businessMailboxQuotaMb,
    domainAliasLimit: readIntegerEnv("MAILCOW_DOMAIN_ALIAS_LIMIT", 400),
    domainMailboxLimit,
    domainMaxMailboxQuotaMb,
    domainQuotaMb,
    dkimKeySize: readIntegerEnv("MAILCOW_DKIM_KEY_SIZE", 2048),
    dkimSelector: readRequiredEnv("DKIM_SELECTOR", "dkim"),
    freeMailboxQuotaMb,
    growthMailboxQuotaMb,
    requestTimeoutMs: readIntegerEnv("MAILCOW_REQUEST_TIMEOUT_MS", 15000),
  };
}

export function getMailcowPlanQuotaConfig(): MailcowPlanQuotaConfig {
  const config = getMailcowConfig();

  return {
    businessMailboxQuotaMb: config.businessMailboxQuotaMb,
    domainMaxMailboxQuotaMb: config.domainMaxMailboxQuotaMb,
    freeMailboxQuotaMb: config.freeMailboxQuotaMb,
    growthMailboxQuotaMb: config.growthMailboxQuotaMb,
  };
}

function normalizeMailcowMessage(payload: MailcowJson) {
  const messages = new Set<string>();

  const collect = (value: unknown) => {
    if (typeof value === "string" && value.trim()) {
      messages.add(value.trim());
      return;
    }

    if (Array.isArray(value)) {
      value.forEach(collect);
      return;
    }

    if (value && typeof value === "object") {
      const record = value as Record<string, unknown>;
      collect(record.msg);
      collect(record.message);
      collect(record.error);
      collect(record.detail);
    }
  };

  collect(payload);

  return [...messages].join(" | ");
}

function payloadHasMutationError(payload: MailcowJson) {
  if (Array.isArray(payload)) {
    return payload.some((item) => {
      if (!item || typeof item !== "object" || Array.isArray(item)) {
        return false;
      }

      const type = item.type;
      return type === "danger" || type === "error";
    });
  }

  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
    return false;
  }

  const type = payload.type;
  return type === "danger" || type === "error";
}

function buildMailcowUrl(pathname: string, baseUrl: string) {
  const url = new URL(baseUrl);
  url.pathname = pathname;
  url.search = "";
  return url;
}

function readOptionalNumber(value: unknown) {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }

  if (typeof value === "string") {
    const normalized = value.trim();

    if (!normalized || normalized === "-") {
      return null;
    }

    const parsed = Number(normalized);
    return Number.isFinite(parsed) ? parsed : null;
  }

  return null;
}

async function performNodeRequest(
  url: URL,
  init: RequestInit,
  config: MailcowConfig,
) {
  const body =
    typeof init.body === "string"
      ? init.body
      : init.body
        ? String(init.body)
        : undefined;
  const headers = Object.fromEntries(new Headers(init.headers).entries());
  const isHttps = url.protocol === "https:";

  return new Promise<{ statusCode: number; body: string }>((resolve, reject) => {
    const request = (isHttps ? https : http).request(
      {
        protocol: url.protocol,
        hostname: url.hostname,
        port: url.port ? Number(url.port) : isHttps ? 443 : 80,
        path: `${url.pathname}${url.search}`,
        method: init.method ?? "GET",
        headers,
        rejectUnauthorized: isHttps ? !config.allowSelfSigned : undefined,
      },
      (response) => {
        let responseBody = "";

        response.setEncoding("utf8");
        response.on("data", (chunk) => {
          responseBody += chunk;
        });
        response.on("end", () => {
          resolve({
            statusCode: response.statusCode ?? 0,
            body: responseBody,
          });
        });
      },
    );

    request.setTimeout(config.requestTimeoutMs, () => {
      request.destroy(new Error("Mailcow request timed out"));
    });
    request.on("error", reject);

    if (body) {
      request.write(body);
    }

    request.end();
  });
}

async function requestMailcow(
  pathname: string,
  init: RequestInit,
  config = getMailcowConfig(),
): Promise<MailcowJson> {
  const headers = new Headers(init.headers);
  headers.set("Accept", "application/json");
  headers.set("X-API-Key", config.apiKey);

  if (init.body) {
    headers.set("Content-Type", "application/json");
  }

  const url = buildMailcowUrl(pathname, config.baseUrl);
  let response: { statusCode: number; body: string };

  try {
    response = await performNodeRequest(url, { ...init, headers }, config);
  } catch (error) {
    const message = error instanceof Error ? error.message : "Mailcow network request failed";
    throw new Error(`mailcow-request-failed:${message}`);
  }

  const text = response.body;
  let payload: MailcowJson = null;

  if (text) {
    try {
      payload = JSON.parse(text) as MailcowJson;
    } catch {
      payload = text;
    }
  }

  if (response.statusCode < 200 || response.statusCode >= 300) {
    const message = normalizeMailcowMessage(payload) || `HTTP ${response.statusCode}` || "Mailcow request failed";

    if (response.statusCode === 401 || response.statusCode === 403) {
      throw new Error("mailcow-auth-failed");
    }

    throw new Error(`mailcow-request-failed:${message}`);
  }

  return payload;
}

function findRecord<T extends Record<string, unknown>>(
  payload: MailcowJson,
  predicate: (record: Record<string, unknown>) => boolean,
) {
  if (Array.isArray(payload)) {
    for (const item of payload) {
      if (!item || typeof item !== "object" || Array.isArray(item)) {
        continue;
      }

      if (predicate(item)) {
        return item as T;
      }
    }

    return null;
  }

  if (payload && typeof payload === "object" && !Array.isArray(payload) && predicate(payload)) {
    return payload as T;
  }

  return null;
}

function collectRecordValues(payload: MailcowJson, key: string) {
  if (!Array.isArray(payload)) {
    return [];
  }

  return payload
    .map((item) => {
      if (!item || typeof item !== "object" || Array.isArray(item)) {
        return "";
      }

      const value = item[key];
      return typeof value === "string" ? value.trim().toLowerCase() : "";
    })
    .filter(Boolean);
}

export async function getMailcowProvisioningInventory() {
  const config = getMailcowConfig();
  const [domains, mailboxes] = await Promise.all([
    requestMailcow("/api/v1/get/domain/all", { method: "GET" }, config),
    requestMailcow("/api/v1/get/mailbox/all", { method: "GET" }, config),
  ]);

  return {
    domainNames: collectRecordValues(domains, "domain_name"),
    mailboxEmails: collectRecordValues(mailboxes, "username"),
  } satisfies MailcowProvisioningInventory;
}

export async function getMailcowPasswordPolicy() {
  const config = getMailcowConfig();
  const payload = await requestMailcow(
    "/api/v1/get/passwordpolicy",
    { method: "GET" },
    config,
  );
  const record = findRecord<Record<string, unknown>>(
    payload,
    (candidate) => "length" in candidate,
  );

  if (!record) {
    throw new Error("mailcow-password-policy-unavailable");
  }

  const minLength = Math.max(1, Math.floor(readOptionalNumber(record.length) ?? 6));

  return {
    minLength,
    requiresLetters: readOptionalNumber(record.chars) === 1,
    requiresLowerAndUpper: readOptionalNumber(record.lowerupper) === 1,
    requiresNumbers: readOptionalNumber(record.numbers) === 1,
    requiresSpecialCharacters: readOptionalNumber(record.special_chars) === 1,
  } satisfies MailcowPasswordPolicy;
}

export async function assertMailcowPasswordPolicy(password: string) {
  const policy = await getMailcowPasswordPolicy();
  const hasLetters = /[a-z]/i.test(password);
  const hasLowerAndUpper = /[a-z]/.test(password) && /[A-Z]/.test(password);
  const hasNumbers = /\d/.test(password);
  const hasSpecialCharacters = /[^a-z\d]/i.test(password);

  if (
    password.length < policy.minLength ||
    (policy.requiresLetters && !hasLetters) ||
    (policy.requiresLowerAndUpper && !hasLowerAndUpper) ||
    (policy.requiresNumbers && !hasNumbers) ||
    (policy.requiresSpecialCharacters && !hasSpecialCharacters)
  ) {
    throw new Error(
      `mailcow-password-policy:${policy.minLength}:${policy.requiresLetters ? 1 : 0}:${policy.requiresLowerAndUpper ? 1 : 0}:${policy.requiresNumbers ? 1 : 0}:${policy.requiresSpecialCharacters ? 1 : 0}`,
    );
  }

  return policy;
}

async function getMailcowDomain(domain: string, config = getMailcowConfig()) {
  const payload = await requestMailcow(`/api/v1/get/domain/${encodeURIComponent(domain)}`, { method: "GET" }, config);

  return findRecord<MailcowDomainRecord>(payload, (record) => record.domain_name === domain);
}

async function getMailcowMailbox(mailboxEmail: string, config = getMailcowConfig()) {
  const payload = await requestMailcow(
    `/api/v1/get/mailbox/${encodeURIComponent(mailboxEmail)}`,
    { method: "GET" },
    config,
  );

  return findRecord<MailcowMailboxRecord>(
    payload,
    (record) =>
      typeof record.username === "string" &&
      record.username.trim().toLowerCase() === mailboxEmail.trim().toLowerCase(),
  );
}

async function getMailcowAppPasswords(mailboxEmail: string, config = getMailcowConfig()) {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();
  const payload = await requestMailcow(
    `/api/v1/get/app-passwd/all/${encodeURIComponent(normalizedMailboxEmail)}`,
    { method: "GET" },
    config,
  );

  if (!Array.isArray(payload)) {
    return [] satisfies MailcowAppPasswordRecord[];
  }

  return payload.filter((item): item is MailcowAppPasswordRecord => {
    if (!item || typeof item !== "object" || Array.isArray(item)) {
      return false;
    }

    const record = item as MailcowAppPasswordRecord;
    return record.mailbox?.trim().toLowerCase() === normalizedMailboxEmail;
  });
}

function isOfficialMailPop3AppPassword(record: MailcowAppPasswordRecord) {
  return record.name?.trim() === OFFICIAL_MAIL_POP3_APP_PASSWORD_NAME;
}

function hasOnlyAppPasswordProtocols(
  record: MailcowAppPasswordRecord,
  protocols: ReadonlyArray<"imap_access" | "smtp_access">,
) {
  const enabled = new Set(protocols);
  return (
    isMailcowAccessEnabled(record.active) &&
    isMailcowAccessEnabled(record.imap_access) === enabled.has("imap_access") &&
    isMailcowAccessEnabled(record.smtp_access) === enabled.has("smtp_access") &&
    !isMailcowAccessEnabled(record.pop3_access) &&
    !isMailcowAccessEnabled(record.sieve_access) &&
    !isMailcowAccessEnabled(record.eas_access) &&
    !isMailcowAccessEnabled(record.dav_access)
  );
}

async function deleteNamedMailcowAppPassword(mailboxEmail: string, name: string) {
  const config = getMailcowConfig();
  const records = await getMailcowAppPasswords(mailboxEmail, config);
  const ids = records
    .filter((record) => record.name?.trim() === name)
    .map((record) => record.id)
    .filter((id): id is string | number => typeof id === "string" || typeof id === "number");
  await deleteMailcowAppPasswordIds(ids, config);
}

async function upsertNamedMailcowAppPassword(
  mailboxEmail: string,
  password: string,
  name: string,
  protocols: ReadonlyArray<"imap_access" | "smtp_access">,
  replace: boolean,
) {
  const config = getMailcowConfig();
  const normalizedEmail = mailboxEmail.trim().toLowerCase();
  if (!normalizedEmail || !password) {
    throw new Error("mailcow-provisioning-failed:App password is required");
  }
  const existing = (await getMailcowAppPasswords(normalizedEmail, config)).filter(
    (record) => record.name?.trim() === name,
  );
  if (
    !replace &&
    existing.length === 1 &&
    hasOnlyAppPasswordProtocols(existing[0], protocols)
  ) {
    return;
  }
  await deleteMailcowAppPasswordIds(
    existing
      .map((record) => record.id)
      .filter((id): id is string | number => typeof id === "string" || typeof id === "number"),
    config,
  );
  const payload = await requestMailcow(
    "/api/v1/add/app-passwd",
    {
      method: "POST",
      body: JSON.stringify({
        active: true,
        username: normalizedEmail,
        app_name: name,
        app_passwd: password,
        app_passwd2: password,
        protocols,
      }),
    },
    config,
  );
  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to create app password in Mailcow"}`,
    );
  }
  const created = (await getMailcowAppPasswords(normalizedEmail, config)).filter(
    (record) => record.name?.trim() === name,
  );
  if (created.length !== 1 || !hasOnlyAppPasswordProtocols(created[0], protocols)) {
    throw new Error("mailcow-provisioning-failed:App password was not applied");
  }
}

export async function ensureMailcowNativeAppPassword(mailboxEmail: string, password: string) {
  await upsertNamedMailcowAppPassword(
    mailboxEmail,
    password,
    OFFICIAL_MAIL_NATIVE_APP_PASSWORD_NAME,
    ["imap_access"],
    false,
  );
}

export async function deleteMailcowNativeAppPassword(mailboxEmail: string) {
  await deleteNamedMailcowAppPassword(mailboxEmail, OFFICIAL_MAIL_NATIVE_APP_PASSWORD_NAME);
}

export async function replaceMailcowExternalImapAppPassword(
  mailboxEmail: string,
  password: string,
  allowSmtp: boolean,
) {
  await upsertNamedMailcowAppPassword(
    mailboxEmail,
    password,
    OFFICIAL_MAIL_EXTERNAL_IMAP_PASSWORD_NAME,
    allowSmtp ? ["imap_access", "smtp_access"] : ["imap_access"],
    true,
  );
}

export async function ensureMailcowExternalImapAppPassword(
  mailboxEmail: string,
  password: string,
  allowSmtp: boolean,
) {
  await upsertNamedMailcowAppPassword(
    mailboxEmail,
    password,
    OFFICIAL_MAIL_EXTERNAL_IMAP_PASSWORD_NAME,
    allowSmtp ? ["imap_access", "smtp_access"] : ["imap_access"],
    false,
  );
}

export async function deleteMailcowExternalImapAppPassword(mailboxEmail: string) {
  await deleteNamedMailcowAppPassword(mailboxEmail, OFFICIAL_MAIL_EXTERNAL_IMAP_PASSWORD_NAME);
}

function isPop3OnlyAppPassword(record: MailcowAppPasswordRecord) {
  return (
    isMailcowAccessEnabled(record.active) &&
    isMailcowAccessEnabled(record.pop3_access) &&
    !isMailcowAccessEnabled(record.imap_access) &&
    !isMailcowAccessEnabled(record.smtp_access) &&
    !isMailcowAccessEnabled(record.sieve_access) &&
    !isMailcowAccessEnabled(record.eas_access) &&
    !isMailcowAccessEnabled(record.dav_access)
  );
}

async function deleteMailcowAppPasswordIds(
  ids: Array<string | number>,
  config = getMailcowConfig(),
) {
  if (ids.length === 0) {
    return;
  }

  const payload = await requestMailcow(
    "/api/v1/delete/app-passwd",
    {
      method: "POST",
      body: JSON.stringify(ids),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to delete POP3 app password in Mailcow"}`,
    );
  }
}

export async function hasMailcowPop3AppPassword(
  mailboxEmail: string,
  config = getMailcowConfig(),
) {
  const appPasswords = await getMailcowAppPasswords(mailboxEmail, config);
  return appPasswords.some(
    (record) => isOfficialMailPop3AppPassword(record) && isPop3OnlyAppPassword(record),
  );
}

export async function deleteMailcowPop3AppPassword(
  mailboxEmail: string,
  config = getMailcowConfig(),
) {
  const appPasswords = await getMailcowAppPasswords(mailboxEmail, config);
  const matchingIds = appPasswords
    .filter(isOfficialMailPop3AppPassword)
    .map((record) => record.id)
    .filter((id): id is string | number => typeof id === "string" || typeof id === "number");

  if (matchingIds.length === 0) {
    return;
  }

  await deleteMailcowAppPasswordIds(matchingIds, config);

  if (await hasMailcowPop3AppPassword(mailboxEmail, config)) {
    throw new Error("mailcow-provisioning-failed:POP3 app password deletion was not applied");
  }
}

export async function replaceMailcowPop3AppPassword(
  mailboxEmail: string,
  password: string,
  config = getMailcowConfig(),
) {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();

  if (!normalizedMailboxEmail || !password) {
    throw new Error("mailcow-provisioning-failed:POP3 app password is required");
  }

  await deleteMailcowPop3AppPassword(normalizedMailboxEmail, config);

  const payload = await requestMailcow(
    "/api/v1/add/app-passwd",
    {
      method: "POST",
      body: JSON.stringify({
        active: true,
        username: normalizedMailboxEmail,
        app_name: OFFICIAL_MAIL_POP3_APP_PASSWORD_NAME,
        app_passwd: password,
        app_passwd2: password,
        protocols: ["pop3_access"],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to create POP3 app password in Mailcow"}`,
    );
  }

  if (!(await hasMailcowPop3AppPassword(normalizedMailboxEmail, config))) {
    throw new Error("mailcow-provisioning-failed:POP3-only app password was not applied");
  }
}

function isMailcowAccessEnabled(value: string | number | boolean | undefined) {
  if (typeof value === "boolean") {
    return value;
  }

  if (typeof value === "number") {
    return value === 1;
  }

  return ["1", "true", "yes", "on"].includes(value?.trim().toLowerCase() ?? "");
}

function getMailcowMailboxProtocols(mailbox: MailcowMailboxRecord) {
  return MAILCOW_MAILBOX_PROTOCOL_FIELDS.flatMap(([protocol, field]) =>
    isMailcowAccessEnabled(mailbox.attributes?.[field] ?? mailbox[field]) ? [protocol] : [],
  );
}

function getMailcowMailboxProtocolValue(
  mailbox: MailcowMailboxRecord,
  field: keyof MailcowMailboxProtocolAttributes,
) {
  return mailbox.attributes?.[field] ?? mailbox[field];
}

async function getMailcowAliasesByAddress(
  aliasAddress: string,
  config = getMailcowConfig(),
) {
  const normalizedAddress = aliasAddress.trim().toLowerCase();
  const payload = await requestMailcow(
    "/api/v1/get/alias/all",
    { method: "GET" },
    config,
  );

  if (!Array.isArray(payload)) {
    return [] satisfies MailcowAliasRecord[];
  }

  return payload.filter((item): item is MailcowAliasRecord => {
    if (!item || typeof item !== "object" || Array.isArray(item)) {
      return false;
    }

    return (
      typeof item.address === "string" &&
      item.address.trim().toLowerCase() === normalizedAddress &&
      (typeof item.id === "string" || typeof item.id === "number")
    );
  });
}

export async function getMailcowInboundRoutingInventory(): Promise<{
  aliases: MailcowInboundAlias[];
  aliasDomains: MailcowInboundAliasDomain[];
}> {
  // Rspamd waits for this lookup during SMTP receipt. Keep Mailcow's own
  // timeout below the milter's five-second policy request timeout.
  const config = { ...getMailcowConfig(), requestTimeoutMs: 2500 };
  const [aliasPayload, domainPayload] = await Promise.all([
    requestMailcow("/api/v1/get/alias/all", { method: "GET" }, config),
    requestMailcow("/api/v1/get/alias-domain/all", { method: "GET" }, config),
  ]);
  const records = (payload: MailcowJson) => {
    if (Array.isArray(payload)) return payload;
    if (payload && typeof payload === "object" && Object.keys(payload).length === 0) return [];
    throw new Error("mailcow-inbound-routing-unavailable");
  };
  const aliases = records(aliasPayload).map((item) => {
    if (!item || typeof item !== "object" || Array.isArray(item) ||
        typeof item.address !== "string" || typeof item.goto !== "string") {
      throw new Error("mailcow-inbound-alias-invalid");
    }
    return {
      active: isMailcowAccessEnabled(item.active as string | number | boolean | undefined),
      address: item.address.trim().toLowerCase(),
      goto: item.goto.trim().toLowerCase(),
    };
  });
  const aliasDomains = records(domainPayload).map((item) => {
    if (!item || typeof item !== "object" || Array.isArray(item) ||
        typeof item.alias_domain !== "string" || typeof item.target_domain !== "string") {
      throw new Error("mailcow-inbound-alias-domain-invalid");
    }
    return {
      active: isMailcowAccessEnabled(item.active as string | number | boolean | undefined),
      aliasDomain: item.alias_domain.trim().toLowerCase(),
      targetDomain: item.target_domain.trim().toLowerCase(),
    };
  });
  return { aliases, aliasDomains };
}

export async function deleteMailcowAliasByAddress(aliasAddress: string) {
  const config = getMailcowConfig();
  const aliases = await getMailcowAliasesByAddress(aliasAddress, config);

  if (aliases.length === 0) {
    return { deletedAliasCount: 0 };
  }

  const aliasIds = aliases.map((alias) => alias.id);
  let deletionError: Error | null = null;

  try {
    const payload = await requestMailcow(
      "/api/v1/delete/alias",
      {
        method: "POST",
        body: JSON.stringify(aliasIds),
      },
      config,
    );

    if (payloadHasMutationError(payload)) {
      deletionError = new Error(
        `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to delete mailbox alias in Mailcow"}`,
      );
    }
  } catch (error) {
    deletionError = error instanceof Error ? error : new Error("mailcow-request-failed");
  }

  const remainingAliases = await getMailcowAliasesByAddress(aliasAddress, config);

  if (remainingAliases.length > 0) {
    throw deletionError ?? new Error("mailcow-mailbox-alias-cleanup-incomplete");
  }

  return { deletedAliasCount: aliases.length };
}

export async function renameMailcowMailbox(input: {
  acceptAlreadyRenamed?: boolean;
  domain: string;
  newLocalPart: string;
  oldLocalPart: string;
}): Promise<MailcowMailboxRenameResult> {
  const config = getMailcowConfig();
  const domain = input.domain.trim().toLowerCase();
  const oldLocalPart = input.oldLocalPart.trim().toLowerCase();
  const newLocalPart = input.newLocalPart.trim().toLowerCase();
  const oldEmail = `${oldLocalPart}@${domain}`;
  const newEmail = `${newLocalPart}@${domain}`;
  const [oldMailbox, newMailbox] = await Promise.all([
    getMailcowMailbox(oldEmail, config),
    getMailcowMailbox(newEmail, config),
  ]);

  if (!oldMailbox) {
    if (newMailbox && input.acceptAlreadyRenamed) {
      try {
        await deleteMailcowAliasByAddress(oldEmail);
      } catch (error) {
        const detail = error instanceof Error ? error.message : "mailcow-mailbox-alias-cleanup-failed";
        throw new Error(`mailcow-mailbox-rename-state-unknown:${detail}`);
      }

      return { alreadyRenamed: true };
    }

    throw new Error(newMailbox ? "mailcow-mailbox-target-exists" : "mailcow-mailbox-source-missing");
  }

  if (newMailbox) {
    throw new Error("mailcow-mailbox-target-exists");
  }

  let payload: MailcowJson = null;
  let requestError: Error | null = null;

  try {
    payload = await requestMailcow(
      "/api/v1/edit/rename-mbox",
      {
        method: "POST",
        body: JSON.stringify({
          attr: {
            create_alias: 0,
            domain,
            new_local_part: newLocalPart,
            old_local_part: oldLocalPart,
          },
          items: [oldEmail],
        }),
      },
      config,
    );
  } catch (error) {
    requestError = error instanceof Error ? error : new Error("mailcow-request-failed");
  }

  let oldMailboxAfter: MailcowMailboxRecord | null;
  let newMailboxAfter: MailcowMailboxRecord | null;

  try {
    [oldMailboxAfter, newMailboxAfter] = await Promise.all([
      getMailcowMailbox(oldEmail, config),
      getMailcowMailbox(newEmail, config),
    ]);
  } catch (error) {
    const detail = error instanceof Error ? error.message : "mailcow-verification-failed";
    throw new Error(`mailcow-mailbox-rename-state-unknown:${detail}`);
  }

  if (!oldMailboxAfter && newMailboxAfter) {
    try {
      await deleteMailcowAliasByAddress(oldEmail);
    } catch (error) {
      const detail = error instanceof Error ? error.message : "mailcow-mailbox-alias-cleanup-failed";
      throw new Error(`mailcow-mailbox-rename-state-unknown:${detail}`);
    }

    return { alreadyRenamed: false };
  }

  if (requestError) {
    throw requestError;
  }

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to rename mailbox in Mailcow"}`,
    );
  }

  throw new Error("mailcow-mailbox-rename-state-unknown:mailcow-mailbox-rename-not-applied");
}

export async function getMailcowMailboxQuota(
  mailboxEmail: string,
  config = getMailcowConfig(),
): Promise<MailcowMailboxQuotaSnapshot | null> {
  const mailbox = await getMailcowMailbox(mailboxEmail, config);

  if (!mailbox) {
    return null;
  }

  const rawLimitBytes = readOptionalNumber(mailbox.quota);
  const usedBytes = readOptionalNumber(mailbox.quota_used);
  const isUnlimited = rawLimitBytes === 0;
  const limitBytes = rawLimitBytes === null || rawLimitBytes === 0 ? null : rawLimitBytes;
  const rawUsagePercent = readOptionalNumber(mailbox.percent_in_use);
  const usagePercent =
    rawUsagePercent !== null
      ? Math.max(0, Math.min(100, rawUsagePercent))
      : usedBytes !== null && limitBytes && limitBytes > 0
        ? Math.max(0, Math.min(100, Math.round((usedBytes / limitBytes) * 100)))
        : null;

  return {
    isUnlimited,
    limitBytes,
    usagePercent,
    usedBytes,
  };
}

export async function getMailcowPostfixLogs(count = 500) {
  const normalizedCount = Math.max(1, Math.min(5000, Math.floor(count)));
  const payload = await requestMailcow(`/api/v1/get/logs/postfix/${normalizedCount}`, { method: "GET" });

  if (!Array.isArray(payload)) {
    return [] satisfies MailcowPostfixLogEntry[];
  }

  return payload
    .map((item) => {
      if (!item || typeof item !== "object" || Array.isArray(item)) {
        return null;
      }

      const message = typeof item.message === "string" ? item.message : "";

      if (!message) {
        return null;
      }

      return {
        message,
        time:
          typeof item.time === "string" || typeof item.time === "number"
            ? item.time
            : null,
      } satisfies MailcowPostfixLogEntry;
    })
    .filter((item): item is MailcowPostfixLogEntry => Boolean(item));
}

async function getMailcowDkim(domain: string, config = getMailcowConfig()) {
  const payload = await requestMailcow(`/api/v1/get/dkim/${encodeURIComponent(domain)}`, { method: "GET" }, config);

  return findRecord<MailcowDkimRecord>(
    payload,
    (record) => typeof record.pubkey === "string" && typeof record.dkim_selector === "string",
  );
}

async function upsertMailcowDomain(
  domain: string,
  allowExistingDomain: boolean,
  mailboxEmail: string,
  config = getMailcowConfig(),
) {
  const existingDomain = await getMailcowDomain(domain, config);
  const createPayload = {
    active: true,
    aliases: config.domainAliasLimit,
    backupmx: false,
    defquota: config.freeMailboxQuotaMb,
    description: `Provisioned by Official Mail (${domain})`,
    domain,
    mailboxes: config.domainMailboxLimit,
    maxquota: config.domainMaxMailboxQuotaMb,
    quota: config.domainQuotaMb,
    relay_all_recipients: false,
    rl_frame: "s",
    rl_value: 10,
    restart_sogo: 1,
  };
  const editPayload = {
    active: true,
    aliases: config.domainAliasLimit,
    backupmx: false,
    defquota: config.freeMailboxQuotaMb,
    description: `Provisioned by Official Mail (${domain})`,
    mailboxes: config.domainMailboxLimit,
    maxquota: config.domainMaxMailboxQuotaMb,
    quota: config.domainQuotaMb,
    relay_all_recipients: false,
  };

  if (!existingDomain) {
    const payload = await requestMailcow(
      "/api/v1/add/domain",
      { method: "POST", body: JSON.stringify(createPayload) },
      config,
    );

    if (payloadHasMutationError(payload) && !(await getMailcowDomain(domain, config))) {
      throw new Error(
        `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to create domain in Mailcow"}`,
      );
    }

    return;
  }

  if (!allowExistingDomain) {
    const existingMailbox = await getMailcowMailbox(mailboxEmail, config);

    if (!existingMailbox) {
      throw new Error("mailcow-domain-exists");
    }
  }

  const payload = await requestMailcow(
    "/api/v1/edit/domain",
    {
      method: "POST",
      body: JSON.stringify({
        attr: editPayload,
        items: [domain],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update domain in Mailcow"}`,
    );
  }
}

export async function ensureMailcowDomain(input: {
  allowExistingDomain: boolean;
  domain: string;
  mailboxEmail: string;
}) {
  const config = getMailcowConfig();

  await upsertMailcowDomain(
    input.domain,
    input.allowExistingDomain,
    input.mailboxEmail,
    config,
  );
}

async function upsertMailcowMailbox(
  input: {
    active?: boolean;
    displayName: string;
    domain: string;
    localPart: string;
    mailboxPassword: string;
    quotaMb?: number;
  },
  config = getMailcowConfig(),
) {
  const mailboxEmail = `${input.localPart}@${input.domain}`;
  const existingMailbox = await getMailcowMailbox(mailboxEmail, config);
  const quotaMb = input.quotaMb ?? config.freeMailboxQuotaMb;
  const createPayload = {
    active: input.active ?? true,
    authsource: "mailcow",
    domain: input.domain,
    force_pw_update: false,
    local_part: input.localPart,
    name: input.displayName,
    password: input.mailboxPassword,
    password2: input.mailboxPassword,
    quota: quotaMb,
    tls_enforce_in: false,
    tls_enforce_out: false,
  };
  const editPayload: Record<string, string | number | boolean> = {
    active: input.active ?? true,
    authsource: "mailcow",
    force_pw_update: false,
    name: input.displayName,
    password: input.mailboxPassword,
    password2: input.mailboxPassword,
  };

  if (input.quotaMb !== undefined) {
    editPayload.quota = input.quotaMb;
  }

  if (!existingMailbox) {
    const payload = await requestMailcow(
      "/api/v1/add/mailbox",
      {
        method: "POST",
        body: JSON.stringify(createPayload),
      },
      config,
    );

    if (payloadHasMutationError(payload) && !(await getMailcowMailbox(mailboxEmail, config))) {
      throw new Error(
        `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to create mailbox in Mailcow"}`,
      );
    }

    return;
  }

  const payload = await requestMailcow(
    "/api/v1/edit/mailbox",
    {
      method: "POST",
      body: JSON.stringify({
        attr: editPayload,
        items: [mailboxEmail],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update mailbox in Mailcow"}`,
    );
  }
}

export async function ensureMailcowMailbox(input: {
  active?: boolean;
  displayName: string;
  domain: string;
  localPart: string;
  mailboxPassword: string;
  quotaMb?: number;
}) {
  const config = getMailcowConfig();

  await upsertMailcowMailbox(input, config);
}

export async function ensureMailcowMailboxAccount(input: {
  displayName: string;
  domain: string;
  localPart: string;
  mailboxPassword: string;
  quotaMb?: number;
}) {
  const config = getMailcowConfig();
  const mailboxEmail = `${input.localPart}@${input.domain}`;

  await upsertMailcowDomain(input.domain, true, mailboxEmail, config);
  await upsertMailcowMailbox(input, config);
}

function quotaMbToBytes(quotaMb: number) {
  return quotaMb * 1024 * 1024;
}

export async function ensureMailcowDomainQuotaLimits(
  domain: string,
  config = getMailcowConfig(),
): Promise<MailcowQuotaUpdateResult> {
  const normalizedDomain = domain.trim().toLowerCase();

  if (!normalizedDomain) {
    throw new Error("mailcow-domain-required");
  }

  const existingDomain = await getMailcowDomain(normalizedDomain, config);

  if (!existingDomain) {
    return "missing";
  }

  const expectedDefaultBytes = quotaMbToBytes(config.freeMailboxQuotaMb);
  const expectedMaximumBytes = quotaMbToBytes(config.domainMaxMailboxQuotaMb);
  const currentDefaultBytes =
    readOptionalNumber(existingDomain.def_new_mailbox_quota) ??
    readOptionalNumber(existingDomain.def_quota_for_mbox);
  const currentMaximumBytes =
    readOptionalNumber(existingDomain.max_new_mailbox_quota) ??
    readOptionalNumber(existingDomain.max_quota_for_mbox);

  if (
    currentDefaultBytes === expectedDefaultBytes &&
    currentMaximumBytes === expectedMaximumBytes
  ) {
    return "unchanged";
  }

  const payload = await requestMailcow(
    "/api/v1/edit/domain",
    {
      method: "POST",
      body: JSON.stringify({
        attr: {
          defquota: config.freeMailboxQuotaMb,
          maxquota: config.domainMaxMailboxQuotaMb,
        },
        items: [normalizedDomain],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update domain quota limits in Mailcow"}`,
    );
  }

  return "updated";
}

export async function setMailcowMailboxQuota(
  mailboxEmail: string,
  quotaMb: number,
  config = getMailcowConfig(),
): Promise<MailcowQuotaUpdateResult> {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();

  if (!normalizedMailboxEmail) {
    throw new Error("mailcow-mailbox-required");
  }

  if (!Number.isInteger(quotaMb) || quotaMb <= 0) {
    throw new Error("mailcow-mailbox-quota-invalid");
  }

  if (quotaMb > config.domainMaxMailboxQuotaMb) {
    throw new Error("mailcow-mailbox-quota-exceeds-domain-maximum");
  }

  const existingMailbox = await getMailcowMailbox(normalizedMailboxEmail, config);

  if (!existingMailbox) {
    return "missing";
  }

  if (readOptionalNumber(existingMailbox.quota) === quotaMbToBytes(quotaMb)) {
    return "unchanged";
  }

  const payload = await requestMailcow(
    "/api/v1/edit/mailbox",
    {
      method: "POST",
      body: JSON.stringify({
        attr: { quota: quotaMb },
        items: [normalizedMailboxEmail],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update mailbox quota in Mailcow"}`,
    );
  }

  return "updated";
}

export async function setMailcowMailboxActive(
  mailboxEmail: string,
  active: boolean,
  config = getMailcowConfig(),
) {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();

  if (!normalizedMailboxEmail) {
    return false;
  }

  const existingMailbox = await getMailcowMailbox(normalizedMailboxEmail, config);

  if (!existingMailbox) {
    return false;
  }

  const payload = await requestMailcow(
    "/api/v1/edit/mailbox",
    {
      method: "POST",
      body: JSON.stringify({
        attr: { active },
        items: [normalizedMailboxEmail],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update mailbox in Mailcow"}`,
    );
  }

  return true;
}

export async function setMailcowMailboxPop3Access(
  mailboxEmail: string,
  enabled: boolean,
  config = getMailcowConfig(),
) {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();

  if (!normalizedMailboxEmail) {
    return false;
  }

  const existingMailbox = await getMailcowMailbox(normalizedMailboxEmail, config);

  if (!existingMailbox) {
    return false;
  }

  const missingCoreProtocolField = ["imap_access", "pop3_access", "smtp_access", "sieve_access"].find(
    (field) => getMailcowMailboxProtocolValue(
      existingMailbox,
      field as keyof MailcowMailboxProtocolAttributes,
    ) === undefined,
  );

  if (missingCoreProtocolField) {
    throw new Error(
      `mailcow-provisioning-failed:Unable to read mailbox protocol access (${missingCoreProtocolField})`,
    );
  }

  if (isMailcowAccessEnabled(getMailcowMailboxProtocolValue(existingMailbox, "pop3_access")) === enabled) {
    return true;
  }

  const protocolAccess = new Set(getMailcowMailboxProtocols(existingMailbox));

  if (enabled) {
    protocolAccess.add("pop3");
  } else {
    protocolAccess.delete("pop3");
  }

  const payload = await requestMailcow(
    "/api/v1/edit/mailbox",
    {
      method: "POST",
      body: JSON.stringify({
        attr: { protocol_access: [...protocolAccess] },
        items: [normalizedMailboxEmail],
      }),
    },
    config,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to update mailbox POP3 access in Mailcow"}`,
    );
  }

  const updatedMailbox = await getMailcowMailbox(normalizedMailboxEmail, config);

  if (
    !updatedMailbox ||
    isMailcowAccessEnabled(getMailcowMailboxProtocolValue(updatedMailbox, "pop3_access")) !== enabled
  ) {
    throw new Error("mailcow-provisioning-failed:Mailbox POP3 access update was not applied");
  }

  return true;
}

export async function deleteMailcowMailboxAccount(
  mailboxEmail: string,
  config = getMailcowConfig(),
) {
  const normalizedMailboxEmail = mailboxEmail.trim().toLowerCase();

  if (!normalizedMailboxEmail) {
    return;
  }

  const existingMailbox = await getMailcowMailbox(normalizedMailboxEmail, config);

  if (!existingMailbox) {
    return;
  }

  const payload = await requestMailcow(
    "/api/v1/delete/mailbox",
    {
      method: "POST",
      body: JSON.stringify([normalizedMailboxEmail]),
    },
    config,
  );

  if (payloadHasMutationError(payload) && (await getMailcowMailbox(normalizedMailboxEmail, config))) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to delete mailbox in Mailcow"}`,
    );
  }
}

export async function deleteMailcowDomain(domain: string) {
  const config = getMailcowConfig();
  const normalizedDomain = domain.trim().toLowerCase();

  if (!normalizedDomain) {
    throw new Error("mailcow-domain-required");
  }

  const mailboxPayload = await requestMailcow(
    "/api/v1/get/mailbox/all",
    { method: "GET" },
    config,
  );
  const domainMailboxes = collectRecordValues(mailboxPayload, "username").filter(
    (username) => username.endsWith(`@${normalizedDomain}`),
  );

  for (let index = 0; index < domainMailboxes.length; index += 100) {
    await requestMailcow(
      "/api/v1/delete/mailbox",
      {
        method: "POST",
        body: JSON.stringify(domainMailboxes.slice(index, index + 100)),
      },
      config,
    );
  }

  if (domainMailboxes.length > 0) {
    const remainingMailboxPayload = await requestMailcow(
      "/api/v1/get/mailbox/all",
      { method: "GET" },
      config,
    );
    const remainingDomainMailboxes = collectRecordValues(
      remainingMailboxPayload,
      "username",
    ).filter((username) => username.endsWith(`@${normalizedDomain}`));

    if (remainingDomainMailboxes.length > 0) {
      throw new Error("mailcow-domain-mailbox-cleanup-incomplete");
    }
  }

  const existingDkim = await getMailcowDkim(normalizedDomain, config);

  if (existingDkim) {
    const dkimPayload = await requestMailcow(
      "/api/v1/delete/dkim",
      {
        method: "POST",
        body: JSON.stringify([normalizedDomain]),
      },
      config,
    );

    if (payloadHasMutationError(dkimPayload) && (await getMailcowDkim(normalizedDomain, config))) {
      throw new Error(
        `mailcow-provisioning-failed:${normalizeMailcowMessage(dkimPayload) || "Unable to delete DKIM in Mailcow"}`,
      );
    }
  }

  const existingDomain = await getMailcowDomain(normalizedDomain, config);

  if (!existingDomain) {
    return { deletedMailboxCount: domainMailboxes.length };
  }

  const payload = await requestMailcow(
    "/api/v1/delete/domain",
    {
      method: "POST",
      body: JSON.stringify([normalizedDomain]),
    },
    config,
  );

  if (payloadHasMutationError(payload) && (await getMailcowDomain(normalizedDomain, config))) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to delete domain in Mailcow"}`,
    );
  }

  return { deletedMailboxCount: domainMailboxes.length };
}

async function ensureMailcowDkim(
  domain: string,
  config = getMailcowConfig(),
  dkimKeySize = config.dkimKeySize,
) {
  const existingDkim = await getMailcowDkim(domain, config);

  if (existingDkim?.pubkey && existingDkim.dkim_selector) {
    return {
      dkimPublicKey: existingDkim.pubkey,
      dkimSelector: existingDkim.dkim_selector,
    } satisfies MailcowProvisioningResult;
  }

  return createMailcowDkim(domain, config, dkimKeySize);
}

async function createMailcowDkim(
  domain: string,
  config: MailcowConfig,
  dkimKeySize: number,
  dkimSelector = config.dkimSelector,
) {
  // RSA key generation can exceed the normal API timeout on a busy Mailcow host.
  const generationConfig = {
    ...config,
    requestTimeoutMs: Math.max(config.requestTimeoutMs, DKIM_GENERATION_TIMEOUT_MS),
  };
  const payload = await requestMailcow(
    "/api/v1/add/dkim",
    {
      method: "POST",
      body: JSON.stringify({
        dkim_selector: dkimSelector,
        domains: domain,
        key_size: dkimKeySize,
      }),
    },
    generationConfig,
  );

  if (payloadHasMutationError(payload)) {
    throw new Error(
      `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to generate DKIM in Mailcow"}`,
    );
  }

  const dkimRecord = await getMailcowDkim(domain, config);

  if (!dkimRecord?.pubkey || !dkimRecord.dkim_selector) {
    throw new Error("mailcow-provisioning-failed:DKIM key not returned by Mailcow");
  }

  return {
    dkimPublicKey: dkimRecord.pubkey,
    dkimSelector: dkimRecord.dkim_selector,
  } satisfies MailcowProvisioningResult;
}

export async function ensureMailcowDkimRecord(domain: string) {
  const config = getMailcowConfig();

  return ensureMailcowDkim(domain, config);
}

export async function regenerateMailcowDkim(
  domain: string,
  dkimKeySize: 1024 | 2048,
) {
  const config = getMailcowConfig();
  const normalizedDomain = domain.trim().toLowerCase();

  if (!normalizedDomain) {
    throw new Error("mailcow-domain-required");
  }

  const existingDkim = await getMailcowDkim(normalizedDomain, config);
  const dkimSelector = existingDkim?.dkim_selector || config.dkimSelector;

  if (existingDkim) {
    const payload = await requestMailcow(
      "/api/v1/delete/dkim",
      {
        method: "POST",
        body: JSON.stringify([normalizedDomain]),
      },
      config,
    );

    if (payloadHasMutationError(payload)) {
      throw new Error(
        `mailcow-provisioning-failed:${normalizeMailcowMessage(payload) || "Unable to replace DKIM in Mailcow"}`,
      );
    }
  }

  // Reissue must create a new key instead of reusing a stale read after deletion.
  return createMailcowDkim(
    normalizedDomain,
    config,
    dkimKeySize,
    dkimSelector,
  );
}

export async function ensureMailcowProvisioning(input: {
  allowExistingDomain: boolean;
  dkimKeySize?: 1024 | 2048;
  displayName: string;
  domain: string;
  localPart: string;
  mailboxPassword: string;
}) {
  const config = getMailcowConfig();
  const mailboxEmail = `${input.localPart}@${input.domain}`;

  await upsertMailcowDomain(input.domain, input.allowExistingDomain, mailboxEmail, config);
  await upsertMailcowMailbox(
    {
      displayName: input.displayName,
      domain: input.domain,
      localPart: input.localPart,
      mailboxPassword: input.mailboxPassword,
    },
    config,
  );

  return ensureMailcowDkim(input.domain, config, input.dkimKeySize);
}

export async function isMailcowProvisioned(domain: string, mailboxEmail: string) {
  const config = getMailcowConfig();
  const [domainRecord, mailboxRecord] = await Promise.all([
    getMailcowDomain(domain, config),
    getMailcowMailbox(mailboxEmail, config),
  ]);

  return Boolean(domainRecord && mailboxRecord);
}
