import type { Readable } from "node:stream";
import { ImapFlow } from "imapflow";
import type {
  FetchMessageObject,
  ListResponse,
  MessageAddressObject,
  MessageStructureObject,
  SearchObject,
} from "imapflow";
import { convert } from "html-to-text";
import { simpleParser, type AddressObject, type ParsedMail } from "mailparser";
import nodemailer from "nodemailer";
import type { SMTPSentMessageInfo } from "nodemailer";
import type { AppConfig, MailboxConfig } from "../config.js";
import { ActionTokenService } from "./action-token.js";
import { buildRawMessage } from "./mime.js";
import { MessageReferenceCodec, type MessageReference } from "./reference.js";
import type { ComposeMessage, SearchMailInput } from "./schemas.js";

type Address = { name?: string; address: string };

export type AttachmentInfo = {
  part: string;
  filename: string;
  content_type: string;
  size: number | null;
  disposition: string | null;
};

export type EmailSummary = {
  message_ref: string;
  uid: number;
  folder: string;
  subject: string;
  from: Address[];
  to: Address[];
  date: string | null;
  received_at: string | null;
  message_id: string | null;
  size: number | null;
  unread: boolean;
  flagged: boolean;
  has_attachments: boolean;
};

export type EmailDetails = EmailSummary & {
  cc: Address[];
  bcc: Address[];
  reply_to: Address[];
  in_reply_to: string | null;
  references: string[];
  body: string;
  body_format: "plain_text" | "html_converted" | "empty";
  body_truncated: boolean;
  attachments: AttachmentInfo[];
};

function asIso(value: Date | string | undefined): string | null {
  if (!value) return null;
  const date = value instanceof Date ? value : new Date(value);
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
}

function envelopeAddresses(value?: MessageAddressObject[]): Address[] {
  return (value ?? [])
    .filter((item): item is MessageAddressObject & { address: string } => Boolean(item.address))
    .map((item) => ({
      ...(item.name ? { name: item.name } : {}),
      address: item.address,
    }));
}

function parsedAddresses(value?: AddressObject | AddressObject[]): Address[] {
  const objects = value ? (Array.isArray(value) ? value : [value]) : [];
  const flattened = objects.flatMap((object) => object.value.flatMap((item) => item.group ?? [item]));
  return flattened
    .filter((item): item is typeof item & { address: string } => Boolean(item.address))
    .map((item) => ({
      ...(item.name ? { name: item.name } : {}),
      address: item.address,
    }));
}

function attachmentsFromStructure(structure?: MessageStructureObject): AttachmentInfo[] {
  if (!structure) return [];
  const attachments: AttachmentInfo[] = [];

  const visit = (node: MessageStructureObject): void => {
    const filename =
      node.dispositionParameters?.filename ?? node.parameters?.name ?? node.parameters?.filename;
    const disposition = node.disposition?.toLowerCase();
    if (node.part && (disposition === "attachment" || Boolean(filename))) {
      attachments.push({
        part: node.part,
        filename: filename || `attachment-${node.part}`,
        content_type: node.type || "application/octet-stream",
        size: typeof node.size === "number" ? node.size : null,
        disposition: node.disposition ?? null,
      });
    }
    node.childNodes?.forEach(visit);
  };

  visit(structure);
  return attachments;
}

function referencesFromParsed(parsed: ParsedMail): string[] {
  const values = parsed.references
    ? Array.isArray(parsed.references)
      ? parsed.references
      : [parsed.references]
    : [];
  return [...new Set(values.filter(Boolean))];
}

function plainBody(parsed: ParsedMail): { body: string; format: EmailDetails["body_format"] } {
  if (parsed.text?.trim()) return { body: parsed.text.trim(), format: "plain_text" };
  if (typeof parsed.html === "string" && parsed.html.trim()) {
    return {
      body: convert(parsed.html, {
        wordwrap: false,
        selectors: [
          { selector: "img", format: "skip" },
          { selector: "script", format: "skip" },
          { selector: "style", format: "skip" },
        ],
      }).trim(),
      format: "html_converted",
    };
  }
  return { body: "", format: "empty" };
}

async function readStreamWithLimit(stream: Readable, maximumBytes: number): Promise<Buffer> {
  const chunks: Buffer[] = [];
  let total = 0;
  for await (const chunk of stream) {
    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    total += buffer.length;
    if (total > maximumBytes) {
      stream.destroy();
      throw new Error(`Attachment exceeds the ${maximumBytes}-byte safety limit`);
    }
    chunks.push(buffer);
  }
  return Buffer.concat(chunks);
}

