#!/usr/bin/env node

const args = new Set(process.argv.slice(2));
const intervalSecondsRaw = process.env.CAMPAIGN_RETRY_DAEMON_INTERVAL_SECONDS?.trim();
const timeoutMsRaw = process.env.CAMPAIGN_RETRY_DAEMON_TIMEOUT_MS?.trim();
const runOnce = args.has("--once") || args.has("-1");
const shouldJitter = !args.has("--no-jitter");

const intervalMs = normalizePositiveNumber(intervalSecondsRaw, 300) * 1000;
const requestTimeoutMs = normalizePositiveNumber(timeoutMsRaw, 30000);

if (isNaN(intervalMs) || intervalMs <= 0) {
  console.error("Invalid CAMPAIGN_RETRY_DAEMON_INTERVAL_SECONDS");
  process.exit(1);
}

const endpoint = resolveCampaignRetryEndpoint();
const token = process.env.CAMPAIGN_RETRY_JOB_TOKEN?.trim();

const payload = {
  ...(process.env.CAMPAIGN_RETRY_TENANT_ID?.trim() ? { tenantId: process.env.CAMPAIGN_RETRY_TENANT_ID.trim() } : {}),
  ...(process.env.CAMPAIGN_RETRY_CAMPAIGN_ID?.trim()
    ? { campaignId: process.env.CAMPAIGN_RETRY_CAMPAIGN_ID.trim() }
    : {}),
  ...(isPositiveInt(process.env.CAMPAIGN_RETRY_BATCH_SIZE)
    ? { recipientBatchSize: Number(process.env.CAMPAIGN_RETRY_BATCH_SIZE) }
    : {}),
  ...(isPositiveInt(process.env.CAMPAIGN_RETRY_MAX_CAMPAIGNS)
    ? { maxCampaigns: Number(process.env.CAMPAIGN_RETRY_MAX_CAMPAIGNS) }
    : {})
};

let isShuttingDown = false;
let lastSummary = null;

process.on("SIGINT", () => {
  isShuttingDown = true;
  console.info("\n[daemon] shutdown requested");
});

process.on("SIGTERM", () => {
  isShuttingDown = true;
  console.info("\n[daemon] shutdown requested");
});

(async function main() {
  if (runOnce) {
    await runLoopOnce();
  } else {
    await runDaemon();
  }
})();

async function runDaemon() {
  console.info(`[daemon] starting campaign retry worker, interval=${intervalMs / 1000}s`);
  while (!isShuttingDown) {
    await runLoopOnce();
    if (isShuttingDown) {
      break;
    }

    const jitter = shouldJitter ? Math.random() * Math.min(1000, intervalMs * 0.1) : 0;
    const waitMs = Math.max(1000, intervalMs + jitter);
    await sleep(waitMs);
  }

  if (lastSummary) {
    console.info(`[daemon] last run summary: ${JSON.stringify(lastSummary)}`);
  }
  console.info("[daemon] stopped");
}

async function runLoopOnce() {
  const startedAt = new Date().toISOString();
  const headers = {
    "content-type": "application/json"
  };
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);

  try {
    const response = await fetch(endpoint, {
      method: "POST",
      headers,
      body: JSON.stringify(payload),
      signal: controller.signal
    });

    const text = await response.text();
    clearTimeout(timeout);

    if (!response.ok) {
      console.error(`[${startedAt}] campaign retry failed: ${response.status} ${response.statusText}`);
      console.error(text);
      lastSummary = { ts: startedAt, status: "failed", code: response.status, message: response.statusText };
      if (runOnce) {
        process.exitCode = 1;
      }
      return;
    }

    lastSummary = safeParseJson(text) ?? { ts: startedAt, raw: text, status: "ok" };
    console.log(`[${startedAt}] campaign retry done: ${typeof text === "string" ? text.slice(0, 250) : text}`);
  } catch (error) {
    clearTimeout(timeout);
    const message = error instanceof Error ? error.message : String(error);
    lastSummary = { ts: startedAt, status: "error", message };
    console.error(`[${startedAt}] campaign retry request failed: ${message}`);
    if (runOnce) {
      process.exitCode = 1;
    }
  }

  if (runOnce) {
    process.exit(process.exitCode ?? 0);
  }
}

function resolveCampaignRetryEndpoint() {
  const baseUrl = process.env.CAMPAIGN_RETRY_JOB_URL?.trim() || process.env.APP_URL?.trim() || process.env.NEXT_PUBLIC_APP_URL?.trim();

  if (!baseUrl) {
    console.error("Missing CAMPAIGN_RETRY_JOB_URL / APP_URL / NEXT_PUBLIC_APP_URL");
    process.exit(1);
  }

  return baseUrl.endsWith("/api/jobs/campaign-retry")
    ? baseUrl
    : `${baseUrl.replace(/\/+$/, "")}/api/jobs/campaign-retry`;
}

function normalizePositiveNumber(value, fallback) {
  const raw = Number(value);
  if (!Number.isFinite(raw) || raw <= 0) {
    return fallback;
  }
  return raw;
}

function isPositiveInt(raw) {
  const value = Number(raw);
  return Number.isInteger(value) && value > 0;
}

function safeParseJson(raw) {
  try {
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
