import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";

type Journal = {
  entries?: Array<{
    idx: number;
    when: number;
    tag: string;
    breakpoints?: boolean;
    version?: string;
  }>;
};

function padMigrationKey(idx: number): string {
  return `m${String(idx).padStart(4, "0")}`;
}

function escapeTemplateLiteral(value: string): string {
  return value.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}

async function fileExists(path: string): Promise<boolean> {
  try {
    await readFile(path);
    return true;
  } catch {
    return false;
  }
}

async function walkDirectories(root: string, onDir: (dir: string) => Promise<void>): Promise<void> {
  const entries = await readdir(root, { withFileTypes: true });
  await onDir(root);
  for (const entry of entries) {
    if (!entry.isDirectory()) continue;
    if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) {
      continue;
    }
    await walkDirectories(join(root, entry.name), onDir);
  }
}

async function generateOne(drizzleDir: string): Promise<void> {
  const metaDir = resolve(drizzleDir, "meta");
  const journalPath = resolve(metaDir, "_journal.json");
  if (!(await fileExists(journalPath))) {
    return;
  }

  const drizzleEntries = (await readdir(drizzleDir, { withFileTypes: true }))
    .filter((entry) => entry.isFile() && entry.name.endsWith(".sql"))
    .map((entry) => entry.name)
    .sort();

  if (drizzleEntries.length === 0) {
    return;
  }

  const journalRaw = await readFile(journalPath, "utf8");
  const journal = JSON.parse(journalRaw) as Journal;
  const entries = journal.entries ?? [];

  const sqlByKey = new Map<string, string>();
  for (const entry of entries) {
    const file = drizzleEntries[entry.idx];
    if (!file) {
      throw new Error(`Missing migration SQL file for idx=${entry.idx} in ${drizzleDir}`);
    }
    const sqlPath = resolve(drizzleDir, file);
    const sqlRaw = await readFile(sqlPath, "utf8");
    sqlByKey.set(padMigrationKey(entry.idx), sqlRaw);
  }

  const migrationsObjectLines: string[] = [];
  for (const entry of entries) {
    const key = padMigrationKey(entry.idx);
    const sql = sqlByKey.get(key);
    if (!sql) continue;
    migrationsObjectLines.push(`    ${key}: \`${escapeTemplateLiteral(sql)}\`,`);
  }

  const banner = `// This file is generated by src/actors/_scripts/generate-actor-migrations.ts.
// Source of truth is drizzle-kit output under ./drizzle (meta/_journal.json + *.sql).
// Do not hand-edit this file.
`;

  const journalLiteral = JSON.stringify(
    {
      entries: entries.map((entry) => ({
        idx: entry.idx,
        when: entry.when,
        tag: entry.tag,
        breakpoints: Boolean(entry.breakpoints),
      })),
    },
    null,
    2,
  );

  const outPath = resolve(drizzleDir, "..", "migrations.ts");
  const content = `${banner}
const journal = ${journalLiteral} as const;

export default {
  journal,
  migrations: {
${migrationsObjectLines.join("\n")}
  } as const
};
`;

  await mkdir(dirname(outPath), { recursive: true });
  await writeFile(outPath, content, "utf8");

  // drizzle-kit generates a JS helper file by default; delete to keep TS-only sources.
  await rm(resolve(drizzleDir, "migrations.js"), { force: true });
}

async function main(): Promise<void> {
  const packageRoot = resolve(import.meta.dirname, "..", "..", ".."); // packages/backend
  const actorsRoot = resolve(packageRoot, "src", "actors");

  await walkDirectories(actorsRoot, async (dir) => {
    if (dir.endsWith(`${join("db", "drizzle")}`)) {
      await generateOne(dir);
    }
  });
}

main().catch((error: unknown) => {
  const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
  // eslint-disable-next-line no-console
  console.error(message);
  process.exitCode = 1;
});