function recipientValue(value: unknown): string {
  if (typeof value === "string") return value;
  if (value && typeof value === "object" && "address" in value) {
    return String((value as { address: unknown }).address);
  }
  return String(value);
}

export class MailService {
  readonly references: MessageReferenceCodec;
  readonly actions: ActionTokenService;
  private readonly mailboxMap: Map<string, MailboxConfig>;

  constructor(private readonly config: AppConfig) {
    this.mailboxMap = new Map(config.mailboxes.map((mailbox) => [mailbox.id, mailbox]));
    this.references = new MessageReferenceCodec(config.tokenSecret);
    this.actions = new ActionTokenService(config);
  }

  listMailboxes(): Array<{
    id: string;
    label: string;
    address: string;
    aliases: string[];
    can_create_drafts: boolean;
    can_send: boolean;
  }> {
    return this.config.mailboxes.map((mailbox) => ({
      id: mailbox.id,
      label: mailbox.label,
      address: mailbox.address,
      aliases: mailbox.aliases,
      can_create_drafts: this.config.features.drafts,
      can_send: this.config.features.send && Boolean(mailbox.smtp),
    }));
  }

  async listFolders(mailboxId: string, includeStatus = false): Promise<
    Array<{
      path: string;
      name: string;
      special_use: string | null;
      subscribed: boolean;
      selectable: boolean;
      messages: number | null;
      unseen: number | null;
    }>
  > {
    const mailbox = this.getMailbox(mailboxId);
    return this.withImap(mailbox, async (client) => {
      const folders = await client.list(
        includeStatus ? { statusQuery: { messages: true, unseen: true } } : undefined,
      );
      return folders.map((folder) => ({
        path: folder.path,
        name: folder.name,
        special_use: folder.specialUse ?? null,
        subscribed: folder.subscribed,
        selectable: !folder.flags.has("\\Noselect"),
        messages: folder.status?.messages ?? null,
        unseen: folder.status?.unseen ?? null,
      }));
    });
  }

  async search(input: SearchMailInput): Promise<{
    messages: EmailSummary[];
    next_before_uid: number | null;
    uid_validity: string;
  }> {
    const mailbox = this.getMailbox(input.mailbox_id);
    const limit = Math.min(input.limit, this.config.limits.maxSearchResults);
    return this.withImap(mailbox, async (client) => {
      const lock = await client.getMailboxLock(input.folder, {
        readOnly: true,
        description: "MCP search",
      });
      try {
        const uidValidity = this.currentUidValidity(client);
        const query: SearchObject = {};
        if (input.text) query.text = input.text;
        if (input.from) query.from = input.from;
        if (input.to) query.to = input.to;
        if (input.subject) query.subject = input.subject;
        if (input.since) query.since = input.since;
        if (input.before) query.before = input.before;
        if (input.unread_only) query.seen = false;
        if (input.flagged_only) query.flagged = true;
        if (Object.keys(query).length === 0) query.all = true;

        const result = await client.search(query, { uid: true });
        const matching = Array.isArray(result)
          ? result
              .filter((uid) => !input.before_uid || uid < input.before_uid)
              .sort((left, right) => right - left)
          : [];
        const selected = matching.slice(0, limit);
        if (selected.length === 0) {
          return { messages: [], next_before_uid: null, uid_validity: uidValidity };
        }

        const fetched = await client.fetchAll(
          selected,
          { uid: true, flags: true, envelope: true, internalDate: true, size: true, bodyStructure: true },
          { uid: true },
        );
        fetched.sort((left, right) => right.uid - left.uid);

        const messages = await Promise.all(
          fetched.map((message) =>
            this.toSummary(mailbox, input.folder, uidValidity, message),
          ),
        );
        const hasMore = matching.length > selected.length;
        return {
          messages,
          next_before_uid: hasMore ? (selected.at(-1) ?? null) : null,
          uid_validity: uidValidity,
        };
      } finally {
        lock.release();
      }
    });
  }

  async getEmail(messageRef: string): Promise<EmailDetails> {
    const reference = await this.references.decode(messageRef);
    const mailbox = this.getMailbox(reference.mailbox_id);
    return this.withImap(mailbox, async (client) => {
      const lock = await client.getMailboxLock(reference.folder, {
        readOnly: true,
        description: "MCP read message",
      });
      try {
        this.assertUidValidity(client, reference);
        return await this.fetchDetails(client, mailbox, reference);
      } finally {
        lock.release();
      }
    });
  }

