/**
 * Database migration runner for SentryAgent.ai AgentIdP.
 * Reads all .sql files from src/db/migrations/ in alphabetical order,
 * tracks applied migrations in a schema_migrations table, and executes
 * only unapplied migrations.
 */

import * as fs from 'fs';
import * as path from 'path';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';

dotenv.config();

const MIGRATIONS_DIR = path.join(__dirname, '../src/db/migrations');

interface MigrationRow {
  name: string;
  applied_at: Date;
}

/**
 * Ensures the schema_migrations tracking table exists.
 *
 * @param pool - The PostgreSQL connection pool.
 */
async function ensureMigrationsTable(pool: Pool): Promise<void> {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS schema_migrations (
      name       VARCHAR(255) PRIMARY KEY,
      applied_at TIMESTAMPTZ  NOT NULL DEFAULT NOW()
    )
  `);
}

/**
 * Returns the list of already-applied migration names.
 *
 * @param pool - The PostgreSQL connection pool.
 * @returns Array of applied migration names.
 */
async function getAppliedMigrations(pool: Pool): Promise<string[]> {
  const result = await pool.query<MigrationRow>('SELECT name FROM schema_migrations ORDER BY name');
  return result.rows.map((row) => row.name);
}

/**
 * Applies a single migration file within a transaction.
 *
 * @param pool - The PostgreSQL connection pool.
 * @param name - The migration file name (without path).
 * @param sql - The SQL to execute.
 */
async function applyMigration(pool: Pool, name: string, sql: string): Promise<void> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query(sql);
    await client.query('INSERT INTO schema_migrations (name) VALUES ($1)', [name]);
    await client.query('COMMIT');
    // eslint-disable-next-line no-console
    console.log(`  ✓ Applied: ${name}`);
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

/**
 * Main migration runner.
 * Reads all .sql files in alphabetical order and applies unapplied ones.
 */
async function migrate(): Promise<void> {
  const connectionString = process.env['DATABASE_URL'];
  if (!connectionString) {
    throw new Error('DATABASE_URL environment variable is required');
  }

  const pool = new Pool({ connectionString });

  try {
    // eslint-disable-next-line no-console
    console.log('Running database migrations...');

    await ensureMigrationsTable(pool);
    const applied = await getAppliedMigrations(pool);

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

    let count = 0;
    for (const file of files) {
      if (applied.includes(file)) {
        // eslint-disable-next-line no-console
        console.log(`  - Skipped (already applied): ${file}`);
        continue;
      }

      const filePath = path.join(MIGRATIONS_DIR, file);
      const sql = fs.readFileSync(filePath, 'utf-8');
      await applyMigration(pool, file, sql);
      count++;
    }

    // eslint-disable-next-line no-console
    console.log(`\nMigrations complete. ${count} migration(s) applied.`);
  } finally {
    await pool.end();
  }
}

migrate().catch((err: unknown) => {
  // eslint-disable-next-line no-console
  console.error('Migration failed:', err);
  process.exit(1);
});
