<?php
/**
 * Migration Runner - Abstract Base Class
 *
 * Provides production-ready database migration management for CloudXM plugins.
 * Handles migration execution, rollback, and tracking using dedicated migrations table.
 *
 * @package CloudXM\SDK\Persistence
 */

namespace CloudXM\SDK\Persistence;

use CloudXM\SDK\Support\HasLogger;

/**
 * Migration Runner
 *
 * Production-ready migration system with:
 * - Dedicated migrations tracking table
 * - Batch management for rollbacks
 * - Proper error handling and logging
 * - Plugin-specific table prefixes
 */
class MigrationRunner
{
    use HasLogger;

    /**
     * Migrations table name - dedicated, production-ready
     */
    const MIGRATIONS_TABLE = 'migrations';

    const MIGRATION_PREFIX = 'migration_';

    const MIGRATION_LOG = 'migration_log';

    /**
     * Plugin table prefix (e.g., 'cloudxm_', 'marlim_')
     *
     * @var string
     */
    protected $table_prefix;

    /**
     * Migrations directory path
     *
     * @var string
     */
    private $migrations_path;

    /**
     * WordPress database object
     *
     * @var \wpdb
     */
    private $wpdb;

    /**
     * Configuration interface for accessing plugin settings
     *
     * @var \CloudXM\SDK\Contracts\ConfigurationInterface
     */
    private $config;

    /**
     * Constructor
     *
     * @param \CloudXM\SDK\Contracts\ConfigurationInterface $config Configuration for migration settings
     */
    public function __construct(
        \CloudXM\SDK\Contracts\ConfigurationInterface $config
    ) {
        global $wpdb;
        $this->wpdb = $wpdb;
        $this->config = $config;
        $this->table_prefix = $config->getTablePrefix() ?? 'cloudxm_';
        $this->migrations_path = $this->getMigrationsPath();
    }

    /**
     * Get the migrations path from configuration
     *
     * @return string
     */
    protected function getMigrationsPath(): string
    {
        // Standard pattern: pluginPath + src/Persistence/Migrations/
        $pluginPath = $this->config->getPluginDirPath();
        return $pluginPath . 'src/Persistence/Migrations/';
    }

    /**
     * Run all pending migrations
     *
     * @return array Results of migration execution
     */
    public function runMigrations()
    {
        // Verify migrations table exists (created in migration 001)
        if (!$this->ensureMigrationsTableExists()) {
            return array(
                'success' => false,
                'message' => 'Migration system not initialized - migrations table is missing',
                'migrations' => array(),
                'errors' => array('Migrations table not found'),
            );
        }

        $results = array(
            'success' => true,
            'message' => 'All migrations completed successfully',
            'migrations' => array(),
            'errors' => array(),
        );

        try {
            // Get current batch number for this batch of migrations
            $batch_number = $this->getNextBatchNumber();

            $migration_files = $this->getPendingMigrations();

            if (empty($migration_files)) {
                $results['message'] = 'No pending migrations found';
                return $results;
            }

            foreach ($migration_files as $migration_file) {
                $migration_result = $this->runSingleMigration($migration_file, $batch_number);
                $results['migrations'][] = $migration_result;

                if (!$migration_result['success']) {
                    $results['success'] = false;
                    $results['errors'][] = $migration_result['error'];
                    // Continue with other migrations - don't stop on single failure
                }
            }

            if (count($results['migrations']) > 0) {
                $results['message'] = sprintf(
                    'Successfully executed %d migrations',
                    count($results['migrations'])
                );
            }

            if (!empty($results['errors'])) {
                $results['message'] = sprintf(
                    'Executed %d migrations with %d errors',
                    count($results['migrations']),
                    count($results['errors'])
                );
            }

        } catch (\Exception $e) {
            $results['success'] = false;
            $results['message'] = 'Migration execution failed: ' . $e->getMessage();
            $results['errors'][] = $e->getMessage();

            $this->log()->error('Migration execution failed', ['error' => $e->getMessage()]);
        }

        return $results;
    }