  async getAttachment(messageRef: string, part: string): Promise<{
    filename: string;
    content_type: string;
    size: number;
    content: Buffer;
  }> {
    if (!/^\d+(?:\.\d+)*$/.test(part)) throw new Error("Invalid attachment part identifier");
    const reference = await this.references.decode(messageRef);
    const mailbox = this.getMailbox(reference.mailbox_id);

    return this.withImap(mailbox, async (client) => {
      const lock = await client.getMailboxLock(reference.folder, {
        readOnly: true,
        description: "MCP read attachment",
      });
      try {
        this.assertUidValidity(client, reference);
        const metadata = await client.fetchOne(
          reference.uid,
          { uid: true, bodyStructure: true },
          { uid: true },
        );
        if (!metadata) throw new Error("Email no longer exists");
        const attachment = attachmentsFromStructure(metadata.bodyStructure).find(
          (item) => item.part === part,
        );
        if (!attachment) throw new Error("Attachment not found in this email");
        if (
          attachment.size !== null &&
          attachment.size > this.config.limits.maxAttachmentBytes
        ) {
          throw new Error("Attachment exceeds the configured safety limit");
        }

        const downloaded = await client.download(reference.uid, part, {
          uid: true,
          maxBytes: this.config.limits.maxAttachmentBytes + 1,
        });
        const content = await readStreamWithLimit(
          downloaded.content,
          this.config.limits.maxAttachmentBytes,
        );
        return {
          filename: downloaded.meta.filename || attachment.filename,
          content_type:
            downloaded.meta.contentType || attachment.content_type || "application/octet-stream",
          size: content.length,
          content,
        };
      } finally {
        lock.release();
      }
    });
  }

  async getThread(
    messageRef: string,
    maxMessages: number,
    includeBody: boolean,
  ): Promise<{ messages: EmailDetails[]; heuristic: boolean; warnings: string[] }> {
    const target = await this.getEmail(messageRef);
    const targetReference = await this.references.decode(messageRef);
    const mailbox = this.getMailbox(targetReference.mailbox_id);
    const ids = [...new Set([target.message_id, target.in_reply_to, ...target.references].filter(Boolean))] as string[];
    const heuristic = ids.length === 0;
    const warnings: string[] = [];

    const folders = await this.withImap(mailbox, async (client) => {
      const listed = await client.list();
      const sent = this.resolveSpecialFolder(mailbox, listed, "sent");
      return [...new Set([targetReference.folder, ...(sent ? [sent] : [])])];
    });

    const found: EmailDetails[] = [];
    for (const folder of folders) {
      try {
        const messages = await this.searchThreadFolder(
          mailbox,
          folder,
          ids,
          target.subject,
          Math.min(Math.max(maxMessages, 1), 30),
          includeBody,
        );
        found.push(...messages);
      } catch (error) {
        const reason = error instanceof Error ? error.message : String(error);
        warnings.push(`Could not search ${folder}: ${reason}`);
      }
    }

    const deduplicated = new Map<string, EmailDetails>();
    for (const message of found) {
      const key = message.message_id || message.message_ref;
      if (!deduplicated.has(key)) deduplicated.set(key, message);
    }
    if (![...deduplicated.values()].some((message) => message.message_ref === messageRef)) {
      deduplicated.set(target.message_id || target.message_ref, target);
    }

    const messages = [...deduplicated.values()]
      .sort((left, right) => (left.date ?? left.received_at ?? "").localeCompare(right.date ?? right.received_at ?? ""))
      .slice(-Math.min(Math.max(maxMessages, 1), 30));
    return { messages, heuristic, warnings };
  }

  async createDraft(message: ComposeMessage): Promise<{
    folder: string;
    uid: number | null;
    message_ref: string | null;
    message_id: string;
  }> {
    if (!this.config.features.drafts) throw new Error("Draft creation is disabled");
    const mailbox = this.getMailbox(message.mailbox_id);
    const built = await buildRawMessage(mailbox, message, "draft");

    return this.withImap(mailbox, async (client) => {
      const listed = await client.list();
      const folder = this.resolveSpecialFolder(mailbox, listed, "drafts");
      if (!folder) {
        throw new Error("No Drafts folder was found; configure folders.drafts for this mailbox");
      }
      const appended = await client.append(folder, built.raw, ["\\Draft"], new Date());
      if (!appended) throw new Error("The IMAP server did not accept the draft");

      const uid = appended.uid ?? null;
      const uidValidity = appended.uidValidity?.toString();
      const ref =
        uid && uidValidity
          ? await this.references.encode({
              mailbox_id: mailbox.id,
              folder,
              uid,
              uid_validity: uidValidity,
            })
          : null;
      return { folder, uid, message_ref: ref, message_id: built.messageId };
    });
  }

