/**
 * Manage database migrations as people upgrade their app over time
 */

import { logger } from "../utils/logger";

/**
 * Migration interface for database schema migrations
 */
interface Migration {
  name: string;
  sql: string;
}

/**
 * Migration registry to store and manage database migrations
 */
class MigrationRegistry {
  private migrations: Migration[] = [];

  /**
   * Register a migration with the registry
   *
   * @param migration - The migration to register
   */
  registerMigration(migration: Migration): void {
    this.migrations.push(migration);
  }

  /**
   * Get all registered migrations
   *
   * @returns Array of registered migrations
   */
  getMigrations(): Migration[] {
    return this.migrations;
  }

  /**
   * Clear all registered migrations
   */
  clearMigrations(): void {
    this.migrations = [];
  }
}

// Create a singleton instance of the migration registry
const migrationRegistry = new MigrationRegistry();

/**
 * Register a migration with the migration service
 *
 * This function is used by the migration system to register database
 * schema migrations that need to be applied to the database.
 *
 * @param migration - The migration to register
 */
export function registerMigration(migration: Migration): void {
  migrationRegistry.registerMigration(migration);
}

/**
 * Run all registered migrations against the database
 *
 * This function executes all registered migrations in order, checking
 * which ones have already been applied to avoid duplicate execution.
 * It creates a migrations table if it doesn't exist to track applied
 * migrations.
 *
 * @param sqlExec - Function to execute SQL statements
 * @param sqlQuery - Function to query SQL data
 * @param extractMigrationNames - Function to extract migration names from query results
 * @returns Promise that resolves when all migrations are complete
 */
export async function runMigrations<T>(
  sqlExec: (sql: string, params?: unknown[]) => Promise<unknown>,
  sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
  extractMigrationNames: (result: T) => Set<string>,
): Promise<void> {
  try {
    // Create migrations table if it doesn't exist
    await sqlExec(`
      CREATE TABLE IF NOT EXISTS migrations (
        name TEXT PRIMARY KEY,
        applied_at TEXT DEFAULT CURRENT_TIMESTAMP
      );
    `);

    // Get list of already applied migrations
    const appliedMigrationsResult = await sqlQuery(
      "SELECT name FROM migrations",
    );
    const appliedMigrations = extractMigrationNames(appliedMigrationsResult);

    // Get all registered migrations
    const migrations = migrationRegistry.getMigrations();

    if (migrations.length === 0) {
      logger.warn("[MigrationService] No migrations registered");
      return;
    }

    // Run each migration that hasn't been applied yet
    for (const migration of migrations) {
      if (appliedMigrations.has(migration.name)) {
        continue;
      }

      try {
        // Execute the migration SQL
        await sqlExec(migration.sql);

        // Record that the migration was applied
        await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
          migration.name,
        ]);

        logger.info(
          `[MigrationService] Successfully applied migration: ${migration.name}`,
        );
      } catch (error) {
        logger.error(
          `[MigrationService] Failed to apply migration ${migration.name}:`,
          error,
        );
        throw new Error(`Migration ${migration.name} failed: ${error}`);
      }
    }
  } catch (error) {
    logger.error("[MigrationService] Migration process failed:", error);
    throw error;
  }
}
