<?php
namespace FleekDash\Database\Core;

defined('ABSPATH') || exit;

/**
 * Migration Manager
 *
 * Orchestrates database migrations: discovery, execution, rollback, and tracking
 */
class MigrationManager
{
    private \wpdb $wpdb;

    private string $table;

    private string $migrationsPath;

    public function __construct()
    {
        global $wpdb;
        $this->wpdb = $wpdb;
        $this->table = $wpdb->prefix . 'fleekdash_migrations';
        $this->migrationsPath = FLEEKDASH_DIR . 'database/Migrations/';

        // Ensure migrations table exists
        if (!MigrationInstaller::exists()) {
            MigrationInstaller::install();
        }
    }

    /**
     * Discover all migration files
     */
    public function discover(): array
    {
        $files = glob($this->migrationsPath . '*.php');
        if (!$files) {
            return [];
        }

        sort($files); // Alphabetical = chronological (with timestamps)

        $migrations = [];
        foreach ($files as $file) {
            $migrations[] = basename($file, '.php');
        }

        return $migrations;
    }

    /**
     * Get pending migrations
     */
    public function pending(): array
    {
        $all = $this->discover();
        $executed = $this->executed();

        return array_diff($all, $executed);
    }

    /**
     * Get executed migrations
     */
    public function executed(): array
    {
        $results = $this->wpdb->get_col(
            "SELECT migration FROM {$this->table}
             WHERE status = 'completed'
             ORDER BY id"
        );

        return $results ?: [];
    }

    /**
     * Run all pending migrations
     */
    public function run(int $limit = 0): array
    {
        return $this->runBatch($limit);
    }

