<?php

declare(strict_types=1);

namespace App\Framework\Database\Schema;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Schema\Commands\{
    AlterTableCommand,
    CreateTableCommand,
    DropTableCommand,
    RenameTableCommand
};
use App\Framework\Database\ValueObjects\SqlQuery;

/**
 * Database schema builder for migrations
 */
final class Schema
{
    private array $commands = [];

    public function __construct(
        private readonly ConnectionInterface $connection
    ) {
    }

    /**
     * Create a new table
     * @param string $table
     * @param callable(Blueprint): void $callback
     * @param bool $ifNotExists - Default true for idempotent migrations
     */
    public function create(string $table, callable $callback, bool $ifNotExists = true): void
    {
        $blueprint = new Blueprint($table);
        if ($ifNotExists) {
            $blueprint->ifNotExists();
        }
        $callback($blueprint);

        $this->commands[] = new CreateTableCommand($table, $blueprint);
    }

    /**
     * Create a new table with IF NOT EXISTS
     * @param string $table
     * @param callable(Blueprint): void $callback
     */
    public function createIfNotExists(string $table, callable $callback): void
    {
        $blueprint = new Blueprint($table);
        $blueprint->ifNotExists();
        $callback($blueprint);

        $this->commands[] = new CreateTableCommand($table, $blueprint);
    }

    /**
     * Modify an existing table
     * @param string $table
     * @param callable(Blueprint): void $callback
     */
    public function table(string $table, callable $callback): void
    {
        $blueprint = new Blueprint($table);
        $callback($blueprint);

        $this->commands[] = new AlterTableCommand($table, $blueprint);
    }

    /**
     * Drop a table
     */
    public function drop(string $table): void
    {
        $this->commands[] = new DropTableCommand($table);
    }

    /**
     * Drop a table if it exists
     */
    public function dropIfExists(string $table): void
    {
        $this->commands[] = new DropTableCommand($table, true);
    }

    /**
     * Rename a table
     */
    public function rename(string $from, string $to): void
    {
        $this->commands[] = new RenameTableCommand($from, $to);
    }

    /**
     * Check if table exists
     */
    public function hasTable(string $table): bool
    {
        $driver = $this->getDriverName();

        $query = match($driver) {
            'mysql' => "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?",
            'pgsql' => "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = ?)",
            'sqlite' => "SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
            default => throw new \RuntimeException("Unsupported driver: {$driver}")
        };

        $result = $this->connection->queryScalar($query, [$table]);

        return (bool) $result;
    }

    /**
     * Check if column exists
     */
    public function hasColumn(string $table, string $column): bool
    {
        $driver = $this->getDriverName();

        $query = match($driver) {
            'mysql' => "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
            'pgsql' => "SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?",
            'sqlite' => "PRAGMA table_info({$table})",
            default => throw new \RuntimeException("Unsupported driver: {$driver}")
        };

        if ($driver === 'pgsql') {
            $result = $this->connection->queryScalar($query, [$table, $column]);
        } elseif ($driver === 'sqlite') {
            $columns = $this->connection->query($query)->fetchAll();
            foreach ($columns as $col) {
                if ($col['name'] === $column) {
                    return true;
                }
            }

            return false;
        } elseif ($driver === 'mysql') {
            $result = $this->connection->queryScalar($query, [$table, $column]);
        } else {
            throw new \RuntimeException("Unsupported driver: {$driver}");
        }

        return (bool) $result;
    }

    /**
     * Execute all schema commands
     */
    public function execute(): void
    {
        if (empty($this->commands)) {
            return;
        }

        $statements = $this->toSql();

        // If we're already in a transaction (e.g., from MigrationRunner),
        // execute statements directly without managing our own transaction
        if ($this->connection->inTransaction()) {
            foreach ($statements as $statement) {
                $this->connection->execute(SqlQuery::create($statement));
            }
        } else {
            // We're not in a transaction, so manage our own
            $this->connection->beginTransaction();

            try {
                foreach ($statements as $statement) {
                    $this->connection->execute(SqlQuery::create($statement));
                }
                $this->connection->commit();
            } catch (\Throwable $e) {
                $this->connection->rollback();

                throw $e;
            }
        }

        $this->commands = [];
    }

    /**
     * Get SQL statements without executing
     */
    public function toSql(): array
    {
        $compiler = $this->createCompiler();
        $statements = [];

        foreach ($this->commands as $command) {
            $sql = $compiler->compile($command);

            if (is_array($sql)) {
                $statements = array_merge($statements, $sql);
            } else {
                $statements[] = $sql;
            }
        }

        return $statements;
    }

    /**
     * Create compiler for current database driver
     */
    private function createCompiler(): SchemaCompiler
    {
        $driver = $this->getDriverName();

        return match($driver) {
            'mysql' => new MySQLSchemaCompiler(),
            'pgsql' => new PostgreSQLSchemaCompiler(),
            'sqlite' => new SQLiteSchemaCompiler(),
            default => throw new \RuntimeException("Unsupported driver: {$driver}")
        };
    }

    /**
     * Get database driver name
     */
    private function getDriverName(): string
    {
        $pdo = $this->connection->getPdo();

        return $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
    }
}