  async prepareEmail(message: ComposeMessage): Promise<{
    token: string;
    expires_at: string;
    preview: Omit<ComposeMessage, "text"> & { text_preview: string; text_length: number };
  }> {
    const mailbox = this.getMailbox(message.mailbox_id);
    if (!this.config.features.send || !mailbox.smtp) {
      throw new Error("Sending is disabled for this mailbox");
    }
    const prepared = await this.actions.prepare(message);
    return {
      token: prepared.token,
      expires_at: new Date(prepared.expiresAt * 1000).toISOString(),
      preview: {
        mailbox_id: message.mailbox_id,
        to: message.to,
        cc: message.cc,
        bcc: message.bcc,
        subject: message.subject,
        in_reply_to: message.in_reply_to,
        references: message.references,
        text_preview: message.text.slice(0, 2_000),
        text_length: message.text.length,
      },
    };
  }

  async sendPreparedEmail(token: string): Promise<{
    message_id: string;
    accepted: string[];
    rejected: string[];
    sent_copy_saved: boolean;
    warning: string | null;
  }> {
    if (!this.config.features.send) throw new Error("Sending is disabled");
    const prepared = await this.actions.decode(token);
    const mailbox = this.getMailbox(prepared.message.mailbox_id);
    if (!mailbox.smtp || !mailbox.smtpPassword) {
      throw new Error("SMTP is not configured for this mailbox");
    }
    const built = await buildRawMessage(mailbox, prepared.message, "send");

    // Reserve before SMTP. If the SMTP attempt fails, prepare a new token rather than risk a duplicate send.
    await this.actions.reserveForSending(prepared);

    const smtp = nodemailer.createTransport({
      host: mailbox.smtp.host,
      port: mailbox.smtp.port,
      secure: mailbox.smtp.secure,
      requireTLS: !mailbox.smtp.secure,
      auth: { user: mailbox.smtp.user, pass: mailbox.smtpPassword },
      tls: {
        rejectUnauthorized: true,
        ...(mailbox.smtp.servername ? { servername: mailbox.smtp.servername } : {}),
      },
      connectionTimeout: 20_000,
      greetingTimeout: 15_000,
      socketTimeout: 60_000,
    });

    let info: SMTPSentMessageInfo;
    try {
      info = await smtp.sendMail({ envelope: built.envelope, raw: built.raw });
    } catch (error) {
      const reason = error instanceof Error ? error.message : String(error);
      throw new Error(
        `SMTP did not confirm sending (${reason}). This approval token is now closed; prepare the message again only after checking Sent items.`,
      );
    } finally {
      smtp.close();
    }

    let sentCopySaved = false;
    let warning: string | null = null;
    try {
      sentCopySaved = await this.appendSentCopy(mailbox, built.raw);
      if (!sentCopySaved) warning = "Message sent, but no Sent folder was found for the IMAP copy.";
    } catch {
      warning = "Message sent, but saving the IMAP copy in Sent failed.";
    }

    return {
      message_id: info.messageId || built.messageId,
      accepted: (info.accepted ?? []).map(recipientValue),
      rejected: (info.rejected ?? []).map(recipientValue),
      sent_copy_saved: sentCopySaved,
      warning,
    };
  }

  async verifyConnections(): Promise<
    Array<{ mailbox_id: string; imap: "ok" | string; smtp: "ok" | "not_configured" | string }>
  > {
    const results = [];
    for (const mailbox of this.config.mailboxes) {
      let imap: "ok" | string = "ok";
      let smtp: "ok" | "not_configured" | string = mailbox.smtp ? "ok" : "not_configured";
      try {
        await this.withImap(mailbox, async (client) => {
          await client.list({ listOnly: true });
        });
      } catch (error) {
        imap = error instanceof Error ? error.message : String(error);
      }

      if (mailbox.smtp && mailbox.smtpPassword) {
        const transport = nodemailer.createTransport({
          host: mailbox.smtp.host,
          port: mailbox.smtp.port,
          secure: mailbox.smtp.secure,
          requireTLS: !mailbox.smtp.secure,
          auth: { user: mailbox.smtp.user, pass: mailbox.smtpPassword },
          tls: {
            rejectUnauthorized: true,
            ...(mailbox.smtp.servername ? { servername: mailbox.smtp.servername } : {}),
          },
          connectionTimeout: 20_000,
          greetingTimeout: 15_000,
          socketTimeout: 30_000,
        });
        try {
          await transport.verify();
        } catch (error) {
          smtp = error instanceof Error ? error.message : String(error);
        } finally {
          transport.close();
        }
      }
      results.push({ mailbox_id: mailbox.id, imap, smtp });
    }
    return results;
  }

