/**
 * Prepare the isolated E2E database before the backend webServer starts.
 *
 * Playwright starts configured webServer processes before test execution, so
 * relying on globalSetup for migrations can leave the backend serving against
 * an empty database during first requests. This script runs as part of the
 * backend webServer command to make the ordering explicit.
 */
import { spawnSync } from 'node:child_process';
import { resolve } from 'node:path';
import { Client } from 'pg';
import { config as loadEnv } from 'dotenv';

const e2eDir = resolve(__dirname, '..');
const repoRoot = resolve(e2eDir, '..', '..');
const backendDir = resolve(repoRoot, 'backend');

loadEnv({ path: resolve(e2eDir, '.env.e2e') });

function run(cmd: string, args: string[], opts: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) {
  const executable = process.platform === 'win32' && ['npm', 'npx'].includes(cmd)
    ? `${cmd}.cmd`
    : cmd;
  const env = Object.fromEntries(
    Object.entries({ ...process.env, ...opts.env }).filter(([, value]) => value !== undefined),
  ) as NodeJS.ProcessEnv;
  const result = spawnSync(executable, args, {
    stdio: 'inherit',
    cwd: opts.cwd ?? e2eDir,
    env,
    shell: process.platform === 'win32',
  });

  if (result.status !== 0) {
    const detail = result.error ? ` (${result.error.message})` : '';
    throw new Error(`${executable} ${args.join(' ')} exited with ${result.status}${detail}`);
  }
}

async function waitForPg(timeoutMs = 30_000) {
  const start = Date.now();

  // eslint-disable-next-line no-constant-condition
  while (true) {
    try {
      const client = new Client({
        host: process.env.DB_HOST,
        port: parseInt(process.env.DB_PORT || '5434', 10),
        user: process.env.DB_USERNAME,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_DATABASE,
      });
      await client.connect();
      await client.query('SELECT 1');
      await client.end();
      return;
    } catch (err) {
      if (Date.now() - start > timeoutMs) {
        throw new Error(`Postgres did not become ready within ${timeoutMs}ms: ${err}`);
      }
      await new Promise((resolveRetry) => setTimeout(resolveRetry, 500));
    }
  }
}

async function main() {
  console.log('[e2e] prepare-db 1/4 - ensuring Postgres + Redis are healthy');
  run('docker', ['compose', '-f', 'docker-compose.e2e.yml', 'up', '-d', '--wait']);

  console.log('[e2e] prepare-db 2/4 - waiting for Postgres');
  await waitForPg();

  console.log('[e2e] prepare-db 3/4 - applying migrations');
  run('npm', ['run', 'db:migrate'], {
    cwd: backendDir,
    env: {
      DB_HOST: process.env.DB_HOST,
      DB_PORT: process.env.DB_PORT,
      DB_USERNAME: process.env.DB_USERNAME,
      DB_PASSWORD: process.env.DB_PASSWORD,
      DB_DATABASE: process.env.DB_DATABASE,
      DB_SSL: process.env.DB_SSL,
      NODE_ENV: 'test',
    },
  });

  console.log('[e2e] prepare-db 4/4 - seeding fixtures');
  run('npx', ['tsx', 'scripts/seed.ts'], { cwd: e2eDir });

  console.log('[e2e] prepare-db complete');
}

main().catch((err) => {
  console.error('[e2e] prepare-db failed:', err);
  process.exit(1);
});