    /**
     * Get list of pending migrations
     *
     * @return array Array of migration class names
     */
    private function getPendingMigrations()
    {
        if (!is_dir($this->migrations_path)) {
            $this->log()->error('Migrations path does not exist', ['path' => $this->migrations_path]);
            return array();
        }

        $migration_files = glob($this->migrations_path . '*.php');
        // Remove debug logging during migration status checking to eliminate log spam on every page load
        $pending_migrations = array();

        foreach ($migration_files as $migration_file) {
            $migration_name = $this->getMigrationName($migration_file);
            // Remove debug logging during status checking to avoid spam on every page load

            if (!$this->isMigrationExecuted($migration_name)) {
                $pending_migrations[] = $migration_name;
            }
            // Completely silent status checking to eliminate log spam
        }

        // Sort migrations alphabetically to ensure proper execution order
        sort($pending_migrations);
        // Only log when actually running migrations via runMigrations(), not during constructor/instantiation
        if (!empty($pending_migrations)) {
            $this->log()->debug('Pending migrations identified', ['count' => count($pending_migrations)]);
        }

        return $pending_migrations;
    }

    /**
     * Run a single migration class
     *
     * @param string $migration_name Migration class name
     * @param int $batch_number Current batch number
     * @return array Migration execution result
     */
    private function runSingleMigration($migration_name, $batch_number)
    {
        $start_time = microtime(true);

        $result = array(
            'name' => $migration_name,
            'success' => false,
            'execution_time' => 0,
        );

        try {
            // Include the migration file to load the class
            $migration_file = $this->migrations_path . $migration_name . '.php';
            if (!file_exists($migration_file)) {
                throw new \Exception("Migration file not found: {$migration_file}");
            }

            // Read the file content to extract namespace and class name
            $file_content = file_get_contents($migration_file);
            if ($file_content === false) {
                throw new \Exception("Could not read migration file: {$migration_file}");
            }

            // Extract namespace from file content
            if (!preg_match('/namespace\s+([^;]+);/', $file_content, $namespace_match)) {
                throw new \Exception("Could not find namespace in migration file: {$migration_file}");
            }
            $migration_namespace = trim($namespace_match[1]);

            // Extract class name from file content
            if (!preg_match('/class\s+(\w+)/', $file_content, $class_match)) {
                throw new \Exception("Could not find class name in migration file: {$migration_file}");
            }
            $migration_class_name = $class_match[1];

            // Build full class name
            $migration_class = $migration_namespace . '\\' . $migration_class_name;

            include_once $migration_file;

            if (!$migration_class) {
                throw new \Exception("Could not find migration class for {$migration_name} in namespace {$plugin_namespace}\\Persistence\\Migrations");
            }

            $this->log()->info('Executing migration', ['migration' => $migration_name, 'class' => $migration_class]);

            // Instantiate migration with table prefix
            $migration = new $migration_class($this->table_prefix);

            // Verify it implements the interface
            if (!$migration instanceof \CloudXM\SDK\Contracts\MigrationInterface) {
                throw new \Exception("Migration {$migration_class} does not implement MigrationInterface");
            }

            // Run the migration
            $success = $migration->run();

            $result['success'] = $success;
            $result['execution_time'] = microtime(true) - $start_time;

            if ($success) {
                // Mark migration as successful in dedicated table
                try {
                    $this->recordMigrationInTable($migration_name, $batch_number);
                    $this->log()->info('Migration successfully recorded in database', [
                        'migration' => $migration_name,
                        'batch' => $batch_number,
                        'table_prefix' => $this->table_prefix,
                        'method' => 'interface_based_migration'
                    ]);
                } catch (\Exception $e) {
                    // If marking fails, migration still succeeded but log the issue
                    $result['marking_warning'] = $e->getMessage();
                    $this->log()->warning('Migration tracking failed, but migration executed', [
                        'migration' => $migration_name,
                        'error' => $e->getMessage()
                    ]);
                }
            } else {
                // MIGRATION FAILED - DO NOT RECORD IN DATABASE
                $this->log()->error('Migration failed - NOT recorded in database', [
                    'migration' => $migration_name,
                    'status' => 'FAILED'
                ]);
                $result['error'] = 'Migration run() method returned false';
            }

        } catch (\Exception $e) {
            $result['error'] = $e->getMessage();
            $result['execution_time'] = microtime(true) - $start_time;

            $this->log()->error('Migration execution failed', [
                'migration' => $migration_name,
                'error' => $e->getMessage(),
                'execution_time' => round($result['execution_time'], 4)
            ]);

        } catch (\Error $e) {
            $result['error'] = 'PHP Error: ' . $e->getMessage();
            $result['execution_time'] = microtime(true) - $start_time;

            $this->log()->error('Migration execution failed with PHP Error', [
                'migration' => $migration_name,
                'error' => $e->getMessage(),
                'execution_time' => round($result['execution_time'], 4)
            ]);
        }

        return $result;
    }

