import sgMail from "@sendgrid/mail";
import type { WorkerConfig } from "./config.js";
import { describe, type MailMessage } from "./message.js";

export type SendOutcome =
  | { kind: "sent" }
  | { kind: "permanent"; reason: string }
  | { kind: "transient"; reason: string };

interface SendGridError {
  code?: number;
  message?: string;
  response?: { body?: unknown };
}

const describeError = (err: unknown): string => {
  const error = err as SendGridError;
  const body = error?.response?.body;
  const detail = body ? ` ${JSON.stringify(body)}` : "";
  return `${error?.message ?? String(err)}${detail}`.slice(0, 500);
};

const isTransient = (err: unknown): boolean => {
  const code = (err as SendGridError)?.code;
  if (typeof code !== "number") return true;
  return code === 429 || code >= 500;
};

export function configureSender(config: WorkerConfig): void {
  sgMail.setApiKey(config.sendgridApiKey);
}

export async function sendMail(
  message: MailMessage,
  config: WorkerConfig,
): Promise<SendOutcome> {
  if (config.dryRun) {
    console.log(`[smtp-worker] DRY RUN, not sending: ${describe(message)}`);
    return { kind: "sent" };
  }

  try {
    await sgMail.send({
      to: message.to,
      cc: message.cc,
      bcc: message.bcc,
      // Either spelling from the producer; see the note in message.ts.
      replyToList: message.replyTo ?? message.replyToList,
      from: message.fromEmail ?? config.fallbackFrom,
      subject: message.subject,
      templateId: message.templateID,
      dynamicTemplateData: message.templateData ?? {},
      attachments: message.attachments,
    });
    return { kind: "sent" };
  } catch (err) {
    const reason = describeError(err);
    return isTransient(err)
      ? { kind: "transient", reason }
      : { kind: "permanent", reason };
  }
}

export async function sendWithRetries(
  message: MailMessage,
  config: WorkerConfig,
): Promise<SendOutcome> {
  let last: SendOutcome = { kind: "transient", reason: "not attempted" };

  for (let attempt = 1; attempt <= config.maxAttempts; attempt += 1) {
    last = await sendMail(message, config);
    if (last.kind !== "transient") return last;

    if (attempt < config.maxAttempts) {
      const waitMs = 1000 * 2 ** (attempt - 1);
      console.warn(
        `[smtp-worker] attempt ${attempt}/${config.maxAttempts} failed for ${describe(message)}: ${last.reason}. Retrying in ${waitMs}ms`,
      );
      await new Promise((resolve) => setTimeout(resolve, waitMs));
    }
  }

  return last;
}
