#!/usr/bin/env node
import { createReadStream, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { spawn } from 'node:child_process';
import {
  commandToString,
  composePrefix,
  flag,
  option,
  parseArgs,
  quotePgLiteral,
  readEnvFile,
  runCommand,
} from './ops-utils.mjs';

function usage() {
  return `Usage: node scripts/restore-postgres.mjs --input <dump> --confirm-overwrite [options]

This script restores into a freshly recreated PostgreSQL database. It refuses to
drop/recreate a database unless --confirm-overwrite is provided.

Options:
  --input <path>          pg_dump custom-format dump file
  --confirm-overwrite     Required safety flag for destructive restore
  --env-file <path>       Compose env file (default: .env.production)
  --compose-file <path>   Compose file (default: docker-compose.prod.yml)
  --service <name>        Postgres service name (default: postgres)
  --user <name>           Database user (default: POSTGRES_USER or postgres)
  --target-db <name>      Target database (default: POSTGRES_DB or ftb_pm)
  --maintenance-db <name> Maintenance database (default: postgres)
  --dry-run               Print restore commands without changing data
  --help                  Show this help
`;
}

function buildOptions(argv) {
  const args = parseArgs(argv);
  if (flag(args, 'help')) return { help: true };

  const envFile = option(args, 'env-file', '.env.production');
  const env = readEnvFile(envFile, { optional: flag(args, 'dry-run') });
  const input = option(args, 'input');
  if (!input) {
    throw new Error('Missing required --input <dump>');
  }

  const inputPath = resolve(input);
  if (!existsSync(inputPath)) {
    throw new Error(`Restore input not found: ${inputPath}`);
  }

  if (!flag(args, 'confirm-overwrite')) {
    throw new Error('Refusing destructive restore. Re-run with --confirm-overwrite.');
  }

  return {
    help: false,
    dryRun: flag(args, 'dry-run'),
    input: inputPath,
    envFile,
    composeFile: option(args, 'compose-file', 'docker-compose.prod.yml'),
    service: option(args, 'service', 'postgres'),
    user: option(args, 'user', env.POSTGRES_USER || 'postgres'),
    targetDb: option(args, 'target-db', env.POSTGRES_DB || 'ftb_pm'),
    maintenanceDb: option(args, 'maintenance-db', 'postgres'),
  };
}

export function buildRestoreCommands(options) {
  const prefix = composePrefix(options);
  const terminateSql = [
    'SELECT pg_terminate_backend(pid)',
    'FROM pg_stat_activity',
    `WHERE datname = ${quotePgLiteral(options.targetDb)} AND pid <> pg_backend_pid();`,
  ].join(' ');

  return [
    [
      ...prefix,
      'exec',
      '-T',
      options.service,
      'psql',
      '-U',
      options.user,
      '-d',
      options.maintenanceDb,
      '-v',
      'ON_ERROR_STOP=1',
      '-c',
      terminateSql,
    ],
    [
      ...prefix,
      'exec',
      '-T',
      options.service,
      'dropdb',
      '--if-exists',
      '-U',
      options.user,
      options.targetDb,
    ],
    [
      ...prefix,
      'exec',
      '-T',
      options.service,
      'createdb',
      '-U',
      options.user,
      options.targetDb,
    ],
    [
      ...prefix,
      'exec',
      '-T',
      options.service,
      'pg_restore',
      '-U',
      options.user,
      '-d',
      options.targetDb,
      '--no-owner',
      '--no-acl',
    ],
  ];
}

function runRestore(command, input) {
  return new Promise((resolveRun, rejectRun) => {
    const child = spawn(command[0], command.slice(1), {
      cwd: process.cwd(),
      stdio: ['pipe', 'inherit', 'inherit'],
    });
    createReadStream(input).pipe(child.stdin);
    child.on('error', rejectRun);
    child.on('close', (code) => {
      if (code === 0) {
        resolveRun();
      } else {
        rejectRun(new Error(`pg_restore failed with exit code ${code}`));
      }
    });
  });
}

export async function main(argv = process.argv.slice(2)) {
  const options = buildOptions(argv);
  if (options.help) {
    process.stdout.write(usage());
    return;
  }

  const commands = buildRestoreCommands(options);
  if (options.dryRun) {
    process.stdout.write(
      `[dry-run] PostgreSQL restore would recreate database: ${options.targetDb}\n`,
    );
    for (const command of commands.slice(0, -1)) {
      process.stdout.write(`${commandToString(command)}\n`);
    }
    process.stdout.write(`${commandToString(commands.at(-1), { stdin: options.input })}\n`);
    return;
  }

  for (const command of commands.slice(0, -1)) {
    await runCommand(command);
  }
  await runRestore(commands.at(-1), options.input);
  process.stdout.write(`PostgreSQL restore completed into fresh database: ${options.targetDb}\n`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch((error) => {
    process.stderr.write(`${error.message}\n`);
    process.exit(1);
  });
}

