import { existsSync } from "node:fs";
import path from "node:path";
import dotenv from "dotenv";

/*
 * Environment files, most specific first.
 *
 * `dotenv.config()` alone reads `.env` and nothing else, so a server holding its
 * settings in `.env.prod` would start with none of them — and the failure would look
 * like missing configuration rather than a file that was never read.
 *
 * Both spellings of each environment are accepted because this repo uses `.env.prod`
 * while `NODE_ENV` is `production`. Loaded in order and dotenv does not overwrite a
 * key already set, so the environment-specific file wins over the shared one, and a
 * variable exported by the shell wins over both — which is what lets PM2 or a
 * one-off command override a single value without editing a file.
 */
const NODE_ENV = process.env.NODE_ENV ?? "development";
const SHORT_ENV = NODE_ENV === "production" ? "prod" : "dev";

for (const file of [`.env.${NODE_ENV}`, `.env.${SHORT_ENV}`, ".env"]) {
  const full = path.resolve(process.cwd(), file);
  if (existsSync(full)) dotenv.config({ path: full });
}

const read = (...names: string[]): string | undefined => {
  for (const name of names) {
    const value = process.env[name]?.trim();
    if (value) return value;
  }
  return undefined;
};

const readNumber = (name: string, fallback: number): number => {
  const raw = process.env[name]?.trim();
  if (!raw) return fallback;
  const parsed = Number(raw);
  return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
};

export interface WorkerConfig {
  queue: string;
  connectionUrl: string;
  sendgridApiKey: string;
  /** Used only when a message carries no `fromEmail` of its own. */
  fallbackFrom: string;
  /** How many messages may be in flight at once. */
  prefetch: number;
  /** Attempts against SendGrid for one message before giving up on this delivery. */
  maxAttempts: number;
  /** Seconds to stop consuming for after a transient failure, so we do not spin. */
  backoffSeconds: number;
  /** When true, log what would be sent and never call SendGrid. */
  dryRun: boolean;
}

export function loadConfig(): WorkerConfig {
  const queue = read("QUEUE", "SMTP_QUEUE_NAME");
  const connectionUrl = read(
    "CONSUMER_QUEUE_CONNECTION",
    "PRODUCER_QUEUE_CONNECTION",
    "QUEUE_CONNECTION",
  );
  const sendgridApiKey = read("SENDGRID_API_KEY");
  const fallbackFrom = read("FROM_EMAIL", "SMTP_FROM");

  const missing = [
    !queue && "QUEUE (or SMTP_QUEUE_NAME)",
    !connectionUrl && "CONSUMER_QUEUE_CONNECTION (or PRODUCER_QUEUE_CONNECTION)",
    !sendgridApiKey && "SENDGRID_API_KEY",
    !fallbackFrom && "FROM_EMAIL (or SMTP_FROM)",
  ].filter(Boolean);

  if (missing.length > 0) {
    throw new Error(
      `Missing required configuration: ${missing.join(", ")}. ` +
      "Copy workers/smtp/.env.example to workers/smtp/.env and fill it in.",
    );
  }

  return {
    queue: queue!,
    connectionUrl: connectionUrl!,
    sendgridApiKey: sendgridApiKey!,
    fallbackFrom: fallbackFrom!,
    prefetch: readNumber("SMTP_WORKER_PREFETCH", 5),
    maxAttempts: readNumber("SMTP_WORKER_MAX_ATTEMPTS", 3),
    backoffSeconds: readNumber("SMTP_WORKER_BACKOFF_SECONDS", 15),
    dryRun: process.env.SMTP_WORKER_DRY_RUN === "1",
  };
}

/** Which env files were found, for the startup log. */
export const envFilesLoaded = (): string[] =>
  [`.env.${NODE_ENV}`, `.env.${SHORT_ENV}`, ".env"].filter((file) =>
    existsSync(path.resolve(process.cwd(), file)),
  );

/** The broker URL with its password removed, for logging. */
export const redactUrl = (url: string): string =>
  url.replace(/(\/\/[^:]+:)[^@]+@/, "$1***@");
