/**
 * Documenso webhook URL auto-updater. Called by `./scripts/tunnel-url.sh`
 * when the env flag `DEV_AUTO_UPDATE_DOCUMENSO_WEBHOOK=1` is set so a
 * freshly-restarted cloudflared quick-tunnel (which gets a NEW hostname
 * on every restart) doesn't leave Documenso pointing at a dead URL.
 *
 * Gated by env flag so production ports — which may have a stable
 * webhook URL — can never have their config rotated by a stale dev
 * script. Reads Documenso credentials from env (DOCUMENSO_API_URL +
 * DOCUMENSO_API_KEY + optional DOCUMENSO_API_VERSION).
 *
 * Usage (manual invocation):
 *   DEV_AUTO_UPDATE_DOCUMENSO_WEBHOOK=1 pnpm tsx scripts/update-documenso-webhook.ts https://foo.trycloudflare.com
 *
 * Behaviour:
 *   - Lists every webhook currently configured on the Documenso
 *     instance.
 *   - Identifies webhooks whose `webhookUrl` looks like a
 *     trycloudflare.com domain OR matches our `/api/webhooks/documenso`
 *     path suffix. These are the ones to rotate.
 *   - PATCHes each matching webhook to point at the new tunnel URL.
 *   - Leaves all other webhooks alone (in case the instance also
 *     services another tenant or a stable production URL).
 *
 * Tries Documenso v2 first, falls back to v1 if the v2 endpoint
 * returns 404. Both versions support GET /webhook(s) + PATCH on the
 * webhook resource — the shape differs slightly between them but the
 * fields we touch (`id`, `webhookUrl`) are stable across versions.
 */

import 'dotenv/config';

const ENABLE_FLAG = process.env.DEV_AUTO_UPDATE_DOCUMENSO_WEBHOOK;
const TUNNEL_BASE = process.argv[2];

if (ENABLE_FLAG !== '1') {
  console.log(
    'DEV_AUTO_UPDATE_DOCUMENSO_WEBHOOK is not set to 1 — skipping Documenso webhook update.',
  );
  process.exit(0);
}

if (!TUNNEL_BASE) {
  console.error('Usage: pnpm tsx scripts/update-documenso-webhook.ts <tunnel-base-url>');
  console.error(
    'Example: pnpm tsx scripts/update-documenso-webhook.ts https://foo.trycloudflare.com',
  );
  process.exit(1);
}

const API_URL = process.env.DOCUMENSO_API_URL;
const API_KEY = process.env.DOCUMENSO_API_KEY;
const API_VERSION = (process.env.DOCUMENSO_API_VERSION ?? 'v2').toLowerCase();

if (!API_URL || !API_KEY) {
  console.error('DOCUMENSO_API_URL and DOCUMENSO_API_KEY must be set in env to update webhooks.');
  process.exit(1);
}

// Trim trailing slash so we can compose paths cleanly.
const BASE = API_URL.replace(/\/+$/, '');
const NEW_WEBHOOK_URL = `${TUNNEL_BASE.replace(/\/+$/, '')}/api/webhooks/documenso`;

async function documensoRequest(path: string, init?: RequestInit): Promise<Response> {
  return fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      'Content-Type': 'application/json',
      Authorization: API_KEY!,
      ...(init?.headers ?? {}),
    },
  });
}

interface DocumensoWebhook {
  id: string | number;
  webhookUrl: string;
}

/**
 * Pluck the array of webhooks out of whatever shape the Documenso
 * version returned. v1 historically returned an array directly; v2
 * tends to wrap in `{ data: [...] }` or similar. Be tolerant.
 */
function extractWebhooks(raw: unknown): DocumensoWebhook[] {
  if (Array.isArray(raw)) return raw as DocumensoWebhook[];
  if (raw && typeof raw === 'object') {
    const r = raw as Record<string, unknown>;
    if (Array.isArray(r.data)) return r.data as DocumensoWebhook[];
    if (Array.isArray(r.webhooks)) return r.webhooks as DocumensoWebhook[];
  }
  return [];
}