  private async searchThreadFolder(
    mailbox: MailboxConfig,
    folder: string,
    ids: string[],
    subject: string,
    limit: number,
    includeBody: boolean,
  ): Promise<EmailDetails[]> {
    return this.withImap(mailbox, async (client) => {
      const lock = await client.getMailboxLock(folder, {
        readOnly: true,
        description: "MCP thread search",
      });
      try {
        const uidValidity = this.currentUidValidity(client);
        let query: SearchObject;
        if (ids.length) {
          const clauses: SearchObject[] = [];
          for (const id of ids) {
            clauses.push(
              { header: { "message-id": id } },
              { header: { "in-reply-to": id } },
              { header: { references: id } },
            );
          }
          query = clauses.length === 1 ? clauses[0]! : { or: clauses };
        } else {
          query = {
            subject: subject.replace(/^(?:(?:re|fw|fwd):\s*)+/i, "").trim(),
            since: new Date(Date.now() - 366 * 86_400_000),
          };
        }

        const result = await client.search(query, { uid: true });
        const uids = Array.isArray(result)
          ? result.sort((left, right) => right - left).slice(0, limit)
          : [];
        const details: EmailDetails[] = [];
        for (const uid of uids) {
          const reference: MessageReference = {
            mailbox_id: mailbox.id,
            folder,
            uid,
            uid_validity: uidValidity,
          };
          const detail = await this.fetchDetails(client, mailbox, reference, includeBody ? 20_000 : 0);
          details.push(detail);
        }
        return details;
      } finally {
        lock.release();
      }
    });
  }

  private async fetchDetails(
    client: ImapFlow,
    mailbox: MailboxConfig,
    reference: MessageReference,
    bodyLimit = this.config.limits.maxBodyChars,
  ): Promise<EmailDetails> {
    const metadata = await client.fetchOne(
      reference.uid,
      { uid: true, flags: true, envelope: true, internalDate: true, size: true, bodyStructure: true },
      { uid: true },
    );
    if (!metadata) throw new Error("Email no longer exists");
    if ((metadata.size ?? 0) > this.config.limits.maxMessageBytes) {
      throw new Error("Email exceeds the configured message-size safety limit");
    }

    const fetched = await client.fetchOne(
      reference.uid,
      { uid: true, source: true, flags: true, envelope: true, internalDate: true, size: true, bodyStructure: true },
      { uid: true },
    );
    if (!fetched || !fetched.source) throw new Error("Unable to retrieve email content");
    const parsed = await simpleParser(fetched.source, {
      skipHtmlToText: true,
      skipImageLinks: true,
      skipTextToHtml: true,
      keepCidLinks: true,
      maxHtmlLengthToParse: this.config.limits.maxMessageBytes,
    });
    const plain = plainBody(parsed);
    const bodyTruncated = bodyLimit >= 0 && plain.body.length > bodyLimit;
    const body = bodyLimit === 0 ? "" : plain.body.slice(0, bodyLimit);
    const messageRef = await this.references.encode(reference);
    const summary = await this.toSummary(
      mailbox,
      reference.folder,
      reference.uid_validity,
      fetched,
      messageRef,
    );

    return {
      ...summary,
      from: parsedAddresses(parsed.from).length ? parsedAddresses(parsed.from) : summary.from,
      to: parsedAddresses(parsed.to).length ? parsedAddresses(parsed.to) : summary.to,
      cc: parsedAddresses(parsed.cc),
      bcc: parsedAddresses(parsed.bcc),
      reply_to: parsedAddresses(parsed.replyTo),
      in_reply_to: parsed.inReplyTo ?? null,
      references: referencesFromParsed(parsed),
      body,
      body_format: bodyLimit === 0 ? "empty" : plain.format,
      body_truncated: bodyLimit === 0 ? plain.body.length > 0 : bodyTruncated,
      attachments: attachmentsFromStructure(fetched.bodyStructure),
      subject: parsed.subject ?? summary.subject,
      date: parsed.date?.toISOString() ?? summary.date,
      message_id: parsed.messageId ?? summary.message_id,
    };
  }

