/**
 * Import a dump produced by export-data.mjs into the current database.
 * Tables are truncated first (order-independent via session_replication_role)
 * and columns are cast back to their real types using information_schema.
 *
 * Usage (inside the api container):
 *   node scripts/import-data.mjs <dump.json>
 */
import { PrismaClient, Prisma } from '@prisma/client';
import { readFileSync } from 'node:fs';

const prisma = new PrismaClient();

function toLiteral(value, udtName) {
  if (value === null) return Prisma.sql`NULL`;
  const target = Prisma.raw(`"${udtName}"`);
  switch (udtName) {
    case 'int2':
    case 'int4':
    case 'int8':
      return Prisma.sql`${BigInt(value)}::${target}`;
    case 'float4':
    case 'float8':
    case 'numeric':
      return Prisma.sql`${Number(value)}::${target}`;
    case 'bool':
      return Prisma.sql`${!!value}::${target}`;
    case 'timestamptz':
    case 'timestamp':
      return Prisma.sql`${new Date(value).toISOString()}::${target}`;
    case 'date':
      return Prisma.sql`${String(value)}::${target}`;
    case 'jsonb':
    case 'json':
      return Prisma.sql`${typeof value === 'string' ? value : JSON.stringify(value)}::${target}`;
    case 'bytea':
      return Prisma.sql`${Buffer.from(value, 'base64')}::bytea`;
    default:
      // text, varchar, enums and anything else: pass as text and cast
      return Prisma.sql`${String(value)}::${target}`;
  }
}

async function main() {
  const file = process.argv[2];
  if (!file) {
    console.error('Usage: node scripts/import-data.mjs <dump.json>');
    process.exit(1);
  }
  const dump = JSON.parse(readFileSync(file, 'utf8'));

  // Suspend FK checks during bulk load (postgres superuser not required for
  // session_replication_role in the compose postgres where app user owns db).
  await prisma.$executeRawUnsafe(`SET session_replication_role = replica`);

  const summary = {};
  for (const [table, rows] of Object.entries(dump.tables)) {
    if (rows.length === 0) {
      summary[table] = 0;
      continue;
    }
    await prisma.$executeRawUnsafe(`TRUNCATE TABLE "${table}" CASCADE`);

    const colTypes = {};
    const info = await prisma.$queryRawUnsafe(
      `SELECT column_name, udt_name FROM information_schema.columns WHERE table_name = '${table}'`,
    );
    for (const c of info) colTypes[c.column_name] = c.udt_name;

    const columns = Object.keys(rows[0]);
    const colList = Prisma.raw(columns.map((c) => `"${c}"`).join(', '));
    const CHUNK = 200;
    for (let i = 0; i < rows.length; i += CHUNK) {
      const tuples = rows.slice(i, i + CHUNK).map(
        (r) =>
          Prisma.sql`(${Prisma.join(
            columns.map((c) => toLiteral(r[c], colTypes[c])),
          )})`,
      );
      await prisma.$executeRaw(
        Prisma.sql`INSERT INTO ${Prisma.raw(`"${table}"`)} (${colList}) VALUES ${Prisma.join(tuples)}`,
      );
    }
    // Keep sequences ahead of imported serial ids
    // Keep serial sequences ahead of imported ids
    const idCol = columns.find(
      (c) => c === 'id' || c === `${table.replace(/s$/, '')}_id`,
    );
    if (idCol) {
      await prisma.$executeRawUnsafe(
        `SELECT setval(pg_get_serial_sequence('"${table}"', '${idCol}'), COALESCE((SELECT MAX("${idCol}") FROM "${table}"), 1))`,
      );
    }
    summary[table] = rows.length;
  }

  await prisma.$executeRawUnsafe(`SET session_replication_role = DEFAULT`);
  console.log('Imported:', JSON.stringify(summary, null, 2));
}

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(() => prisma.$disconnect());
