import { Pool } from 'pg';
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { prisma } from './database.js';
import { env } from './env.js';

const MIGRATIONS_DIR = join(__dirname, '..', 'migrations', 'tenant');

export interface MigrationFile {
  version: number;
  name: string;
  sql: string;
}

export async function getMigrationFiles(): Promise<MigrationFile[]> {
  let files: string[];

  try {
    files = await readdir(MIGRATIONS_DIR);
  } catch (err: any) {
    if (err.code === 'ENOENT') {
      console.warn(`[Migrations] Directory not found: ${MIGRATIONS_DIR}`);
      return [];
    }
    throw err;
  }

  const pattern = /^(\d{3})_(.+)\.sql$/;
  const migrations: MigrationFile[] = [];

  for (const file of files) {
    const match = pattern.exec(file);
    if (!match) continue;

    const version = parseInt(match[1], 10);
    const name = file;
    const sql = await readFile(join(MIGRATIONS_DIR, file), 'utf8');

    migrations.push({ version, name, sql });
  }

  migrations.sort((a, b) => a.version - b.version);
  return migrations;
}

export async function migrate(pool: Pool, label?: string): Promise<number> {
  const prefix = label ? `[Migrations] (${label})` : '[Migrations]';

  // Ensure schema_migrations table exists
  await pool.query(`
    CREATE TABLE IF NOT EXISTS schema_migrations (
      version INTEGER PRIMARY KEY,
      name VARCHAR(255) NOT NULL,
      applied_at TIMESTAMP DEFAULT NOW()
    );
  `);

  // Get already-applied versions
  const { rows } = await pool.query<{ version: number }>(
    'SELECT version FROM schema_migrations ORDER BY version'
  );
  const appliedVersions = new Set(rows.map((r) => r.version));

  // Get all migration files
  const migrationFiles = await getMigrationFiles();
  const pending = migrationFiles.filter((m) => !appliedVersions.has(m.version));

  if (pending.length === 0) {
    return 0;
  }

  console.log(`${prefix} Applying ${pending.length} pending migration(s)...`);

  for (const migration of pending) {
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      await client.query(migration.sql);
      await client.query(
        'INSERT INTO schema_migrations (version, name) VALUES ($1, $2)',
        [migration.version, migration.name]
      );
      await client.query('COMMIT');
      console.log(`${prefix} Applied: ${migration.name}`);
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  }

  return pending.length;
}

export async function migrateAll(): Promise<{
  success: number;
  failed: number;
  skipped: number;
}> {
  const tenants = await prisma.tenant.findMany({
    where: { active: true },
    select: { id: true, rfc: true, databaseName: true },
  });

  let success = 0;
  let failed = 0;
  let skipped = 0;

  for (const tenant of tenants) {
    const parsed = new URL(env.DATABASE_URL);
    const pool = new Pool({
      host: parsed.hostname,
      port: parseInt(parsed.port || '5432'),
      user: decodeURIComponent(parsed.username),
      password: decodeURIComponent(parsed.password),
      database: tenant.databaseName,
      max: 1,
    });

    try {
      const applied = await migrate(pool, tenant.rfc);
      if (applied > 0) {
        success++;
      } else {
        skipped++;
      }
    } catch (err: any) {
      failed++;
      console.error(
        `[Migrations] (${tenant.rfc}) Failed: ${err.message}`
      );
    } finally {
      await pool.end();
    }
  }

  console.log(
    `[Migrations] Summary — success: ${success}, skipped: ${skipped}, failed: ${failed}`
  );

  return { success, failed, skipped };
}