    /**
     * Run one or more migrations in a controlled batch
     *
     * @param int $limit Number of migrations to execute (<=0 means run all)
     */
    public function runBatch(int $limit = 0): array
    {
        $pending = $this->pending();

        if (empty($pending)) {
            return $this->buildRunBatchResult(
                message: 'Nothing to migrate',
                batch: 0,
                pendingRemaining: 0,
            );
        }

        if ($limit <= 0) {
            $limit = PHP_INT_MAX;
        }

        $batch = $this->getNextBatch();
        $results = $this->buildRunBatchResult(batch: $batch);

        $executed = 0;

        foreach ($pending as $migration) {
            if ($executed >= $limit) {
                break;
            }

            $result = $this->runMigration($migration, $batch);
            $results['migrations'][] = $result;
            $executed++;

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

        $remaining = max(0, count($pending) - $executed);
        $results['pending_remaining'] = $remaining;

        if (!$results['stopped'] && $remaining > 0) {
            $results['has_more'] = true;
        }

        return $results;
    }

    /**
     * Canonical shape for {@see runBatch()} / {@see run()} responses.
     *
     * @return array{
     *     message?: string,
     *     batch: int,
     *     migrations: list<array<string, mixed>>,
     *     has_more: bool,
     *     stopped: bool,
     *     pending_remaining: int
     * }
     */
    private function buildRunBatchResult(
        ?string $message = null,
        int $batch = 0,
        int $pendingRemaining = 0,
        bool $hasMore = false,
        bool $stopped = false,
    ): array {
        $result = [
            'batch' => $batch,
            'migrations' => [],
            'has_more' => $hasMore,
            'stopped' => $stopped,
            'pending_remaining' => $pendingRemaining,
        ];

        if ($message !== null) {
            $result['message'] = $message;
        }

        return $result;
    }

    /**
     * Run single migration
     */
    private function runMigration(string $migration, int $batch): array
    {
        $start = microtime(true);

        // Insert as running
        $this->wpdb->insert($this->table, [
            'migration' => $migration,
            'batch' => $batch,
            'status' => 'running',
            'executed_at' => current_time('mysql')
        ]);

        try {
            $instance = $this->loadMigration($migration);
            $instance->up();

            $time = round((microtime(true) - $start) * 1000);

            // Update as completed
            $this->wpdb->update(
                $this->table,
                [
                    'status' => 'completed',
                    'execution_time_ms' => $time
                ],
                ['migration' => $migration]
            );

            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log("FleekDash Migration: {$migration} completed in {$time}ms");
            }

            return [
                'success' => true,
                'migration' => $migration,
                'time' => $time
            ];

        } catch (\Throwable $e) {
            // Update as failed
            $this->wpdb->update(
                $this->table,
                [
                    'status' => 'failed',
                    'error_message' => $e->getMessage()
                ],
                ['migration' => $migration]
            );

            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log("FleekDash Migration Failed: {$migration} - " . $e->getMessage());
            }

            return [
                'success' => false,
                'migration' => $migration,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ];
        }
    }

    /**
     * Rollback last batch
     */
    public function rollback(int $steps = 1): array
    {
        $results = ['rolled_back' => []];

        for ($i = 0; $i < $steps; $i++) {
            $batch = $this->getLastBatch();

            if (!$batch) {
                $results['message'] = 'Nothing to rollback';
                break;
            }

            $migrations = $this->wpdb->get_col($this->wpdb->prepare(
                "SELECT migration FROM {$this->table}
                 WHERE batch = %d AND status = 'completed'
                 ORDER BY id DESC",
                $batch
            ));

            foreach ($migrations as $migration) {
                $result = $this->rollbackMigration($migration);
                $results['rolled_back'][] = $result;

                if (!$result['success']) {
                    $results['stopped'] = true;
                    return $results;
                }
            }
        }

        return $results;
    }

    /**
     * Rollback single migration
     */
    private function rollbackMigration(string $migration): array
    {
        try {
            $instance = $this->loadMigration($migration);
            $instance->down();

            // Mark as rolled back
            $this->wpdb->update(
                $this->table,
                [
                    'status' => 'rolled_back',
                    'rolled_back_at' => current_time('mysql')
                ],
                ['migration' => $migration]
            );

            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log("FleekDash Migration Rolled Back: {$migration}");
            }

            return [
                'success' => true,
                'migration' => $migration
            ];

        } catch (\Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log("FleekDash Migration Rollback Failed: {$migration} - " . $e->getMessage());
            }

            return [
                'success' => false,
                'migration' => $migration,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Reset all migrations (rollback everything)
     */
    public function reset(): array
    {
        $total = $this->wpdb->get_var(
            "SELECT COUNT(*) FROM {$this->table} WHERE status = 'completed'"
        );

        return $this->rollback((int)$total);
    }

    /**
     * Refresh (reset + run)
     */
    public function refresh(): array
    {
        $reset = $this->reset();
        $run = $this->run();

        return [
            'reset' => $reset,
            'run' => $run
        ];
    }

    /**
     * Load migration instance
     */
    private function loadMigration(string $migration): BaseMigration
    {
        $file = $this->migrationsPath . $migration . '.php';

        if (!file_exists($file)) {
            throw new MigrationManagerException("Migration file not found: {$migration}");
        }

        // require (not require_once): migration files return a new anonymous instance each load.
        $instance = require $file; // NOSONAR php:S2003

        if (!$instance instanceof BaseMigration) {
            throw new MigrationManagerException(
                "Migration must return BaseMigration instance: {$migration}",
            );
        }

        return $instance;
    }

    /**
     * Get next batch number
     */
    private function getNextBatch(): int
    {
        $last = $this->wpdb->get_var(
            "SELECT MAX(batch) FROM {$this->table}"
        );

        return ($last ?: 0) + 1;
    }

    /**
     * Get last batch number
     */
    private function getLastBatch(): ?int
    {
        return $this->wpdb->get_var(
            "SELECT MAX(batch) FROM {$this->table} WHERE status = 'completed'"
        );
    }

    /**
     * Get migration status
     */
    public function status(): array
    {
        $all = $this->discover();
        $executed = $this->executed();
        $pending = array_diff($all, $executed);

        $failed = $this->wpdb->get_results(
            "SELECT migration, error_message FROM {$this->table} WHERE status = 'failed'",
            ARRAY_A
        );

        return [
            'total' => count($all),
            'executed' => count($executed),
            'pending' => count($pending),
            'failed' => count($failed),
            'last_batch' => $this->getLastBatch(),
            'migrations' => [
                'executed' => $executed,
                'pending' => array_values($pending),
                'failed' => $failed
            ]
        ];
    }

    /**
     * Check database health
     */
    public function health(): array
    {
        $health = [
            'status' => 'healthy',
            'checks' => []
        ];

        // Check 1: Migrations table exists
        $health['checks']['migrations_table'] = [
            'status' => MigrationInstaller::exists() ? 'pass' : 'fail',
            'message' => MigrationInstaller::exists()
                ? 'Migrations table exists'
                : 'Migrations table missing'
        ];

        // Check 2: No failed migrations
        $failed = $this->wpdb->get_var(
            "SELECT COUNT(*) FROM {$this->table} WHERE status = 'failed'"
        );
        $health['checks']['no_failed_migrations'] = [
            'status' => $failed == 0 ? 'pass' : 'fail',
            'message' => $failed == 0
                ? 'No failed migrations'
                : "{$failed} failed migration(s)"
        ];

        // Check 3: All tables exist
        $executed = $this->executed();
        $health['checks']['tables_exist'] = [
            'status' => 'pass',
            'message' => count($executed) . ' migrations executed'
        ];

        // Overall status
        foreach ($health['checks'] as $check) {
            if ($check['status'] === 'fail') {
                $health['status'] = 'unhealthy';
                break;
            }
        }

        return $health;
    }
}
