<?php

declare(strict_types=1);

namespace App\Core;

use PDO;
use PDOException;
use PDOStatement;
use RuntimeException;

class Database
{
    private static ?PDO $connection = null;

    public static function connect(array $config): PDO
    {
        if (self::$connection instanceof PDO) {
            return self::$connection;
        }

        $dsn = sprintf(
            'mysql:host=%s;port=%s;dbname=%s;charset=%s',
            $config['host'],
            $config['port'],
            $config['database'],
            $config['charset'] ?? 'utf8mb4'
        );

        try {
            self::$connection = new PDO(
                $dsn,
                (string) $config['username'],
                (string) $config['password'],
                [
                    PDO::ATTR_ERRMODE =>
                        PDO::ERRMODE_EXCEPTION,

                    PDO::ATTR_DEFAULT_FETCH_MODE =>
                        PDO::FETCH_ASSOC,

                    PDO::ATTR_EMULATE_PREPARES =>
                        false,
                ]
            );

            return self::$connection;
        } catch (PDOException $exception) {
            throw new RuntimeException(
                'Database connection failed: '
                . $exception->getMessage()
            );
        }
    }

    public static function connection(): PDO
    {
        if (!self::$connection instanceof PDO) {
            throw new RuntimeException(
                'Database connection has not been initialised.'
            );
        }

        return self::$connection;
    }

    public static function query(
        string $sql,
        array $bindings = []
    ): PDOStatement {
        $statement = self::connection()->prepare($sql);
        $statement->execute($bindings);

        return $statement;
    }

    public static function fetch(
        string $sql,
        array $bindings = []
    ): ?array {
        $result = self::query(
            $sql,
            $bindings
        )->fetch();

        return $result ?: null;
    }

    public static function fetchAll(
        string $sql,
        array $bindings = []
    ): array {
        return self::query(
            $sql,
            $bindings
        )->fetchAll();
    }

    public static function insert(
        string $sql,
        array $bindings = []
    ): int {
        self::query($sql, $bindings);

        return (int) self::connection()->lastInsertId();
    }

    public static function execute(
        string $sql,
        array $bindings = []
    ): bool {
        return self::query(
            $sql,
            $bindings
        )->rowCount() >= 0;
    }
}