<?php

declare(strict_types=1);

namespace FatBottom\Core;

use PDO;
use RuntimeException;

final class Database
{
    private PDO $pdo;
    private bool $sqlite;

    public function __construct(private readonly Config $config)
    {
        $driver = (string) $config->get('database.driver', 'sqlite');
        $this->sqlite = $driver !== 'mysql';

        if ($this->sqlite) {
            $directory = BASE_PATH . '/storage/database';
            if (!is_dir($directory)) {
                mkdir($directory, 0775, true);
            }
            $this->pdo = new PDO('sqlite:' . $directory . '/store.sqlite');
            $this->pdo->exec('PRAGMA foreign_keys = ON');
            $this->pdo->exec('PRAGMA journal_mode = WAL');
        } else {
            $dsn = sprintf(
                'mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4',
                $config->get('database.mysql_host'),
                (int) $config->get('database.mysql_port', 3306),
                $config->get('database.mysql_database')
            );
            $this->pdo = new PDO(
                $dsn,
                (string) $config->get('database.mysql_username'),
                (string) $config->get('database.mysql_password')
            );
            $this->pdo->exec("SET time_zone = '+00:00'");
        }

        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
        $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    }

    public function pdo(): PDO
    {
        return $this->pdo;
    }

    public function isSqlite(): bool
    {
        return $this->sqlite;
    }

