<?php
declare(strict_types=1);
require_once __DIR__ . '/OrderStore.php';

function runScenario(string $scenario, string $mode): array
{
    if (!in_array($scenario, ['stock', 'retry', 'rollback', 'delivery', 'query'], true) || !in_array($mode, ['fixed', 'broken'], true)) {
        throw new InvalidArgumentException('Choose a listed scenario and implementation.');
    }
    $store = OrderStore::fixture(); $db = $store->connection(); $trace = []; $detail = [];
    if ($scenario === 'stock') {
        if ($mode === 'broken') {
            // A deterministic interleaving of two stale reads, not two concurrent SQLite connections.
            $first = (int)$db->query('SELECT stock FROM inventory')->fetchColumn();
            $second = (int)$db->query('SELECT stock FROM inventory')->fetchColumn();
            $trace[] = 'Customer A and customer B both read stock = 1 before either writes.';
            foreach (['customer-a' => $first, 'customer-b' => $second] as $key => $stale) {
                $db->prepare('INSERT INTO orders VALUES (?, 1, ?)')->execute([$key, 'unchecked']);
                $db->prepare('UPDATE inventory SET stock = ?')->execute([$stale - 1]);
            }
            $trace[] = 'Both orders were saved. Both writers set stock to 0: two reservations for one item.';
        } else {
            $detail[] = $store->reserve('customer-a'); $detail[] = $store->reserve('customer-b');
            $trace[] = 'A guarded UPDATE reserves the item inside the first transaction.';
            $trace[] = 'The second UPDATE changes zero rows. Its request is declined without creating an order.';
        }
    } elseif ($scenario === 'retry') {
        $detail[] = $store->reserve('request-001');
        if ($mode === 'fixed') { $detail[] = $store->reserve('request-001'); $trace[] = 'The original key and body return the original decision, even though stock is now 0.'; }
        else { $detail[] = $store->reserve('new-key-002'); $trace[] = 'A new key makes this a new purchase attempt. It loses the identity of the successful first request.'; }
    } elseif ($scenario === 'rollback') {
        if ($mode === 'fixed') {
            try { $store->reserve('request-001', 1, true); } catch (RuntimeException) { $trace[] = 'A failure occurred after the order and event writes but before commit.'; }
            $detail['afterFailure'] = $store->snapshot();
            $trace[] = 'Rollback restored stock = 1 and removed both writes. Retrying the same key is safe.';
            $detail['retry'] = $store->reserve('request-001');
        } else {
            $db->exec('UPDATE inventory SET stock = 0');
            $trace[] = 'A write committed before the next step failed. There is no order, but the inventory is gone.';
        }
    } elseif ($scenario === 'delivery') {
        $store->reserve('request-001');
        if ($mode === 'fixed') {
            $detail['firstDelivery'] = $store->deliver('order-request-001');
            $detail['redelivery'] = $store->deliver('order-request-001');
            $trace[] = 'The order and outbox event committed together. A local receiver records the event key.';
            $trace[] = 'After a simulated lost acknowledgment, redelivery finds that same key and creates no second receipt.';
        } else {
            $db->exec("INSERT INTO inbox VALUES ('delivery-attempt-1'), ('delivery-attempt-2')");
            $trace[] = 'The receiver identifies attempts instead of the event. Retrying creates a second receipt.';
        }
    } else {
        $db->exec('CREATE TABLE quote_history (id INTEGER PRIMARY KEY, customer INTEGER, status TEXT, created INTEGER)');
        $db->beginTransaction(); $insert = $db->prepare('INSERT INTO quote_history VALUES (?, ?, ?, ?)');
        for ($i = 0; $i < 5000; $i++) { $insert->execute([$i, $i % 50, $i % 3 === 0 ? 'ready' : 'draft', $i]); }
        $db->commit();
        if ($mode === 'fixed') { $db->exec('CREATE INDEX history_customer_status_recent ON quote_history(customer, status, created DESC)'); }
        $sql = "SELECT id FROM quote_history WHERE customer = 7 AND status = 'ready' ORDER BY created DESC LIMIT 10";
        $detail['query'] = $sql;
        $detail['plan'] = $db->query('EXPLAIN QUERY PLAN ' . $sql)->fetchAll(PDO::FETCH_ASSOC);
        $detail['ids'] = $db->query($sql)->fetchAll(PDO::FETCH_COLUMN);
        $trace[] = 'The query reads a fresh 5,000-row synthetic table.';
        $trace[] = $mode === 'fixed' ? 'The index follows the equality predicates, then the requested ordering.' : 'Without the matching index, inspect the table scan and temporary ordering work in the plan.';
        $trace[] = 'Compare returned IDs as well as plans. This SQLite plan does not prove a MySQL plan.';
    }
    return ['scenario' => $scenario, 'mode' => $mode, 'runtime' => 'PHP ' . PHP_VERSION . ' / SQLite ' . $db->query('SELECT sqlite_version()')->fetchColumn(),
            'snapshot' => $store->snapshot(), 'trace' => $trace, 'detail' => $detail,
            'boundary' => 'Actual PHP and SQL; invented records in a fresh memory database. The stock example replays a chosen interleaving, not a MySQL concurrency benchmark.'];
}