async function listWebhooks(): Promise<{ webhooks: DocumensoWebhook[]; version: 'v1' | 'v2' }> {
  if (API_VERSION === 'v2' || API_VERSION === 'v2.0' || API_VERSION === 'v2.x') {
    const res = await documensoRequest('/api/v2/webhook');
    if (res.ok) {
      const body = (await res.json()) as unknown;
      return { webhooks: extractWebhooks(body), version: 'v2' };
    }
    if (res.status !== 404) {
      console.error(`v2 webhook list returned ${res.status}: ${await res.text()}`);
    }
    // Fall through to v1.
  }
  const res = await documensoRequest('/api/v1/webhooks');
  if (!res.ok) {
    console.error(`v1 webhook list returned ${res.status}: ${await res.text()}`);
    process.exit(1);
  }
  const body = (await res.json()) as unknown;
  return { webhooks: extractWebhooks(body), version: 'v1' };
}

async function patchWebhook(
  version: 'v1' | 'v2',
  webhook: DocumensoWebhook,
  newUrl: string,
): Promise<boolean> {
  const path =
    version === 'v2'
      ? '/api/v2/webhook'
      : `/api/v1/webhooks/${encodeURIComponent(String(webhook.id))}`;
  const body = version === 'v2' ? { id: webhook.id, webhookUrl: newUrl } : { webhookUrl: newUrl };
  const res = await documensoRequest(path, {
    method: 'PATCH',
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    console.error(`PATCH ${path} (id=${webhook.id}) returned ${res.status}: ${await res.text()}`);
    return false;
  }
  return true;
}

/**
 * Decide whether a given existing webhook is "ours" (i.e. matches the
 * pattern we want to rotate). Two signals:
 *   1. Path tail matches `/api/webhooks/documenso` — the CRM-side
 *      handler we own.
 *   2. Host matches `*.trycloudflare.com` — almost certainly a stale
 *      quick-tunnel URL. Rotating these is always safe.
 */
function isRotatableWebhook(w: DocumensoWebhook): boolean {
  if (!w.webhookUrl) return false;
  if (w.webhookUrl.endsWith('/api/webhooks/documenso')) return true;
  try {
    const host = new URL(w.webhookUrl).hostname;
    if (host.endsWith('.trycloudflare.com')) return true;
  } catch {
    /* malformed — leave alone */
  }
  return false;
}

async function main(): Promise<void> {
  console.log(`Listing webhooks via Documenso ${API_VERSION.toUpperCase()} (base: ${BASE})…`);
  const { webhooks, version } = await listWebhooks();
  console.log(`Found ${webhooks.length} webhook(s).`);

  const rotatable = webhooks.filter(isRotatableWebhook);
  if (rotatable.length === 0) {
    console.log(
      `No rotatable webhooks found (looking for paths ending /api/webhooks/documenso or *.trycloudflare.com hosts).`,
    );
    console.log(`If your dev webhook is configured differently, point it at: ${NEW_WEBHOOK_URL}`);
    return;
  }

  console.log(`Updating ${rotatable.length} webhook(s) to ${NEW_WEBHOOK_URL}…`);
  let ok = 0;
  let fail = 0;
  for (const w of rotatable) {
    if (w.webhookUrl === NEW_WEBHOOK_URL) {
      console.log(`  ${w.id}: already at the target URL, skipping.`);
      continue;
    }
    const succeeded = await patchWebhook(version, w, NEW_WEBHOOK_URL);
    if (succeeded) {
      ok++;
      console.log(`  ${w.id}: ${w.webhookUrl} -> ${NEW_WEBHOOK_URL}`);
    } else {
      fail++;
    }
  }
  console.log(`Done. ${ok} updated, ${fail} failed.`);
  if (fail > 0) process.exit(1);
}

main().catch((err) => {
  console.error('Documenso webhook update failed:', err);
  process.exit(1);
});
