<?php

declare(strict_types=1);

namespace App\Framework\Database\Migration\Services;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\ValueObjects\MigrationTableConfig;
use App\Framework\Database\Platform\DatabasePlatform;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\Clock;

final readonly class MigrationDatabaseManager
{
    public function __construct(
        private ConnectionInterface $connection,
        private DatabasePlatform $platform,
        private Clock $clock,
        private MigrationTableConfig $tableConfig
    ) {
    }

    public function ensureMigrationsTable(): void
    {
        if ($this->tableExists($this->tableConfig->tableName)) {
            return;
        }

        $sql = $this->createMigrationsTableSQL($this->tableConfig->tableName);

        // PostgreSQL doesn't support multiple statements in prepared statements
        // Split by semicolon and execute each statement separately
        if ($this->platform->getName() === 'PostgreSQL') {
            $statements = array_filter(
                array_map('trim', explode(';', $sql)),
                fn($stmt) => !empty($stmt)
            );

            foreach ($statements as $statement) {
                $this->connection->execute(SqlQuery::create($statement));
            }
        } else {
            $this->connection->execute(SqlQuery::create($sql));
        }
    }

    public function recordMigrationExecution(Migration $migration, string $version): void
    {
        $now = $this->clock->now()->format('Y-m-d H:i:s');

        $sql = "INSERT INTO {$this->tableConfig->tableName} (version, description, executed_at) VALUES (?, ?, ?)";
        $this->connection->execute(SqlQuery::create($sql, [$version, $migration->getDescription(), $now]));
    }

    public function recordMigrationRollback(string $version): void
    {
        $sql = "DELETE FROM {$this->tableConfig->tableName} WHERE version = ?";
        $this->connection->execute(SqlQuery::create($sql, [$version]));
    }

    public function getAppliedVersions(): array
    {
        $this->ensureMigrationsTable();

        $sql = "SELECT version FROM {$this->tableConfig->tableName} ORDER BY executed_at ASC";

        return $this->connection->queryColumn(SqlQuery::create($sql));
    }

    public function tableExists(string $tableName): bool
    {
        try {
            $sql = $this->platform->getTableExistsSQL($tableName);
            $result = $this->connection->queryScalar(SqlQuery::create($sql));

            return (bool) $result;
        } catch (\Throwable $e) {
            return false;
        }
    }

    private function createMigrationsTableSQL(string $tableName): string
    {
        return match ($this->platform->getName()) {
            'mysql' => "CREATE TABLE {$tableName} (
                id INT AUTO_INCREMENT PRIMARY KEY,
                version VARCHAR(255) NOT NULL UNIQUE,
                description TEXT NOT NULL,
                executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_version (version),
                INDEX idx_executed_at (executed_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",

            'PostgreSQL' => "CREATE TABLE {$tableName} (
                id SERIAL PRIMARY KEY,
                version VARCHAR(255) NOT NULL UNIQUE,
                description TEXT NOT NULL,
                executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
            );
            CREATE INDEX idx_{$tableName}_version ON {$tableName} (version);
            CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);",

            'sqlite' => "CREATE TABLE {$tableName} (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                version TEXT NOT NULL UNIQUE,
                description TEXT NOT NULL,
                executed_at TEXT NOT NULL DEFAULT (datetime('now'))
            );
            CREATE INDEX idx_{$tableName}_version ON {$tableName} (version);
            CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);",

            default => throw new \RuntimeException("Unsupported database platform: {$this->platform->getName()}")
        };
    }
}