    /**
     * Get migration name from file path
     *
     * @param string $migration_file Path to migration file
     * @return string Migration name
     */
    private function getMigrationName($migration_file)
    {
        return basename($migration_file, '.php');
    }

    /**
     * Find migration class in namespace after including the file
     *
     * @param string $namespace Base namespace to search in
     * @param string $migration_file_name Migration filename (without .php)
     * @return string|null Full class name or null if not found
     */
    private function findMigrationClass($namespace, $migration_file_name)
    {
        // Get all declared classes
        $declared_classes = get_declared_classes();

        // Look for classes in the migration namespace that implement MigrationInterface
        foreach ($declared_classes as $class_name) {
            if (strpos($class_name, $namespace) === 0) {
                // Check if it implements the interface
                $reflection = new \ReflectionClass($class_name);
                if ($reflection->implementsInterface(\CloudXM\SDK\Contracts\MigrationInterface::class)) {
                    return $class_name;
                }
            }
        }

        return null;
    }

    /**
     * Check if migration has been executed using dedicated table
     *
     * @param string $migration_name Migration name
     * @return bool True if executed, false otherwise
     */
    private function isMigrationExecuted($migration_name)
    {
        // Use plugin-specific migrations table
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;

        $result = $this->wpdb->get_var($this->wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE migration = %s",
            $migration_name
        ));

