#!/usr/bin/env node
/**
 * Add an auth check to every exported async function in a "use server"
 * file using the TypeScript compiler API. Idempotent.
 *
 * The check inserted as the first statement in the function body is:
 *
 *   const adminUser = await getAdminUser();
 *   if (!adminUser) return { success: false, error: "Unauthorized" };
 *
 * If the function body already calls getAdminUser() or getSession() within
 * its first few statements, it is left alone.
 *
 * Usage: node scripts/fix-server-auth-ast.js <file>...
 */
const fs = require("node:fs");
const path = require("node:path");
const ts = require("typescript");

function ensureGetAdminUserImport(sourceFile) {
  let src = sourceFile.text;
  const hasImport = sourceFile.statements.some(
    (s) =>
      ts.isImportDeclaration(s) &&
      s.moduleSpecifier &&
      s.moduleSpecifier.text === "@/lib/admin-permissions",
  );
  if (hasImport) return src;

  const importLine = `import { getAdminUser } from "@/lib/admin-permissions";\n`;
  // Insert after "use server" directive.
  for (const stmt of sourceFile.statements) {
    if (
      ts.isExpressionStatement(stmt) &&
      ts.isStringLiteral(stmt.expression) &&
      (stmt.expression.text === "use server" ||
        stmt.expression.text === "use client")
    ) {
      const pos = stmt.getEnd();
      return src.slice(0, pos) + "\n" + importLine + src.slice(pos);
    }
  }
  // Otherwise after the first import.
  for (const stmt of sourceFile.statements) {
    if (ts.isImportDeclaration(stmt)) {
      const pos = stmt.getEnd();
      return src.slice(0, pos) + "\n" + importLine + src.slice(pos);
    }
  }
  return importLine + src;
}

function alreadyGated(body) {
  // Walk the function body and detect either:
  //   1. A getAdminUser() / getSession() call (auth gate already in place)
  //   2. An existing `const adminUser = ...` declaration (would collide)
  let hasAuthCall = false;
  let hasAdminUserVar = false;
  function walk(node) {
    if (
      ts.isCallExpression(node) &&
      ts.isIdentifier(node.expression) &&
      (node.expression.text === "getAdminUser" ||
        node.expression.text === "getSession")
    ) {
      hasAuthCall = true;
    }
    if (
      ts.isVariableDeclaration(node) &&
      ts.isIdentifier(node.name) &&
      node.name.text === "adminUser"
    ) {
      hasAdminUserVar = true;
    }
    ts.forEachChild(node, walk);
  }
  walk(body);
  return hasAuthCall || hasAdminUserVar;
}

function processFile(filePath) {
  const abs = path.resolve(filePath);
  if (!fs.existsSync(abs)) {
    console.error(`skip (not found): ${abs}`);
    return 0;
  }
  const original = fs.readFileSync(abs, "utf8");
  if (!original.includes('"use server"') && !original.includes("'use server'")) {
    return 0;
  }

  const sourceFile = ts.createSourceFile(
    abs,
    original,
    ts.ScriptTarget.Latest,
    true,
  );

  let src = ensureGetAdminUserImport(sourceFile);
  // Re-parse after import insertion.
  const sf = ts.createSourceFile(abs, src, ts.ScriptTarget.Latest, true);

  const edits = [];
  function visit(node) {
    if (
      ts.isFunctionDeclaration(node) &&
      node.modifiers &&
      node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
      node.modifiers.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
      node.body
    ) {
      if (!alreadyGated(node.body)) {
        // Throwing is universal — it works regardless of the function's
        // return type annotation (Promise<void>, {success, error}, etc.)
        // and matches how the rest of the app signals "auth gate" errors.
        const insert = `\n  const adminUser = await getAdminUser();\n  if (!adminUser) throw new Error("Unauthorized");\n`;
        const pos = node.body.getStart(sf) + 1; // after `{`
        edits.push({ pos, insert });
      }
    }
    ts.forEachChild(node, visit);
  }
  visit(sf);

  // Apply edits in reverse order so positions don't shift.
  edits.sort((a, b) => b.pos - a.pos);
  let out = src;
  for (const e of edits) {
    out = out.slice(0, e.pos) + e.insert + out.slice(e.pos);
  }

  if (edits.length > 0) {
    fs.writeFileSync(abs, out, "utf8");
    console.log(`patched (${edits.length} functions): ${abs}`);
  }
  return edits.length;
}

const files = process.argv.slice(2);
if (files.length === 0) {
  console.error("usage: node scripts/fix-server-auth-ast.js <file>...");
  process.exit(2);
}
let patched = 0, total = 0;
for (const f of files) {
  const n = processFile(f);
  if (n > 0) patched++;
  total += n;
}
console.log(`done — ${patched} files, ${total} functions`);