import pg from 'pg';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const MIGRATIONS_DIR = path.join(__dirname, '../migrations');

async function connectWithRetry(connStr, maxAttempts = 12) {
  for (let i = 1; i <= maxAttempts; i++) {
    const client = new pg.Client({ connectionString: connStr });
    try {
      await client.connect();
      return client;
    } catch (err) {
      await client.end().catch(() => {});
      if (i === maxAttempts) throw err;
      const delay = Math.min(2000 * i, 30000);
      console.log(`[migrate] DB not ready (attempt ${i}/${maxAttempts}), retry in ${delay}ms — ${err.message}`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

// Identifiant arbitraire mais STABLE du verrou consultatif : deux processus qui
// migrent la même base doivent choisir le même nombre.
const MIGRATION_LOCK_ID = 4_073_219_001;

async function run() {
  const client = await connectWithRetry(process.env.DATABASE_URL);

  // Verrou consultatif : le conteneur API exécute ce script à CHAQUE démarrage.
  // Deux réplicas qui démarrent ensemble lisaient tous deux schema_migrations
  // vide et appliquaient le même fichier en parallèle — au mieux une erreur au
  // second (clé primaire dupliquée) qui faisait échouer le démarrage, au pire
  // deux ALTER concurrents. Le verrou sérialise ; il est relâché à la
  // fermeture de la connexion, y compris si le process est tué.
  console.log('[migrate] acquisition du verrou…');
  await client.query('SELECT pg_advisory_lock($1)', [MIGRATION_LOCK_ID]);

  await client.query(`
    CREATE TABLE IF NOT EXISTS schema_migrations (
      version    TEXT PRIMARY KEY,
      applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
    )
  `);

  const { rows } = await client.query(
    'SELECT version FROM schema_migrations ORDER BY version'
  );
  const applied = new Set(rows.map(r => r.version));

  const files = fs.readdirSync(MIGRATIONS_DIR)
    .filter(f => f.endsWith('.sql'))
    .sort();

  let count = 0;
  for (const file of files) {
    if (applied.has(file)) continue;

    console.log(`[migrate] applying ${file}`);
    const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf8');

    await client.query('BEGIN');
    try {
      await client.query(sql);
      await client.query(
        'INSERT INTO schema_migrations (version) VALUES ($1)', [file]
      );
      await client.query('COMMIT');
      console.log(`[migrate] ok ${file}`);
      count++;
    } catch (err) {
      await client.query('ROLLBACK');
      console.error(`[migrate] FAILED ${file}: ${err.message}`);
      throw err;
    }
  }

  if (count === 0) console.log('[migrate] nothing to apply');
  // Relâché explicitement : la fermeture suffirait, mais l'expliciter rend le
  // verrou visible dans les logs et évite de le garder si end() traîne.
  await client.query('SELECT pg_advisory_unlock($1)', [MIGRATION_LOCK_ID]);
  await client.end();
}

export { MIGRATION_LOCK_ID, run };

// N'exécute la migration que si le fichier est lancé directement : importé par
// un test, il ne doit pas migrer la base au chargement.
const invokedDirectly = process.argv[1]
  && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));

if (invokedDirectly) {
  run().catch(err => {
    console.error('[migrate] fatal:', err.message);
    process.exit(1);
  });
}
