import amqp from "amqplib";
import {
  envFilesLoaded,
  loadConfig,
  redactUrl,
  type WorkerConfig,
} from "./config.js";
import { describe, parseMessage } from "./message.js";
import { configureSender, sendWithRetries } from "./sender.js";


const RECONNECT_DELAY_MS = 5000;

let shuttingDown = false;

async function consume(config: WorkerConfig): Promise<void> {
  const connection = await amqp.connect(config.connectionUrl);
  const channel = await connection.createChannel();

  await channel.assertQueue(config.queue, { durable: true });
  await channel.prefetch(config.prefetch);

  console.log(
    `[smtp-worker] listening on "${config.queue}" at ${redactUrl(config.connectionUrl)} (prefetch ${config.prefetch})`,
  );

  let pausedUntil = 0;

  await channel.consume(
    config.queue,
    (msg) => {
      if (!msg) return;

      void (async () => {
        const parsed = parseMessage(msg.content);
        if (!parsed.ok) {
          console.error(
            `[smtp-worker] discarding unreadable message: ${parsed.reason}`,
          );
          channel.ack(msg);
          return;
        }

        const label = describe(parsed.message);

        if (Date.now() < pausedUntil) {
          channel.nack(msg, false, true);
          return;
        }

        const outcome = await sendWithRetries(parsed.message, config);

        if (outcome.kind === "sent") {
          console.log(`[smtp-worker] sent ${label}`);
          channel.ack(msg);
          return;
        }

        if (outcome.kind === "permanent") {
          console.error(
            `[smtp-worker] REJECTED BY SENDGRID, not retrying ${label}: ${outcome.reason}`,
          );
          channel.ack(msg);
          return;
        }

        pausedUntil = Date.now() + config.backoffSeconds * 1000;
        console.error(
          `[smtp-worker] transient failure for ${label}: ${outcome.reason}. Requeued; pausing ${config.backoffSeconds}s`,
        );
        channel.nack(msg, false, true);
      })();
    },
    { noAck: false },
  );

  await new Promise<void>((resolve, reject) => {
    connection.once("close", () => {
      if (shuttingDown) resolve();
      else reject(new Error("connection closed"));
    });
    connection.once("error", (err) => reject(err));
  });
}

async function main(): Promise<void> {
  /*
   * Logged before anything else.
   *
   * "Missing SENDGRID_API_KEY" and "read the wrong env file" produce the same error,
   * and naming the files that were actually found tells the two apart without
   * anyone having to guess.
   */
  const files = envFilesLoaded();
  console.log(
    `[smtp-worker] NODE_ENV=${process.env.NODE_ENV ?? "development"}, env files loaded: ${files.length ? files.join(", ") : "none"}`,
  );

  const config = loadConfig();
  configureSender(config);

  if (config.dryRun) {
    console.warn(
      "[smtp-worker] SMTP_WORKER_DRY_RUN=1 — messages will be acked without being sent",
    );
  }

  const stop = (signal: string) => {
    console.warn(`[smtp-worker] ${signal} received, shutting down`);
    shuttingDown = true;
    setTimeout(() => process.exit(0), 2000);
  };
  process.on("SIGINT", () => stop("SIGINT"));
  process.on("SIGTERM", () => stop("SIGTERM"));

  while (!shuttingDown) {
    try {
      await consume(config);
    } catch (err) {
      if (shuttingDown) break;
      console.error(
        `[smtp-worker] connection lost: ${err instanceof Error ? err.message : String(err)}. Reconnecting in ${RECONNECT_DELAY_MS}ms`,
      );
      await new Promise((resolve) => setTimeout(resolve, RECONNECT_DELAY_MS));
    }
  }
}

main().catch((err) => {
  console.error(
    `[smtp-worker] fatal: ${err instanceof Error ? err.message : String(err)}`,
  );
  process.exit(1);
});
