<?php
require_once implode(DIRECTORY_SEPARATOR, [
    __DIR__,
    'vendor',
    'autoload.php'
]);

class Benchmark
{
    protected static array $queue = [];
    public static function print(): void
    {
        if (empty(self::$queue)) {
            return;
        }
        echo implode(PHP_EOL, self::$queue), PHP_EOL;
        self::$queue = [];
    }
    public static function execute(callable $callback, ?array $args = null, ?string $description = null): mixed
    {
        if (null === $description) {
            $description = self::getCallableName($callback);
        }
        $i = count(self::$queue);
        self::$queue[$i] = "Executing $description";
        $start = microtime(true);

        if (null === $args) {
            $result = call_user_func($callback);
        } else {
            $result = call_user_func_array($callback, $args);
        }

        $end = microtime(true);
        self::$queue[$i] .= sprintf(" in %.2f seconds", $end - $start);
        return $result;
    }
    public static function getCallableName(callable $callable): string
    {
        return match (true) {
            is_string($callable) && strpos($callable, '::') => '[static] ' . $callable,
            is_string($callable) => '[function] ' . $callable,
            is_array($callable) && is_object($callable[0]) => '[method] ' . get_class($callable[0]) . '->' . $callable[1],
            is_array($callable) => '[static] ' . $callable[0] . '::' . $callable[1],
            $callable instanceof Closure => '[closure]',
            is_object($callable) => '[invokable] ' . get_class($callable),
            default => '[unknown]',
        };
    }
}
class TestBootstrap
{
    public function __construct(protected array $configuration) {}

    public function run(): void
    {
        if (Benchmark::execute([$this, 'isMigrated'])) {
            Benchmark::execute([$this, 'resetDatabase']);
        }
        Benchmark::execute([$this, 'migrate']);
    }

    protected string $baseCommand = "bin/phinx";
    public function isMigrated(): bool
    {
        $cmd = "{$this->baseCommand} status -e testing -f json --no-info";
        $output = shell_exec($cmd);
        $status = json_decode($output, true);

        return $status['missing_count'] > 0;
    }
    public function migrate(): void
    {
        $cmd = "{$this->baseCommand} migrate -e testing";
        $status = shell_exec($cmd);
        if ($status !== false and $status !== null) {
            $cmd = "{$this->baseCommand} seed:run -e testing";
            shell_exec($cmd);
        }
    }

    public function resetDatabase(): void
    {
        $tables = $this->getTables();
        if ($this->connect()->beginTransaction()) {
            try {
                $this->connect()->query("SET FOREIGN_KEY_CHECKS=0;");
                foreach ($tables as $table) {
                    $this->connect()->query("DROP TABLE IF EXISTS `{$table}`");
                }
                $this->connect()->query("SET FOREIGN_KEY_CHECKS=1;");
                if ($this->connect()->inTransaction()) {
                    $this->connect()->commit();
                }
            } catch (PDOException $exception) {
                if ($this->connect()->inTransaction()) {
                    $this->connect()->rollBack();
                }
                throw $exception;
            }
        }
    }
    public function truncateTables(): void
    {
        $tables = $this->getTables();
        if ($this->connect()->beginTransaction()) {
            try {
                $this->connect()->query("SET FOREIGN_KEY_CHECKS=0;");
                foreach ($tables as $table) {
                    $this->connect()->query("TRUNCATE TABLE `{$table}`");
                }
                $this->connect()->query("SET FOREIGN_KEY_CHECKS=1;");
                if ($this->connect()->inTransaction()) {
                    $this->connect()->commit();
                }
            } catch (PDOException $exception) {
                if ($this->connect()->inTransaction()) {
                    $this->connect()->rollBack();
                }
                throw $exception;
            }
        }
    }

    protected PDO $connection;
    protected function connect(): PDO
    {
        if (!isset($this->connection)) {
            $dsn = "mysql:host={$this->configuration['DB_HOST']};dbname={$this->configuration['DB_DATABASE']}";
            $this->connection = new PDO(
                $dsn,
                $this->configuration['DB_USER'],
                $this->configuration['DB_PASSWORD']
            );
        }
        return $this->connection;
    }
    protected array $tables;
    protected function getTables(): array
    {
        if (!isset($this->tables)) {
            $this->tables = $this->connect()->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
        }
        return $this->tables;
    }
}

spl_autoload_register(function($className) {
    $baseTestPath = __DIR__ . "/tests";
    $namespaceMap = [
        "ProVM\\Tests\\Extension\\" => "{$baseTestPath}/extension",
        "ProVM\\Integration\\" => "{$baseTestPath}/integration",
        "ProVM\\Unit\\" => "{$baseTestPath}/unit/src",
        "ProVM\\Performance\\" => "{$baseTestPath}/performance",
    ];
    foreach ($namespaceMap as $namespace => $path) {
        if (str_starts_with($className, $namespace)) {
            require str_replace($namespace, "{$path}/", $className) . ".php";
            return;
        }
    }
});

$bootstrap = new TestBootstrap($_ENV);
Benchmark::execute([$bootstrap, 'run']);
Benchmark::print();

register_shutdown_function(function() use ($bootstrap) {
    Benchmark::execute([$bootstrap, 'truncateTables']);
    Benchmark::print();
});
