<?php
/**
 * Database migration runner.
 *
 * Scans the migrations directory, compares against stored DB version,
 * and runs outstanding migrations in order.
 *
 * @package ThemeREX\AutoDealer
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer\Database;

defined( 'ABSPATH' ) || exit;
class MigrationRunner {

	/**
	 * Option key that stores the current DB version (int).
	 *
	 * @var string
	 */
	private const VERSION_OPTION = 'trx_ad_db_version';

	/**
	 * Transient key set when a migration fails.
	 *
	 * @var string
	 */
	private const ERROR_TRANSIENT = 'trx_ad_migration_error';

	/**
	 * Directory containing migration files.
	 *
	 * @var string
	 */
	private string $migrationsDir;

	/**
	 * Constructor.
	 *
	 * @param string|null $migrationsDir Absolute path to migrations directory.
	 */
	public function __construct( ?string $migrationsDir = null ) {
		$this->migrationsDir = $migrationsDir ?? TRX_AD_PATH . 'includes/Database/migrations/';
	}

	/**
	 * Register hooks.
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'init', array( $this, 'runPendingMigrations' ), 5 );
		add_action( 'admin_notices', array( $this, 'showMigrationError' ) );
	}

	/**
	 * Run all pending migrations.
	 *
	 * @return true|\WP_Error True on success (or nothing to run), WP_Error on failure.
	 */
	public function runPendingMigrations(): bool|\WP_Error {
		$currentVersion = (int) get_option( self::VERSION_OPTION, 0 );
		$migrations     = $this->discoverMigrations();

		if ( $migrations instanceof \WP_Error ) {
			return $migrations;
		}

		// Filter to only pending migrations.
		$pending = array_filter(
			$migrations,
			static fn( MigrationInterface $m ): bool => $m->getVersion() > $currentVersion
		);

		if ( count( $pending ) === 0 ) {
			return true;
		}

		// Sort by version ascending.
		usort(
			$pending,
			static fn( MigrationInterface $a, MigrationInterface $b ): int => $a->getVersion() <=> $b->getVersion()
		);

		global $wpdb;

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		foreach ( $pending as $migration ) {
			try {
				$migration->up( $wpdb );

				// Check for dbDelta errors stored in $wpdb.
				if ( ! empty( $wpdb->last_error ) ) {
					$errorMsg = sprintf(
						'Migration %d failed: %s',
						$migration->getVersion(),
						$wpdb->last_error
					);
					$this->logError( $errorMsg );
					return new \WP_Error( 'trx_ad_migration_failed', $errorMsg );
				}

				update_option( self::VERSION_OPTION, $migration->getVersion() );
			} catch ( \Throwable $e ) {
				$errorMsg = sprintf(
					'Migration %d threw exception: %s',
					$migration->getVersion(),
					$e->getMessage()
				);
				$this->logError( $errorMsg );
				return new \WP_Error( 'trx_ad_migration_exception', $errorMsg );
			}
		}

		return true;
	}

	/**
	 * Get the current DB version.
	 *
	 * @return int
	 */
	public function getCurrentVersion(): int {
		return (int) get_option( self::VERSION_OPTION, 0 );
	}

	/**
	 * Discover migration files and instantiate them.
	 *
	 * @return MigrationInterface[]|\WP_Error
	 */
	private function discoverMigrations(): array|\WP_Error {
		if ( ! is_dir( $this->migrationsDir ) ) {
			return new \WP_Error(
				'trx_ad_migrations_dir_missing',
				sprintf( 'Migrations directory not found: %s', $this->migrationsDir )
			);
		}

		$files      = glob( $this->migrationsDir . '*.php' );
		$migrations = array();

		if ( $files === false ) {
			return new \WP_Error(
				'trx_ad_migrations_glob_failed',
				'Failed to scan migrations directory.'
			);
		}

		foreach ( $files as $file ) {
			$basename = basename( $file, '.php' );

			// Extract version number from filename (e.g., "001_CreateVehicleSpecsTable" -> 1).
			if ( ! preg_match( '/^(\d+)_(.+)$/', $basename, $matches ) ) {
				continue;
			}

			$version   = (int) $matches[1];
			$className = $matches[2];
			$fqcn      = 'ThemeREX\\AutoDealer\\Database\\Migrations\\' . $className;

			if ( ! class_exists( $fqcn ) ) {
				require_once $file;
			}

			if ( ! class_exists( $fqcn ) ) {
				continue;
			}

			$instance = new $fqcn();

			if ( $instance instanceof MigrationInterface ) {
				$migrations[] = $instance;
			}
		}

		return $migrations;
	}

	/**
	 * Log a migration error and set the admin transient.
	 *
	 * @param string $message Error message.
	 * @return void
	 */
	private function logError( string $message ): void {
		set_transient( self::ERROR_TRANSIENT, $message, HOUR_IN_SECONDS );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '[ThemeREX Auto Dealer] ' . $message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
	}

	/**
	 * Display an admin notice if a migration error occurred.
	 *
	 * @return void
	 */
	public function showMigrationError(): void {
		$error = get_transient( self::ERROR_TRANSIENT );

		if ( empty( $error ) || ! is_string( $error ) ) {
			return;
		}

		printf(
			'<div class="notice notice-error"><p><strong>%s</strong> %s</p></div>',
			esc_html__( 'ThemeREX Auto Dealer — Database Error:', 'trx-auto-dealer' ),
			esc_html( $error )
		);
	}
}