  private async toSummary(
    mailbox: MailboxConfig,
    folder: string,
    uidValidity: string,
    message: FetchMessageObject,
    existingRef?: string,
  ): Promise<EmailSummary> {
    const messageRef =
      existingRef ??
      (await this.references.encode({
        mailbox_id: mailbox.id,
        folder,
        uid: message.uid,
        uid_validity: uidValidity,
      }));
    return {
      message_ref: messageRef,
      uid: message.uid,
      folder,
      subject: message.envelope?.subject ?? "",
      from: envelopeAddresses(message.envelope?.from),
      to: envelopeAddresses(message.envelope?.to),
      date: asIso(message.envelope?.date),
      received_at: asIso(message.internalDate),
      message_id: message.envelope?.messageId ?? null,
      size: message.size ?? null,
      unread: !(message.flags?.has("\\Seen") ?? false),
      flagged: message.flags?.has("\\Flagged") ?? false,
      has_attachments: attachmentsFromStructure(message.bodyStructure).length > 0,
    };
  }

  private async appendSentCopy(mailbox: MailboxConfig, raw: Buffer): Promise<boolean> {
    return this.withImap(mailbox, async (client) => {
      const folders = await client.list();
      const sentFolder = this.resolveSpecialFolder(mailbox, folders, "sent");
      if (!sentFolder) return false;
      const result = await client.append(sentFolder, raw, ["\\Seen"], new Date());
      return Boolean(result);
    });
  }

  private resolveSpecialFolder(
    mailbox: MailboxConfig,
    folders: ListResponse[],
    type: "sent" | "drafts",
  ): string | undefined {
    const configured = mailbox.folders[type];
    if (configured) return configured;
    const flag = type === "sent" ? "\\Sent" : "\\Drafts";
    const byFlag = folders.find((folder) => folder.specialUse === flag);
    if (byFlag) return byFlag.path;

    const candidates =
      type === "sent"
        ? ["sent", "sent items", "enviados", "itens enviados"]
        : ["drafts", "rascunhos"];
    return folders.find((folder) => candidates.includes(folder.name.toLowerCase()))?.path;
  }

  private getMailbox(mailboxId: string): MailboxConfig {
    const mailbox = this.mailboxMap.get(mailboxId);
    if (!mailbox) throw new Error(`Unknown mailbox_id: ${mailboxId}`);
    return mailbox;
  }

  private currentUidValidity(client: ImapFlow): string {
    if (!client.mailbox) throw new Error("No IMAP folder is open");
    return client.mailbox.uidValidity.toString();
  }

  private assertUidValidity(client: ImapFlow, reference: MessageReference): void {
    const current = this.currentUidValidity(client);
    if (current !== reference.uid_validity) {
      throw new Error("The folder UIDVALIDITY changed; search for the email again");
    }
  }

  private async withImap<T>(
    mailbox: MailboxConfig,
    operation: (client: ImapFlow) => Promise<T>,
  ): Promise<T> {
    const maximumLiteral = Math.max(
      this.config.limits.maxMessageBytes,
      this.config.limits.maxAttachmentBytes,
    );
    const client = new ImapFlow({
      host: mailbox.imap.host,
      port: mailbox.imap.port,
      secure: mailbox.imap.secure,
      doSTARTTLS: mailbox.imap.secure ? undefined : true,
      auth: { user: mailbox.imap.user, pass: mailbox.imapPassword },
      tls: {
        rejectUnauthorized: true,
        ...(mailbox.imap.servername ? { servername: mailbox.imap.servername } : {}),
      },
      logger: false,
      disableAutoIdle: true,
      connectionTimeout: 20_000,
      greetingTimeout: 15_000,
      socketTimeout: 60_000,
      maxLiteralSize: maximumLiteral + 1024 * 1024,
      maxResponseSize: maximumLiteral + 2 * 1024 * 1024,
      clientInfo: {
        name: "cpanel-mail-mcp",
        version: "0.1.0",
        vendor: "MAIDOT",
      },
    });
    client.on("error", () => undefined);
    await client.connect();
    try {
      return await operation(client);
    } finally {
      try {
        await client.logout();
      } catch {
        client.close();
      }
    }
  }
}