    public function migrate(): void
    {
        if (!$this->sqlite) {
            return;
        }

        $schema = <<<'SQL'
CREATE TABLE IF NOT EXISTS app_migrations (
    id TEXT PRIMARY KEY,
    applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    full_name TEXT NOT NULL,
    phone TEXT NOT NULL DEFAULT '',
    street_address TEXT NOT NULL DEFAULT '',
    city TEXT NOT NULL DEFAULT '',
    state TEXT NOT NULL DEFAULT '',
    postal_code TEXT NOT NULL DEFAULT '',
    role TEXT NOT NULL DEFAULT 'customer',
    newsletter_opt_in INTEGER NOT NULL DEFAULT 1,
    two_factor_enabled INTEGER NOT NULL DEFAULT 0,
    email_verified_at TEXT,
    active INTEGER NOT NULL DEFAULT 1,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS login_codes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    code_hash TEXT NOT NULL,
    purpose TEXT NOT NULL,
    expires_at TEXT NOT NULL,
    used_at TEXT,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS menu_categories (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    slug TEXT NOT NULL UNIQUE,
    description TEXT NOT NULL DEFAULT '',
    display_order INTEGER NOT NULL DEFAULT 0,
    active INTEGER NOT NULL DEFAULT 1,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS menu_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    category_id INTEGER NOT NULL,
    name TEXT NOT NULL,
    slug TEXT NOT NULL UNIQUE,
    description TEXT NOT NULL DEFAULT '',
    price_cents INTEGER NOT NULL,
    compare_at_cents INTEGER,
    image_url TEXT NOT NULL DEFAULT '',
    dietary_tags TEXT NOT NULL DEFAULT '[]',
    featured INTEGER NOT NULL DEFAULT 0,
    active INTEGER NOT NULL DEFAULT 1,
    display_order INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (category_id) REFERENCES menu_categories(id)
);

CREATE TABLE IF NOT EXISTS specials (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    menu_item_id INTEGER,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    badge TEXT NOT NULL DEFAULT 'Special',
    price_cents INTEGER,
    starts_at TEXT,
    ends_at TEXT,
    active INTEGER NOT NULL DEFAULT 1,
    display_order INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (menu_item_id) REFERENCES menu_items(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS tax_rates (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    jurisdiction TEXT NOT NULL,
    rate_basis_points INTEGER NOT NULL,
    active INTEGER NOT NULL DEFAULT 1,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS carts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    token TEXT NOT NULL UNIQUE,
    user_id INTEGER,
    status TEXT NOT NULL DEFAULT 'active',
    subtotal_cents INTEGER NOT NULL DEFAULT 0,
    last_seen_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    converted_at TEXT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS cart_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cart_id INTEGER NOT NULL,
    menu_item_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL DEFAULT 1,
    unit_price_cents INTEGER NOT NULL,
    notes TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(cart_id, menu_item_id, notes),
    FOREIGN KEY (cart_id) REFERENCES carts(id) ON DELETE CASCADE,
    FOREIGN KEY (menu_item_id) REFERENCES menu_items(id)
);

CREATE TABLE IF NOT EXISTS customers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER,
    email TEXT NOT NULL UNIQUE,
    full_name TEXT NOT NULL,
    phone TEXT NOT NULL DEFAULT '',
    active INTEGER NOT NULL DEFAULT 1,
    internal_notes TEXT NOT NULL DEFAULT '',
    order_count INTEGER NOT NULL DEFAULT 0,
    lifetime_value_cents INTEGER NOT NULL DEFAULT 0,
    first_order_at TEXT,
    last_order_at TEXT,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_number TEXT NOT NULL UNIQUE,
    user_id INTEGER,
    customer_id INTEGER,
    cart_id INTEGER,
    tracking_token TEXT NOT NULL DEFAULT '',
    customer_name TEXT NOT NULL,
    customer_email TEXT NOT NULL,
    customer_phone TEXT NOT NULL,
    order_type TEXT NOT NULL DEFAULT 'pickup',
    status TEXT NOT NULL DEFAULT 'pending',
    payment_status TEXT NOT NULL DEFAULT 'paid',
    payment_provider TEXT NOT NULL DEFAULT 'demo',
    payment_reference TEXT NOT NULL DEFAULT '',
    subtotal_cents INTEGER NOT NULL,
    tax_cents INTEGER NOT NULL,
    total_cents INTEGER NOT NULL,
    special_instructions TEXT NOT NULL DEFAULT '',
    pickup_time TEXT,
    placed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
    FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL,
    FOREIGN KEY (cart_id) REFERENCES carts(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS order_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id INTEGER NOT NULL,
    menu_item_id INTEGER,
    item_name TEXT NOT NULL,
    quantity INTEGER NOT NULL,
    unit_price_cents INTEGER NOT NULL,
    line_total_cents INTEGER NOT NULL,
    notes TEXT NOT NULL DEFAULT '',
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (menu_item_id) REFERENCES menu_items(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS order_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id INTEGER NOT NULL,
    user_id INTEGER,
    event_type TEXT NOT NULL,
    details TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT '',
    note TEXT NOT NULL DEFAULT '',
    customer_visible INTEGER NOT NULL DEFAULT 0,
    email_sent INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS refunds (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id INTEGER NOT NULL,
    staff_user_id INTEGER,
    amount_cents INTEGER NOT NULL,
    reason TEXT NOT NULL DEFAULT '',
    provider_reference TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (staff_user_id) REFERENCES users(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS visitor_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_id TEXT NOT NULL,
    user_id INTEGER,
    route TEXT NOT NULL,
    referrer TEXT NOT NULL DEFAULT '',
    user_agent TEXT NOT NULL DEFAULT '',
    ip_hash TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS newsletters (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    subject TEXT NOT NULL,
    content_html TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'draft',
    recipient_count INTEGER NOT NULL DEFAULT 0,
    created_by INTEGER,
    sent_at TEXT,
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
);

CREATE INDEX IF NOT EXISTS idx_menu_items_category ON menu_items(category_id, active);
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status, placed_at);
CREATE INDEX IF NOT EXISTS idx_carts_status ON carts(status, last_seen_at);
CREATE INDEX IF NOT EXISTS idx_visitors_created ON visitor_events(created_at);
CREATE INDEX IF NOT EXISTS idx_users_newsletter ON users(newsletter_opt_in, active);
SQL;

        $this->pdo->exec($schema);
        $this->ensureSqliteColumn('users', 'two_factor_enabled', 'INTEGER NOT NULL DEFAULT 0');
        $this->ensureSqliteColumn('orders', 'customer_id', 'INTEGER');
        $this->ensureSqliteColumn('orders', 'tracking_token', "TEXT NOT NULL DEFAULT ''");
        $this->ensureSqliteColumn('order_events', 'status', "TEXT NOT NULL DEFAULT ''");
        $this->ensureSqliteColumn('order_events', 'note', "TEXT NOT NULL DEFAULT ''");
        $this->ensureSqliteColumn('order_events', 'customer_visible', 'INTEGER NOT NULL DEFAULT 0');
        $this->ensureSqliteColumn('order_events', 'email_sent', 'INTEGER NOT NULL DEFAULT 0');
        $this->backfillOrderTrackingTokens();
        $this->runMigration('20260614_order_status_crm', function (): void {
            $this->normalizeLegacyOrderStatuses();
            $this->syncCustomersFromOrders();
        });
        $this->pdo->exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_orders_tracking_token ON orders(tracking_token)');
        $this->pdo->exec('CREATE INDEX IF NOT EXISTS idx_orders_customer ON orders(customer_id, placed_at)');
        $this->pdo->exec('CREATE INDEX IF NOT EXISTS idx_customers_active ON customers(active, last_order_at)');
        $this->seed();
    }

    private function ensureSqliteColumn(string $table, string $column, string $definition): void
    {
        $columns = $this->pdo->query("PRAGMA table_info(`{$table}`)")->fetchAll();
        foreach ($columns as $existing) {
            if (($existing['name'] ?? '') === $column) {
                return;
            }
        }

        $this->pdo->exec("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
    }

    private function runMigration(string $id, callable $migration): void
    {
        $statement = $this->pdo->prepare('SELECT id FROM app_migrations WHERE id = ?');
        $statement->execute([$id]);
        if ($statement->fetchColumn() !== false) {
            return;
        }

        $this->pdo->beginTransaction();
        try {
            $migration();
            $this->pdo->prepare('INSERT INTO app_migrations (id) VALUES (?)')->execute([$id]);
            $this->pdo->commit();
        } catch (\Throwable $error) {
            $this->pdo->rollBack();
            throw $error;
        }
    }

    private function backfillOrderTrackingTokens(): void
    {
        $orders = $this->pdo->query(
            "SELECT id FROM orders WHERE tracking_token IS NULL OR tracking_token = ''"
        )->fetchAll();
        $update = $this->pdo->prepare('UPDATE orders SET tracking_token = ? WHERE id = ?');
        foreach ($orders as $order) {
            $update->execute([bin2hex(random_bytes(24)), (int) $order['id']]);
        }
    }

    private function normalizeLegacyOrderStatuses(): void
    {
        $this->pdo->exec("UPDATE orders SET status = 'pending' WHERE status = 'paid'");
        $this->pdo->exec("UPDATE orders SET status = 'processing' WHERE status = 'preparing'");
        $this->pdo->exec("UPDATE orders SET status = 'declined' WHERE status = 'cancelled'");
    }

    private function syncCustomersFromOrders(): void
    {
        $orders = $this->pdo->query(
            'SELECT id, user_id, customer_name, customer_email, customer_phone
             FROM orders ORDER BY placed_at, id'
        )->fetchAll();
        $findCustomer = $this->pdo->prepare('SELECT id, user_id FROM customers WHERE email = ?');
        $findUser = $this->pdo->prepare('SELECT id FROM users WHERE email = ?');
        $insert = $this->pdo->prepare(
            'INSERT INTO customers (user_id, email, full_name, phone) VALUES (?, ?, ?, ?)'
        );
        $update = $this->pdo->prepare(
            'UPDATE customers SET user_id = ?, full_name = ?, phone = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
        );
        $linkOrder = $this->pdo->prepare('UPDATE orders SET customer_id = ? WHERE id = ?');
        $customerIds = [];

        foreach ($orders as $order) {
            $email = strtolower(trim((string) $order['customer_email']));
            if ($email === '') {
                continue;
            }
            $findCustomer->execute([$email]);
            $customer = $findCustomer->fetch();
            $userId = (int) ($order['user_id'] ?? 0) ?: null;
            if ($userId === null) {
                $findUser->execute([$email]);
                $userId = (int) ($findUser->fetchColumn() ?: 0) ?: null;
            }
            if ($customer === false) {
                $insert->execute([$userId, $email, $order['customer_name'], $order['customer_phone']]);
                $customerId = (int) $this->pdo->lastInsertId();
            } else {
                $customerId = (int) $customer['id'];
                $update->execute([
                    $userId ?: ((int) ($customer['user_id'] ?? 0) ?: null),
                    $order['customer_name'],
                    $order['customer_phone'],
                    $customerId,
                ]);
            }
            $linkOrder->execute([$customerId, (int) $order['id']]);
            $customerIds[$customerId] = true;
        }

        $refresh = $this->pdo->prepare(
            'UPDATE customers SET
                order_count = (SELECT COUNT(*) FROM orders WHERE customer_id = ?),
                lifetime_value_cents = COALESCE((SELECT SUM(total_cents) FROM orders WHERE customer_id = ?), 0),
                first_order_at = (SELECT MIN(placed_at) FROM orders WHERE customer_id = ?),
                last_order_at = (SELECT MAX(placed_at) FROM orders WHERE customer_id = ?),
                updated_at = CURRENT_TIMESTAMP
             WHERE id = ?'
        );
        foreach (array_keys($customerIds) as $customerId) {
            $refresh->execute([$customerId, $customerId, $customerId, $customerId, $customerId]);
        }
    }

    private function seed(): void
    {
        $categoryCount = (int) $this->pdo->query('SELECT COUNT(*) FROM menu_categories')->fetchColumn();
        if ($categoryCount > 0) {
            return;
        }

        $this->pdo->beginTransaction();
        try {
            $categories = [
                ['Smash Burgers', 'smash-burgers', 'Hand-pressed beef, crisp edges, and the good stuff.', 10],
                ['Burritos & Bowls', 'burritos-bowls', 'Big, packed, and built your way.', 20],
                ['Dinner Plates', 'dinner-plates', 'Comfort-food plates made for a proper supper.', 30],
                ['Sides', 'sides', 'The supporting cast that tends to steal the show.', 40],
                ['Drinks', 'drinks', 'Cold drinks for hot food.', 50],
            ];
            $categoryStatement = $this->pdo->prepare(
                'INSERT INTO menu_categories (name, slug, description, display_order) VALUES (?, ?, ?, ?)'
            );
            foreach ($categories as $category) {
                $categoryStatement->execute($category);
            }

            $categoryIds = [];
            foreach ($this->pdo->query('SELECT id, slug FROM menu_categories')->fetchAll() as $category) {
                $categoryIds[$category['slug']] = (int) $category['id'];
            }

            $items = [
                ['smash-burgers', 'The Fat Bottom', 'the-fat-bottom', 'Two smashed patties, American cheese, grilled onion, pickles, and house sauce.', 1199, 1, '["House favorite"]'],
                ['smash-burgers', 'Piedmont Bacon Burger', 'piedmont-bacon-burger', 'Two patties, smoked bacon, cheddar, lettuce, tomato, onion, and burger sauce.', 1349, 1, '[]'],
                ['smash-burgers', 'Allegheny Melt', 'allegheny-melt', 'Smashed beef, Swiss, caramelized onions, and peppercorn mayo on toasted sourdough.', 1299, 0, '[]'],
                ['burritos-bowls', 'Mountaineer Burrito', 'mountaineer-burrito', 'Seasoned beef, rice, black beans, queso, pico, lettuce, and crema.', 1249, 1, '["Big appetite"]'],
                ['burritos-bowls', 'Chicken Fajita Bowl', 'chicken-fajita-bowl', 'Grilled chicken, peppers, onions, rice, corn salsa, avocado crema.', 1199, 0, '["Gluten conscious"]'],
                ['dinner-plates', 'Country Fried Chicken', 'country-fried-chicken', 'Crispy chicken, peppered gravy, mashed potatoes, and green beans.', 1499, 1, '[]'],
                ['dinner-plates', 'Keyser Meatloaf', 'keyser-meatloaf', 'House meatloaf with brown gravy, mashed potatoes, and seasonal vegetables.', 1449, 0, '[]'],
                ['sides', 'Loaded Fries', 'loaded-fries', 'Crispy fries, queso, bacon, scallions, and ranch drizzle.', 699, 1, '[]'],
                ['sides', 'Mac & Cheese', 'mac-cheese', 'Creamy, baked, and unapologetically cheesy.', 449, 0, '["Vegetarian"]'],
                ['drinks', 'Sweet Tea', 'sweet-tea', 'Fresh-brewed southern sweet tea.', 299, 0, '[]'],
                ['drinks', 'Fountain Drink', 'fountain-drink', 'Your choice of fountain soda.', 299, 0, '[]'],
            ];
            $itemStatement = $this->pdo->prepare(
                'INSERT INTO menu_items
                    (category_id, name, slug, description, price_cents, featured, dietary_tags)
                 VALUES (?, ?, ?, ?, ?, ?, ?)'
            );
            foreach ($items as $item) {
                $itemStatement->execute([
                    $categoryIds[$item[0]],
                    $item[1],
                    $item[2],
                    $item[3],
                    $item[4],
                    $item[5],
                    $item[6],
                ]);
            }

            $fatBottomId = (int) $this->pdo->query(
                "SELECT id FROM menu_items WHERE slug = 'the-fat-bottom'"
            )->fetchColumn();
            $loadedFriesId = (int) $this->pdo->query(
                "SELECT id FROM menu_items WHERE slug = 'loaded-fries'"
            )->fetchColumn();
            $specialStatement = $this->pdo->prepare(
                'INSERT INTO specials (menu_item_id, title, description, badge, price_cents, display_order)
                 VALUES (?, ?, ?, ?, ?, ?)'
            );
            $specialStatement->execute([
                $fatBottomId,
                'The Fat Bottom Deal',
                'Our signature burger at a special online price.',
                'Online Special',
                1099,
                10,
            ]);
            $specialStatement->execute([
                $loadedFriesId,
                'Loaded-Up Lunch',
                'Loaded fries are $1 off weekdays from 11 to 2.',
                'Weekday Deal',
                599,
                20,
            ]);

            $taxStatement = $this->pdo->prepare(
                'INSERT INTO tax_rates (name, jurisdiction, rate_basis_points) VALUES (?, ?, ?)'
            );
            $taxStatement->execute(['West Virginia Sales Tax', 'state', 600]);
            $taxStatement->execute(['Keyser Municipal Tax', 'city', 100]);

            $adminStatement = $this->pdo->prepare(
                'INSERT INTO users
                    (email, password_hash, full_name, phone, role, newsletter_opt_in,
                     two_factor_enabled, email_verified_at)
                 VALUES (?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP)'
            );
            $adminStatement->execute([
                'admin@fatbottomgrille.com',
                password_hash('ChangeMe123!', PASSWORD_DEFAULT),
                'Store Administrator',
                '',
                'admin',
                0,
            ]);

            $this->pdo->commit();
        } catch (\Throwable $error) {
            $this->pdo->rollBack();
            throw new RuntimeException('Unable to seed the store database.', 0, $error);
        }
    }

    public function fetchAll(string $sql, array $parameters = []): array
    {
        $statement = $this->pdo->prepare($sql);
        $statement->execute($parameters);
        return $statement->fetchAll();
    }

    public function fetch(string $sql, array $parameters = []): ?array
    {
        $statement = $this->pdo->prepare($sql);
        $statement->execute($parameters);
        $row = $statement->fetch();
        return $row === false ? null : $row;
    }

    public function execute(string $sql, array $parameters = []): bool
    {
        $statement = $this->pdo->prepare($sql);
        return $statement->execute($parameters);
    }
}
