<?php

declare(strict_types=1);

namespace App\Framework\Database\Migration;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\Transaction;

final class MigrationRunner
{
    private ConnectionInterface $connection;
    private string $migrationsTable;

    public function __construct(ConnectionInterface $connection, string $migrationsTable = 'migrations')
    {
        $this->connection = $connection;
        $this->migrationsTable = $migrationsTable;
        $this->ensureMigrationsTable();
    }

    /**
     * @param Migration[] $migrations
     */
    public function migrate(array $migrations): array
    {
        $executedMigrations = [];
        $appliedVersions = $this->getAppliedVersions();

        foreach ($migrations as $migration) {
            if (in_array($migration->getVersion(), $appliedVersions, true)) {
                continue;
            }

            try {
                Transaction::run($this->connection, function() use ($migration) {
                    echo "Migrating: {$migration->getVersion()} - {$migration->getDescription()}\n";

                    $migration->up($this->connection);

                    $this->connection->execute(
                        "INSERT INTO {$this->migrationsTable} (version, description, executed_at) VALUES (?, ?, ?)",
                        [$migration->getVersion(), $migration->getDescription(), date('Y-m-d H:i:s')]
                    );
                });

                $executedMigrations[] = $migration->getVersion();
                echo "Migrated: {$migration->getVersion()}\n";
            } catch (\Throwable $e) {
                throw new DatabaseException(
                    "Migration {$migration->getVersion()} failed: {$e->getMessage()}",
                    0,
                    $e
                );
            }
        }

        return $executedMigrations;
    }

    /**
     * @param Migration[] $migrations
     */
    public function rollback(array $migrations, int $steps = 1): array
    {
        $rolledBackMigrations = [];
        $appliedVersions = $this->getAppliedVersions();

        $sortedMigrations = $migrations;
        usort($sortedMigrations, fn($a, $b) => $b->getVersion() <=> $a->getVersion());

        $count = 0;
        foreach ($sortedMigrations as $migration) {
            if ($count >= $steps) {
                break;
            }

            if (!in_array($migration->getVersion(), $appliedVersions, true)) {
                continue;
            }

            try {
                Transaction::run($this->connection, function() use ($migration) {
                    echo "Rolling back: {$migration->getVersion()} - {$migration->getDescription()}\n";

                    $migration->down($this->connection);

                    $this->connection->execute(
                        "DELETE FROM {$this->migrationsTable} WHERE version = ?",
                        [$migration->getVersion()]
                    );
                });

                $rolledBackMigrations[] = $migration->getVersion();
                $count++;
                echo "Rolled back: {$migration->getVersion()}\n";
            } catch (\Throwable $e) {
                throw new DatabaseException(
                    "Rollback {$migration->getVersion()} failed: {$e->getMessage()}",
                    0,
                    $e
                );
            }
        }

        return $rolledBackMigrations;
    }

    public function getStatus(array $migrations): array
    {
        $appliedVersions = $this->getAppliedVersions();
        $status = [];

        foreach ($migrations as $migration) {
            $status[] = [
                'version' => $migration->getVersion(),
                'description' => $migration->getDescription(),
                'applied' => in_array($migration->getVersion(), $appliedVersions, true),
            ];
        }

        return $status;
    }

    private function getAppliedVersions(): array
    {
        return $this->connection->queryColumn(
            "SELECT version FROM {$this->migrationsTable} ORDER BY executed_at"
        );
    }

    private function ensureMigrationsTable(): void
    {
        $sql = "CREATE TABLE IF NOT EXISTS {$this->migrationsTable} (
            id INT PRIMARY KEY AUTO_INCREMENT,
            version VARCHAR(255) NOT NULL UNIQUE,
            description TEXT,
            executed_at DATETIME NOT NULL,
            INDEX idx_version (version)
        )";

        $this->connection->execute($sql);
    }
}