        return $result > 0;
    }

    /**
     * Ensure the migrations tracking table exists
     *
     * @return bool True if table exists or was created successfully
     */
    private function ensureMigrationsTableExists()
    {
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;

        // Check if table already exists before attempting to create
        $table_exists = $this->wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;

        if ($table_exists) {
            // Table already exists, silent return to avoid log spam
            return true;
        }

        // Table doesn't exist, create it
        $charset_collate = $this->wpdb->get_charset_collate();
        $migrations_sql = "CREATE TABLE {$table_name} (
            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            migration VARCHAR(255) NOT NULL,
            batch INT(11) UNSIGNED NOT NULL,
            executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY migration_batch_unique (migration, batch),
            KEY idx_migration (migration),
            KEY idx_batch (batch)
        ) {$charset_collate};";

        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($migrations_sql);

        // Verify table was created successfully
        if ($this->wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name) {
            $this->log()->info('Migrations table created', [
                'table' => $table_name,
                'table_prefix' => $this->table_prefix
            ]);
            return true;
        }

        // Table creation failed
        $this->log()->error('Failed to create migrations table', [
            'expected_table' => $table_name,
            'table_prefix' => $this->table_prefix,
            'note' => 'Migration system cannot initialize'
        ]);

        return false;
    }

    /**
     * Get the next batch number for grouping migrations
     *
     * @return int Next batch number
     */
    private function getNextBatchNumber()
    {
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;
        $max_batch = $this->wpdb->get_var("SELECT MAX(batch) FROM {$table_name}");

        return ($max_batch !== null) ? $max_batch + 1 : 1;
    }

    /**
     * Convert legacy scalar migration marker to array format
     *
     * @param string $migration_name Migration name
     * @param mixed $legacy_value Legacy scalar value
     */
    private function convertLegacyMigrationMarker($migration_name, $legacy_value)
    {
        $option_name = self::MIGRATION_PREFIX . $migration_name;
        $migration_data = array(
            'executed_at' => is_string($legacy_value) ? $legacy_value : current_time('mysql'),
        );

        update_option($option_name, $migration_data);

        $this->log()->info('Converted legacy migration marker', ['migration' => $migration_name]);
    }

    /**
     * Record migration execution in dedicated table
     *
     * @param string $migration_name Migration name
     * @param int $batch_number Batch number
     */
    private function recordMigrationInTable($migration_name, $batch_number)
    {
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;

        $result = $this->wpdb->insert(
            $table_name,
            array(
                'migration' => $migration_name,
                'batch' => $batch_number,
            ),
            array('%s', '%d')
        );

        if ($result === false) {
            $error = $this->wpdb->last_error;
            throw new \Exception("Failed to record migration $migration_name: $error");
        }

        $this->log()->debug('Migration recorded in table', [
            'migration' => $migration_name,
            'batch' => $batch_number,
            'table_prefix' => $this->table_prefix,
            'method' => 'dedicated_migrations_table'
        ]);
    }

    /**
     * Get migration history
     *
     * @return array Migration history
     */
    public function getMigrationHistory()
    {
        return get_option(self::MIGRATION_LOG, array());
    }

    /**
     * Get list of executed migrations from dedicated table
     *
     * PRODUCTION-RELIABLE: Uses dedicated migrations table instead of WordPress options
     *
     * @return array Executed migrations with details
     */
    public function getExecutedMigrations()
    {
        // Use plugin-specific migrations table
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;
        $executed_migrations = array();

        // Get all executed migrations from dedicated table
        $migrations = $this->wpdb->get_results(
            $this->wpdb->prepare(
                "SELECT migration, batch, executed_at
                 FROM {$table_name}
                 ORDER BY executed_at, migration"
            )
        );

        foreach ($migrations as $migration_record) {
            $executed_migrations[] = array(
                'name' => $migration_record->migration,
                'executed_at' => $migration_record->executed_at,
                'batch' => $migration_record->batch,
                'version' => 'Production', // Modern migration system
            );
        }

        return $executed_migrations;
    }

    /**
     * Rollback a specific migration (if rollback file exists)
     *
     * @param string $migration_name Migration name to rollback
     * @return array Rollback result
     */
    public function rollbackMigration($migration_name)
    {
        $rollback_file = $this->migrations_path . 'rollbacks/' . $migration_name . '_rollback.php';

        if (!file_exists($rollback_file)) {
            return array(
                'success' => false,
                'message' => 'Rollback file not found for migration: ' . $migration_name,
            );
        }

        $result = array(
            'success' => true,
            'message' => '',
            'migration' => $migration_name,
        );

        try {
            // Remove migration marker from dedicated table (PRODUCTION METHOD)
            $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;

            $this->wpdb->delete(
                $table_name,
                array('migration' => $migration_name),
                array('%s')
            );

            // Fallback: remove legacy option if it exists (for cleanup)
            delete_option(self::MIGRATION_PREFIX . $migration_name);

            $result['success'] = true;
            $result['message'] = 'Migration rolled back successfully';

            $this->log()->info('Migration rolled back successfully', ['migration' => $migration_name]);

        } catch (\Exception $e) {
            $result['message'] = 'Rollback failed: ' . $e->getMessage();
            $this->log()->error('Migration rollback failed', ['migration' => $migration_name, 'error' => $e->getMessage()]);
        }

        return $result;
    }

    /**
     * Reset all migrations (for development/testing)
     *
     * @return array Reset result
     */
    public function resetMigrations()
    {
        if (!defined('WP_DEBUG') || !WP_DEBUG) {
            return array(
                'success' => false,
                'message' => 'Migration reset only allowed in debug mode',
            );
        }

        // Remove all migration markers from dedicated table (PRODUCTION METHOD)
        $table_name = $this->wpdb->prefix . $this->table_prefix . self::MIGRATIONS_TABLE;

        $this->wpdb->query("TRUNCATE TABLE {$table_name}");

        // Cleanup: remove any legacy migration options for complete cleanup
        $this->wpdb->query(
            $this->wpdb->prepare(
                "DELETE FROM {$this->wpdb->options} WHERE option_name LIKE %s",
                self::MIGRATION_PREFIX . '%'
            )
        );

        // Clear migration log option
        delete_option(self::MIGRATION_LOG);

        $this->log()->info('All migration markers reset (production method)', [
            'table_reset' => $table_name,
            'table_prefix' => $this->table_prefix,
        ]);

        return array(
            'success' => true,
            'message' => 'All migration markers reset from dedicated table',
        );
    }

    /**
     * Optional cleanup of legacy migration options (upgrade path)
     *
     * This method can be called to clean up any remaining legacy migration
     * options during plugin upgrades, but is not used in normal operation.
     */
    public function cleanupLegacyMigrationOptions()
    {
        $cleaned_count = 0;

        // Get all options that match the migration prefix
        $migration_options = $this->wpdb->get_results(
            $this->wpdb->prepare(
                "SELECT option_name FROM {$this->wpdb->options} WHERE option_name LIKE %s",
                self::MIGRATION_PREFIX . '%'
            ),
            ARRAY_A
        );

        foreach ($migration_options as $option) {
            delete_option($option['option_name']);
            $cleaned_count++;
        }

        if ($cleaned_count > 0) {
            $this->log()->info('Cleaned up legacy migration options', [
                'count_cleaned' => $cleaned_count,
            ]);
        }
    }

    /**
     * Create a new migration file template
     *
     * @param string $migration_name Migration name
     * @param string $description Migration description
     * @return array Creation result
     */
    public function createMigration($migration_name, $description = '')
    {
        if (!is_dir($this->migrations_path)) {
            wp_mkdir_p($this->migrations_path);
        }

        // Generate migration filename with timestamp
        $timestamp = date('Y_m_d_His');
        $filename = sprintf('%03d_%s_%s.php', $this->getNextMigrationNumber(), $timestamp, $migration_name);
        $filepath = $this->migrations_path . $filename;

        if (file_exists($filepath)) {
            return array(
                'success' => false,
                'message' => 'Migration file already exists: ' . $filename,
            );
        }

        $template = $this->getMigrationTemplate($migration_name, $description);

        $write_result = file_put_contents($filepath, $template);

        if ($write_result === false) {
            return array(
                'success' => false,
                'message' => 'Failed to create migration file',
            );
        }

        return array(
            'success' => true,
            'message' => 'Migration created successfully',
            'filename' => $filename,
        );
    }

    /**
     * Get next migration number
     *
     * @return int Next migration number
     */
    private function getNextMigrationNumber()
    {
        $existing_files = glob($this->migrations_path . '*.php');
        $max_number = 0;

        foreach ($existing_files as $file) {
            $filename = basename($file, '.php');
            if (preg_match('/^(\d{3})_/', $filename, $matches)) {
                $number = (int) $matches[1];
                $max_number = max($max_number, $number);
            }
        }

        return $max_number + 1;
    }

    /**
     * Get migration template
     *
     * @param string $migration_name Migration name
     * @param string $description Migration description
     * @return string Migration template
     */
    private function getMigrationTemplate($migration_name, $description)
    {
        $template = '<?php
/**
 * Migration: ' . ucfirst(str_replace('_', ' ', $migration_name)) . '
 *
';

        if (!empty($description)) {
            $template .= ' * ' . $description . '
 *
';
        }

        $template .= ' * @package CloudXM\SDK\Persistence
 * @since 1.0.0
 */

declare(strict_types=1);

namespace CloudXM\SDK\Persistence\Migrations;

use CloudXM\SDK\Support\HasLogger;

/**
 * Migration: ' . ucfirst(str_replace('_', ' ', $migration_name)) . '
 */
class ' . str_replace('_', '', ucwords($migration_name, '_')) . '
{
    use HasLogger;

    /**
     * Run the migration
     */
    public function run(): void
    {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();

        // Add your migration code here
        // Example:
        // $table_name = $wpdb->prefix . \'your_table\';
        // $sql = "CREATE TABLE {$table_name} (
        //     id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        //     // ... your columns
        // ) {$charset_collate};";
        //
        // require_once(ABSPATH . \'wp-admin/includes/upgrade.php\');
        // dbDelta($sql);

        // Log completion using HasLogger trait
        $this->log(\'migration_completed\', [
            \'migration\' => \'' . $migration_name . '\',
            \'version\' => \'1.0.0\',
            \'operation\' => \'create_table\',
            \'timing\' => date(\'Y-m-d H:i:s\')
        ]);
    }

    /**
     * Rollback the migration
     */
    public function rollback(): void
    {
        global $wpdb;

        // Add your rollback code here
        // Example:
        // $table_name = $wpdb->prefix . \'your_table\';
        // $wpdb->query("DROP TABLE IF EXISTS {$table_name}");

        $this->log(\'migration_rolled_back\', [
            \'migration\' => \'' . $migration_name . '\',
            \'operation\' => \'drop_table\'
        ]);
    }
}

global $migration_execution_result;
$migration_execution_result[\'success\'] = true;
';

        return $template;
    }

    /**
     * Run migrations only when plugin version has changed (activation or upgrade).
     */
    public function runMigrationsIfVersionChanged(string $optionName, string $currentVersion): void
    {
        $storedVersion = get_option($optionName, '0.0.0');

        if (version_compare($storedVersion, $currentVersion, '>=')) {
            return;
        }

        try {
            $this->runMigrations();
        } catch (\Throwable $e) {
            $this->log()->error('migration.version_upgrade_failed', [
                'from_version' => $storedVersion,
                'to_version' => $currentVersion,
                'error' => $e->getMessage(),
            ]);
        }

        update_option($optionName, $currentVersion);
    }
}