/**
 * Database Migration Service for TimeSafari
 * 
 * Manages database migrations as people upgrade their app over time.
 * Provides safe, tracked migrations with rollback capabilities and 
 * detailed logging for debugging.
 * 
 * @author Matthew Raymer
 */

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 {
    console.log("📋 [Migration] Checking migration status...");
    
    // Create migrations table if it doesn't exist
    console.log("🔧 [Migration] Creating 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
      );
    `);
    console.log("✅ [Migration] Migrations table ready");

    // Get list of already applied migrations
    console.log("🔍 [Migration] Querying existing migrations...");
    const appliedMigrationsResult = await sqlQuery(
      "SELECT name FROM migrations",
    );
    console.log("📊 [Migration] Raw query result:", appliedMigrationsResult);
    
    const appliedMigrations = extractMigrationNames(appliedMigrationsResult);
    console.log("📋 [Migration] Extracted applied migrations:", Array.from(appliedMigrations));

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

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

    console.log(`📊 [Migration] Found ${migrations.length} total migrations, ${appliedMigrations.size} already applied`);
    console.log(`📝 [Migration] Registered migrations: ${migrations.map(m => m.name).join(', ')}`);

    let appliedCount = 0;
    let skippedCount = 0;

    // Run each migration that hasn't been applied yet
    for (const migration of migrations) {
      if (appliedMigrations.has(migration.name)) {
        console.log(`⏭️  [Migration] Skipping already applied: ${migration.name}`);
        skippedCount++;
        continue;
      }

      console.log(`🔄 [Migration] Applying migration: ${migration.name}`);

      try {
        // Execute the migration SQL
        console.log(`🔧 [Migration] Executing SQL for ${migration.name}...`);
        await sqlExec(migration.sql);
        console.log(`✅ [Migration] SQL executed successfully for ${migration.name}`);

        // Record that the migration was applied
        console.log(`📝 [Migration] Recording migration ${migration.name} as applied...`);
        const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
          migration.name,
        ]);
        console.log(`✅ [Migration] Migration record inserted:`, insertResult);

        console.log(`✅ [Migration] Successfully applied: ${migration.name}`);
        logger.info(
          `[MigrationService] Successfully applied migration: ${migration.name}`,
        );
        appliedCount++;
      } catch (error) {
        console.error(`❌ [Migration] Error applying ${migration.name}:`, error);
        
        // Handle specific cases where the migration might be partially applied
        const errorMessage = String(error).toLowerCase();

        // Check if it's a duplicate table/column error - this means the schema already exists
        if (
          errorMessage.includes("duplicate column") ||
          errorMessage.includes("column already exists") ||
          errorMessage.includes("already exists") ||
          errorMessage.includes("table") && errorMessage.includes("already exists")
        ) {
          console.log(`⚠️  [Migration] ${migration.name} appears already applied (${errorMessage}). Marking as complete.`);
          logger.warn(
            `[MigrationService] Migration ${migration.name} appears to be already applied (${errorMessage}). Marking as complete.`,
          );

          // Mark the migration as applied since the schema change already exists
          try {
            console.log(`📝 [Migration] Attempting to record ${migration.name} as applied despite error...`);
            const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
              migration.name,
            ]);
            console.log(`✅ [Migration] Migration record inserted after error:`, insertResult);
            console.log(`✅ [Migration] Marked as applied: ${migration.name}`);
            logger.info(
              `[MigrationService] Successfully marked migration as applied: ${migration.name}`,
            );
            appliedCount++;
          } catch (insertError) {
            // If we can't insert the migration record, log it but don't fail
            console.warn(`⚠️  [Migration] Could not record ${migration.name} as applied:`, insertError);
            logger.warn(
              `[MigrationService] Could not record migration ${migration.name} as applied:`,
              insertError,
            );
          }
        } else {
          // For other types of errors, still fail the migration
          console.error(`❌ [Migration] Failed to apply ${migration.name}:`, error);
          logger.error(
            `[MigrationService] Failed to apply migration ${migration.name}:`,
            error,
          );
          throw new Error(`Migration ${migration.name} failed: ${error}`);
        }
      }
    }

    console.log(`🎉 [Migration] Migration process complete! Applied: ${appliedCount}, Skipped: ${skippedCount}`);
  } catch (error) {
    console.error("💥 [Migration] Migration process failed:", error);
    logger.error("[MigrationService] Migration process failed:", error);
    throw error;
  }
}


