export const MIB = 1024 * 1024;
export const FREE_REGULAR_ATTACHMENT_LIMIT_BYTES = 25 * MIB;
export const GROWTH_REGULAR_ATTACHMENT_LIMIT_BYTES = 40 * MIB;
export const GROWTH_LARGE_ATTACHMENT_LIMIT_BYTES = 250 * MIB;
export const LARGE_ATTACHMENT_RETENTION_DAYS = 30;
export const LARGE_ATTACHMENT_DOWNLOAD_LIMIT = 100;
export const FREE_INCOMING_MESSAGE_LIMIT_BYTES = 25 * MIB;
export const PAID_INCOMING_MESSAGE_LIMIT_BYTES = 40 * MIB;

export type InboundRecipientPlan = {
  address: string;
  tier: "business" | "free" | "growth";
};

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

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

export async function resolveInboundRecipientOwners(input: {
  address: string;
  aliases: InboundAlias[];
  aliasDomains: InboundAliasDomain[];
  isManagedDomain: (domain: string) => Promise<boolean>;
  lookupMailboxOwner: (address: string) => Promise<number | null>;
}): Promise<number[]> {
  const aliasDomains = new Map(input.aliasDomains
    .filter((item) => item.active)
    .map((item) => [item.aliasDomain, item.targetDomain]));
  const aliases = new Map<string, string[]>();
  for (const alias of input.aliases) {
    if (!alias.active) continue;
    const address = alias.address.trim().toLowerCase();
    aliases.set(address, [...(aliases.get(address) ?? []), ...alias.goto.split(",")]);
  }

  const resolve = async (address: string, seen: Set<string>): Promise<number[]> => {
    const normalized = address.trim().toLowerCase();
    if (normalized === "null@localhost" || normalized === "spam@localhost" ||
        normalized === "ham@localhost") return [];
    if (!/^[^\s@]+@[^\s@]+$/.test(normalized) || seen.has(normalized) || seen.size >= 12) {
      throw new Error("inbound-recipient-routing-unresolved");
    }
    const owner = await input.lookupMailboxOwner(normalized);
    if (owner !== null) return [owner];

    const nextSeen = new Set(seen).add(normalized);
    const at = normalized.lastIndexOf("@");
    const localPart = normalized.slice(0, at);
    const domain = normalized.slice(at + 1);
    const targetDomain = aliasDomains.get(domain);
    if (targetDomain) {
      return resolve(`${localPart}@${targetDomain}`, nextSeen);
    }
    const destinations = aliases.get(normalized) ?? aliases.get(`@${domain}`);
    if (destinations) {
      const owners = await Promise.all(destinations.map((destination) => resolve(destination, nextSeen)));
      return [...new Set(owners.flat())];
    }
    if (await input.isManagedDomain(domain)) {
      throw new Error("inbound-recipient-routing-unresolved");
    }
    return [];
  };

  return resolve(input.address, new Set());
}

export function evaluateInboundMessageSize(input: {
  recipients: InboundRecipientPlan[];
  sizeBytes: number;
}) {
  if (!Number.isSafeInteger(input.sizeBytes) || input.sizeBytes < 0) {
    throw new Error("invalid-inbound-size");
  }

  const restricted = input.recipients.find((recipient) =>
    input.sizeBytes > (recipient.tier === "free"
      ? FREE_INCOMING_MESSAGE_LIMIT_BYTES
      : PAID_INCOMING_MESSAGE_LIMIT_BYTES),
  );

  return restricted
    ? {
        action: "reject" as const,
        limitBytes: restricted.tier === "free"
          ? FREE_INCOMING_MESSAGE_LIMIT_BYTES
          : PAID_INCOMING_MESSAGE_LIMIT_BYTES,
        recipient: restricted.address,
      }
    : { action: "accept" as const, limitBytes: null, recipient: null };
}

export type ComposeAttachmentMode = "regular" | "user_selected_large" | "auto_large";

export function initialAttachmentMode(input: {
  fileSizeBytes: number;
  regularTotalBytes: number;
  growth: boolean;
}): ComposeAttachmentMode {
  const { fileSizeBytes, regularTotalBytes, growth } = input;
  if (!Number.isSafeInteger(fileSizeBytes) || fileSizeBytes < 1) {
    throw new Error("attachment-empty");
  }
  if (!growth) {
    if (regularTotalBytes + fileSizeBytes > FREE_REGULAR_ATTACHMENT_LIMIT_BYTES) {
      throw new Error("attachment-free-limit");
    }
    return "regular";
  }
  if (fileSizeBytes > GROWTH_LARGE_ATTACHMENT_LIMIT_BYTES) {
    throw new Error("attachment-large-limit");
  }
  return regularTotalBytes + fileSizeBytes > GROWTH_REGULAR_ATTACHMENT_LIMIT_BYTES
    ? "auto_large"
    : "regular";
}

export function resolveComposeAttachmentMode(input: {
  fileSizeBytes: number;
  regularTotalBytes: number;
  growth: boolean;
  requestedMode: ComposeAttachmentMode;
}): ComposeAttachmentMode {
  const automaticMode = initialAttachmentMode(input);
  if (!input.growth && input.requestedMode !== "regular") {
    throw new Error("attachment-growth-required");
  }
  if (automaticMode === "auto_large") return "auto_large";
  return input.requestedMode === "regular" ? "regular" : "user_selected_large";
}

export function canRestoreRegularAttachment(input: {
  fileSizeBytes: number;
  regularTotalBytes: number;
  mode: ComposeAttachmentMode;
  finalized: boolean;
}) {
  return input.mode === "user_selected_large" &&
    !input.finalized &&
    input.fileSizeBytes <= GROWTH_REGULAR_ATTACHMENT_LIMIT_BYTES &&
    input.regularTotalBytes + input.fileSizeBytes <= GROWTH_REGULAR_ATTACHMENT_LIMIT_BYTES;
}

export function resolveComposeSubject(subject: string, firstAttachmentFilename?: string | null) {
  const entered = subject.trim();
  return entered || firstAttachmentFilename?.trim() || "";
}

export function resolveComposeSubjectFromKnownAttachments(input: {
  subject: string;
  firstAttachmentFilename?: string | null;
  attachmentFilenames: string[];
}) {
  const filenames = input.attachmentFilenames.filter((filename) => filename.trim());
  const preferred = input.firstAttachmentFilename?.trim();
  const firstFilename = preferred
    ? filenames.find((filename) => filename.trim() === preferred) ?? filenames[0]
    : filenames[0];
  return resolveComposeSubject(input.subject, firstFilename);
}
