<?php
/**
 * Database Migration Runner
 *
 * Orchestrates execution of database migrations, tracks migration history,
 * and handles migration errors.
 *
 * @since 9.11.0
 * @package ExactMetrics
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Migration Runner class.
 *
 * @since 9.11.0
 */
class ExactMetrics_Migration_Runner {

	/**
	 * Registered migrations.
	 *
	 * @since 9.11.0
	 * @var array
	 */
	private $migrations = array();

	/**
	 * Migration history option name.
	 *
	 * @since 9.11.0
	 * @var string
	 */
	const HISTORY_OPTION = 'exactmetrics_migration_history';

	/**
	 * Migration lock option name.
	 *
	 * @since 9.11.0
	 * @var string
	 */
	const LOCK_OPTION = 'exactmetrics_migration_lock';

	/**
	 * Lock timeout in seconds.
	 *
	 * @since 9.11.0
	 * @var int
	 */
	const LOCK_TIMEOUT = 300; // 5 minutes

	/**
	 * Register a migration.
	 *
	 * @since 9.11.0
	 * @param string $migration_class Migration class name.
	 * @return bool True on success, false if class doesn't exist.
	 */
	public function register_migration( $migration_class ) {
		if ( ! class_exists( $migration_class ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( "ExactMetrics Migration Runner: Class {$migration_class} does not exist" );
			return false;
		}

		// Instantiate migration
		$migration = new $migration_class();

		// Verify it extends ExactMetrics_Migration
		if ( ! $migration instanceof ExactMetrics_Migration ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( "ExactMetrics Migration Runner: {$migration_class} must extend ExactMetrics_Migration" );
			return false;
		}

		// Add to migrations array
		$version = $migration->get_version();

		// Check for duplicate version
		if ( isset( $this->migrations[ $version ] ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( "ExactMetrics Migration Runner: Duplicate version {$version} - {$migration_class} conflicts with existing migration" );
			return false;
		}

		$this->migrations[ $version ] = $migration;

		return true;
	}

	/**
	 * Register multiple migrations.
	 *
	 * @since 9.11.0
	 * @param array $migration_classes Array of migration class names.
	 * @return int Number of migrations successfully registered.
	 */
	public function register_migrations( $migration_classes ) {
		$count = 0;
		foreach ( $migration_classes as $migration_class ) {
			if ( $this->register_migration( $migration_class ) ) {
				$count++;
			}
		}
		return $count;
	}

	/**
	 * Get all registered migrations.
	 *
	 * @since 9.11.0
	 * @return array Array of migration objects.
	 */
	public function get_migrations() {
		return $this->migrations;
	}

	/**
	 * Get pending migrations that haven't been run yet.
	 *
	 * @since 9.11.0
	 * @return array Array of migration objects.
	 */
	public function get_pending_migrations() {
		$history = $this->get_migration_history();
		$completed = wp_list_pluck( $history, 'version' );

		$pending = array();
		foreach ( $this->migrations as $version => $migration ) {
			if ( ! in_array( $version, $completed, true ) ) {
				$pending[ $version ] = $migration;
			}
		}

		// Sort by version
		uksort( $pending, 'version_compare' );

		return $pending;
	}

	/**
	 * Check if there are pending migrations.
	 *
	 * @since 9.11.0
	 * @return bool True if migrations are pending, false otherwise.
	 */
	public function has_pending_migrations() {
		return ! empty( $this->get_pending_migrations() );
	}

	/**
	 * Run all pending migrations.
	 *
	 * @since 9.11.0
	 * @return array {
	 *     Migration results.
	 *
	 *     @type bool  $success Whether all migrations completed successfully.
	 *     @type array $results Array of individual migration results.
	 * }
	 */
	public function run_pending_migrations() {
		// Check for lock
		if ( ! $this->acquire_lock() ) {
			return array(
				'success' => false,
				'error'   => 'Migration is already running',
				'results' => array(),
			);
		}

		$pending = $this->get_pending_migrations();
		$results = array();
		$success = true;

		foreach ( $pending as $version => $migration ) {
			$result = $this->run_migration( $migration );
			$results[ $version ] = $result;

			if ( ! $result['success'] ) {
				$success = false;
				// Stop on first failure
				break;
			}
		}

		// Release lock
		$this->release_lock();

		return array(
			'success' => $success,
			'results' => $results,
		);
	}

	/**
	 * Run a single migration.
	 *
	 * @since 9.11.0
	 * @param ExactMetrics_Migration $migration Migration instance.
	 * @return array {
	 *     Migration result.
	 *
	 *     @type bool   $success Whether migration completed successfully.
	 *     @type string $error   Error message if failed.
	 *     @type float  $duration Time taken in seconds.
	 * }
	 */
	public function run_migration( $migration ) {
		$version = $migration->get_version();
		$description = $migration->get_description();
		$start_time = microtime( true );

		// Log start
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		error_log( "ExactMetrics: Running migration {$version}: {$description}" );

		try {
			// Run the migration
			$migration->up();

			// Calculate duration
			$duration = microtime( true ) - $start_time;

			// Record in history
			$this->record_migration( $version, 'completed', $duration );

			// Log success
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( sprintf(
				'ExactMetrics: Migration %s completed successfully in %.2f seconds',
				$version,
				$duration
			) );

			return array(
				'success'  => true,
				'duration' => $duration,
			);

		} catch ( Exception $e ) {
			// Calculate duration
			$duration = microtime( true ) - $start_time;

			// Record failure in history
			$this->record_migration( $version, 'failed', $duration, $e->getMessage() );

			// Log error
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( sprintf(
				'ExactMetrics: Migration %s failed after %.2f seconds: %s',
				$version,
				$duration,
				$e->getMessage()
			) );

			return array(
				'success'  => false,
				'error'    => $e->getMessage(),
				'duration' => $duration,
			);
		}
	}

	/**
	 * Get migration history.
	 *
	 * @since 9.11.0
	 * @return array Array of migration history entries.
	 */
	public function get_migration_history() {
		return get_option( self::HISTORY_OPTION, array() );
	}

	/**
	 * Record a migration in the history.
	 *
	 * @since 9.11.0
	 * @param string $version  Migration version.
	 * @param string $status   Status (completed, failed).
	 * @param float  $duration Time taken in seconds.
	 * @param string $error    Error message if failed.
	 */
	private function record_migration( $version, $status, $duration, $error = '' ) {
		$history = $this->get_migration_history();

		$history[] = array(
			'version'   => $version,
			'status'    => $status,
			'duration'  => $duration,
			'error'     => $error,
			'timestamp' => current_time( 'mysql' ),
		);

		update_option( self::HISTORY_OPTION, $history, false );
	}

	/**
	 * Clear migration history.
	 *
	 * Useful for testing or troubleshooting.
	 *
	 * @since 9.11.0
	 * @return bool True on success, false on failure.
	 */
	public function clear_history() {
		return delete_option( self::HISTORY_OPTION );
	}

	/**
	 * Acquire migration lock to prevent concurrent runs.
	 *
	 * @since 9.11.0
	 * @return bool True if lock acquired, false if already locked.
	 */
	private function acquire_lock() {
		$lock = get_option( self::LOCK_OPTION );

		// Check if lock exists and is still valid
		if ( $lock && ( time() - $lock ) < self::LOCK_TIMEOUT ) {
			return false;
		}

		// Set new lock
		update_option( self::LOCK_OPTION, time(), false );
		return true;
	}

	/**
	 * Release migration lock.
	 *
	 * @since 9.11.0
	 * @return bool True on success, false on failure.
	 */
	private function release_lock() {
		return delete_option( self::LOCK_OPTION );
	}

	/**
	 * Force release migration lock.
	 *
	 * Useful if migration process dies and lock isn't released.
	 *
	 * @since 9.11.0
	 * @return bool True on success, false on failure.
	 */
	public function force_release_lock() {
		return $this->release_lock();
	}

	/**
	 * Check if migrations are currently locked.
	 *
	 * @since 9.11.0
	 * @return bool True if locked, false otherwise.
	 */
	public function is_locked() {
		$lock = get_option( self::LOCK_OPTION );

		if ( ! $lock ) {
			return false;
		}

		// Check if lock has expired
		if ( ( time() - $lock ) >= self::LOCK_TIMEOUT ) {
			// Lock expired, release it
			$this->release_lock();
			return false;
		}

		return true;
	}

	/**
	 * Get migration statistics.
	 *
	 * @since 9.11.0
	 * @return array {
	 *     Migration statistics.
	 *
	 *     @type int $total_migrations   Total registered migrations.
	 *     @type int $pending_migrations Number of pending migrations.
	 *     @type int $completed_migrations Number of completed migrations.
	 *     @type int $failed_migrations  Number of failed migrations.
	 * }
	 */
	public function get_statistics() {
		$history = $this->get_migration_history();
		$pending = $this->get_pending_migrations();

		$completed = 0;
		$failed = 0;

		foreach ( $history as $entry ) {
			if ( $entry['status'] === 'completed' ) {
				$completed++;
			} elseif ( $entry['status'] === 'failed' ) {
				$failed++;
			}
		}

		return array(
			'total_migrations'     => count( $this->migrations ),
			'pending_migrations'   => count( $pending ),
			'completed_migrations' => $completed,
			'failed_migrations'    => $failed,
		);
	}
}
