<?php
declare(strict_types=1);

namespace App\Core\Migration;

use App\Core\Database;

final class MigrationRunner
{
    private Database $db;
    private string $migrationsDir;

    public function __construct(Database $db)
    {
        $this->db = $db;
        $this->migrationsDir = dirname(__DIR__, 2) . '/../database/migrations';
        $this->migrationsDir = realpath($this->migrationsDir) ?: (dirname(__DIR__, 3) . '/database/migrations');
    }

    public function migrate(): array
    {
        $this->ensureMigrationsTable();
        $executed = $this->getExecutedMigrations();
        $files = $this->getMigrationFiles();
        $batch = $this->getNextBatch();
        $results = [];

        foreach ($files as $file) {
            $name = basename($file, '.php');
            if (in_array($name, $executed)) {
                continue;
            }

            echo "   Migrating: {$name}...\n";

            try {
                $migration = require $file;

                if (is_array($migration) && isset($migration['up'])) {
                    $upSql = $migration['up'];
                    if (is_callable($upSql)) {
                        $upSql($this->db);
                    } else {
                        $statements = $this->splitStatements((string) $upSql);
                        foreach ($statements as $stmt) {
                            $stmt = trim($stmt);
                            if ($stmt !== '') {
                                $this->db->raw($stmt);
                            }
                        }
                    }
                } elseif (is_object($migration) && method_exists($migration, 'up')) {
                    $migration->up($this->db);
                } elseif (is_callable($migration)) {
                    $migration($this->db);
                }

                $this->db->insert('migrations', [
                    'migration'   => $name,
                    'batch'       => $batch,
                    'executed_at' => date('Y-m-d H:i:s'),
                ]);

                $results[] = $name;
                echo "   ✔ {$name}\n";
            } catch (\Throwable $e) {
                echo "   ❌ {$name} FAILED: " . $e->getMessage() . "\n";
                // Continue to next migration — don't let one failure kill the whole batch
                continue;
            }
        }

        return $results;
    }

    public function rollback(): array
    {
        $this->ensureMigrationsTable();
        $lastBatch = $this->getLastBatch();
        if ($lastBatch === 0) {
            return [];
        }

        $rows = $this->db->select(
            "SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC",
            [$lastBatch]
        );

        $results = [];
        foreach ($rows as $row) {
            $name = $row['migration'];
            $file = $this->migrationsDir . '/' . $name . '.php';

            if (file_exists($file)) {
                echo "   Rolling back: {$name}...\n";
                try {
                    $migration = require $file;

                    if (is_array($migration) && isset($migration['down'])) {
                        $downSql = $migration['down'];
                        if (is_callable($downSql)) {
                            $downSql($this->db);
                        } else {
                            $statements = $this->splitStatements((string) $downSql);
                            foreach ($statements as $stmt) {
                                $stmt = trim($stmt);
                                if ($stmt !== '') {
                                    $this->db->raw($stmt);
                                }
                            }
                        }
                    } elseif (is_object($migration) && method_exists($migration, 'down')) {
                        $migration->down($this->db);
                    }
                } catch (\Throwable $e) {
                    echo "   ⚠️  Rollback warning: " . $e->getMessage() . "\n";
                }
            }

            $this->db->query("DELETE FROM migrations WHERE migration = ?", [$name]);
            $results[] = $name;
        }

        return $results;
    }

    public function status(): array
    {
        $this->ensureMigrationsTable();
        $executed = $this->getExecutedMigrations();
        $files = $this->getMigrationFiles();
        $status = [];

        foreach ($files as $file) {
            $name = basename($file, '.php');
            $status[] = [
                'migration' => $name,
                'status'    => in_array($name, $executed) ? 'Ran' : 'Pending',
            ];
        }

        return $status;
    }

    private function ensureMigrationsTable(): void
    {
        try {
            $this->db->selectOne("SELECT 1 FROM migrations LIMIT 1");
        } catch (\Throwable $e) {
            $this->db->raw("
                CREATE TABLE IF NOT EXISTS `migrations` (
                    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                    `migration` VARCHAR(255) NOT NULL,
                    `batch` INT UNSIGNED NOT NULL,
                    `executed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    UNIQUE KEY `uq_migrations_name` (`migration`)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            ");
        }
    }

    private function getExecutedMigrations(): array
    {
        try {
            $rows = $this->db->select("SELECT migration FROM migrations ORDER BY id");
            return array_column($rows, 'migration');
        } catch (\Throwable $e) {
            return [];
        }
    }

    private function getMigrationFiles(): array
    {
        if (!is_dir($this->migrationsDir)) {
            echo "   ⚠️  Migrations directory not found: {$this->migrationsDir}\n";
            return [];
        }

        $files = glob($this->migrationsDir . '/Phase_*.php');
        if ($files === false || empty($files)) {
            return [];
        }
        sort($files);
        return $files;
    }

    private function getNextBatch(): int
    {
        return $this->getLastBatch() + 1;
    }

    private function getLastBatch(): int
    {
        try {
            $row = $this->db->selectOne("SELECT MAX(batch) as max_batch FROM migrations");
            return (int) ($row['max_batch'] ?? 0);
        } catch (\Throwable $e) {
            return 0;
        }
    }

    private function splitStatements(string $sql): array
    {
        $sql = trim($sql);
        if ($sql === '') {
            return [];
        }

        $statements = preg_split('/;\s*\n/', $sql);
        if ($statements === false) {
            return [$sql];
        }

        $result = [];
        foreach ($statements as $stmt) {
            $stmt = trim($stmt);
            $stmt = rtrim($stmt, ';');
            $stmt = trim($stmt);
            if ($stmt !== '') {
                $result[] = $stmt;
            }
        }

        return $result;
    }
}