<?php
declare(strict_types=1);

// One frozen A-side synthetic fixture only. Never replays business HTTP.
$mode = $argv[1] ?? '';
if (count($argv) !== 2 || !in_array($mode, ['--inspect', '--recover'], true)
    || ($mode === '--recover'
        && ((string)getenv('CDX_EM_DUAL_RECOVER_A')
                !== 'ROOT_APPROVED_A_EXACT_DUAL_RECOVERY_V1'
            || !hash_equals((string)hash_file('sha256', __FILE__),
                (string)getenv('CDX_EM_DUAL_RECOVER_A_SHA'))))) {
    echo json_encode(['status' => 'default-deny', 'db_connected' => false,
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(78);
}
$stage = 'frozen_journal';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status' => 'redacted_stop', 'stage' => $stage,
        'error_class' => get_class($error), 'code' => (string)$error->getCode(),
        'reason_sha' => hash('sha256', $error->getMessage()),
        'journal_retained' => true]), PHP_EOL;
    exit(2);
});
$path = '/tmp/cdx-eboard-dual-cdxemdual89c5bdeb043a/recovery.json';
$root = '/tmp/zhct-http-store-a.l5omH8';
$pid = 21023;
$port = 18097;
$marker = 'CDXEMDUAL89C5BDEB043A';
if (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400
    || is_link($path) || realpath($path) !== $path
    || is_link(dirname($path)) || is_link($root)
    || (((int)fileperms(dirname($path)) & 0077) !== 0)
    || (((int)fileperms($path) & 0077) !== 0)) {
    throw new RuntimeException('Private fixture path or runtime changed');
}
$journal = json_decode((string)file_get_contents($path), true);
$policy = __DIR__ . '/recover_swapped_a_policy.php';
if (!is_array($journal) || ($journal['marker'] ?? null) !== $marker
    || !hash_equals('abe945dfcafaba0829a0fe1779618a5ef50325e9410f5c18087a55a9dcc3f164',
        (string)hash_file('sha256', $policy))) {
    throw new RuntimeException('Frozen journal or recovery policy changed');
}
require_once $policy;
$lock = fopen('/tmp/cdx-eboard-dual-dev.lock', 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    throw new RuntimeException('Shared developer MySQL fixture lock unavailable');
}
$stage = 'original_single_worker_generation';
$exitPolicy = __DIR__ . '/../parallel-electronic-menu-bind-race-20261004/race_worker_exit_policy.php';
if (!hash_equals('c0052624d502a095930368fd82d8e6429a387e183a163f0fa8f5b6f5d8ba16ba',
    (string)hash_file('sha256', $exitPolicy))) {
    throw new RuntimeException('Process-generation parser changed');
}
require_once $exitPolicy;
$birth = raceReadProcStat($pid);
$boot = (string)file_get_contents('/proc/stat');
if ($birth === null || !preg_match('/^btime (\d+)$/m', $boot, $bootMatch)) {
    throw new RuntimeException('Original worker generation unavailable');
}
$ticks = (int)trim((string)@shell_exec('/usr/bin/getconf CLK_TCK'));
if ($ticks <= 0 || (int)$bootMatch[1] + (int)$birth['start_ticks'] / $ticks
    >= (int)filemtime($path)) {
    throw new RuntimeException('Original worker started after HTTP journal');
}
$ownsListener = static function () use ($pid, $port): bool {
    $hex = strtoupper(str_pad(dechex($port), 4, '0', STR_PAD_LEFT));
    $lines = @file('/proc/net/tcp', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!is_array($lines)) return false;
    $inodes = [];
    foreach (array_slice($lines, 1) as $line) {
        $parts = preg_split('/\s+/', trim($line));
        if (is_array($parts) && ($parts[1] ?? '') === '0100007F:' . $hex
            && ($parts[3] ?? '') === '0A') $inodes[] = (string)($parts[9] ?? '');
    }
    if (count($inodes) !== 1 || $inodes[0] === '') return false;
    foreach (glob('/proc/' . $pid . '/fd/*') ?: [] as $fd) {
        if (@readlink($fd) === 'socket:[' . $inodes[0] . ']') return true;
    }
    return false;
};
$assertWorker = static function () use ($pid, $port, $root, $birth,
    $ownsListener): void {
    $current = raceReadProcStat($pid);
    $cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $env = (string)@file_get_contents('/proc/' . $pid . '/environ');
    $children = trim((string)@file_get_contents('/proc/' . $pid
        . '/task/' . $pid . '/children'));
    if ($current === null || $current['start_ticks'] !== $birth['start_ticks']
        || strpos($cmd, '127.0.0.1:' . $port) === false
        || strpos($cmd, $root . '/public') === false
        || $env === '' || strpos($env, 'PHP_CLI_SERVER_WORKERS=') !== false
        || $children !== '' || !$ownsListener()) {
        throw new RuntimeException('Original PID/socket/single-worker drift');
    }
};
$assertWorker();
$source = ['application/common/service/ElectronicMenu.php'
        => '525caaeb63bc83a4f401de077b0784de49b98572b3b7732577b143138d9e7e28',
    'application/common/service/ElectronicMenuConfig.php'
        => '9219c5dfee8f22758cc0e7bc3a1eb6e9cbcd18397dad66e1264efe7b1bbd637c',
    'application/p/controller/ElectronicMenu.php'
        => 'a93de90caced9884bb40374b9f3da7cbb04997930023e6cb229a7ca7ffefc2d7',
    'application/api/controller/ElectronicMenu.php'
        => '2c1df1125c5ced2abd4f97a460f4290950d8ca5fbb978d63800e76ba5d60e056'];
foreach ($source as $file => $sha) {
    if (!hash_equals($sha, (string)hash_file('sha256', $root . '/' . $file))) {
        throw new RuntimeException('Original electronic-menu source drift');
    }
}
$stage = 'two_ordered_nonbusiness_completion_barriers';
for ($i = 0; $i < 2; $i++) {
    $assertWorker();
    $boundary = json_decode((string)@file_get_contents(
        'http://127.0.0.1:' . $port . '/__poc_boundary', false,
        stream_context_create(['http' => ['timeout' => 8]])), true);
    $assertWorker();
    if (!is_array($boundary)
        || ($boundary['external_stubs'] ?? null) !== true
        || ($boundary['redis_isolated'] ?? null) !== true
        || ($boundary['real_queue'] ?? true) !== false) {
        throw new RuntimeException('Original worker completion barrier unavailable');
    }
}
$stage = 'code_local_development_mysql';
$runtime = require $root . '/application/database.php';
$local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
foreach (['hostname', 'database', 'username', 'password'] as $key) {
    if (!is_array($runtime) || !is_array($local)
        || ($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
        throw new RuntimeException('Not code-local developer MySQL');
    }
}
if ((int)($runtime['hostport'] ?? 0) !== 3306
    || (string)($runtime['type'] ?? '') !== 'mysql'
    || (isset($local['hostport']) && (int)$local['hostport'] !== 3306)
    || (isset($local['type']) && (string)$local['type'] !== 'mysql')) {
    throw new RuntimeException('Developer MySQL port or driver drift');
}
$dsn = 'mysql:host=' . $runtime['hostname'] . ';port=' . (int)$runtime['hostport']
    . ';dbname=' . $runtime['database'] . ';charset=' . ($runtime['charset'] ?? 'utf8');
$db = new PDO($dsn, (string)$runtime['username'], (string)$runtime['password'],
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
$identity = (string)$db->query(
    "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
    ->fetchColumn();
if (hash('sha256', $identity)
    !== '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401') {
    throw new RuntimeException('Developer MySQL physical identity drift');
}
$table = static function (string $short): string {
    if (!in_array($short, ['user', 'electronic_menu',
        'electronic_menu_bind_code', 'electronic_menu_device',
        'electronic_menu_device_log'], true)) {
        throw new RuntimeException('Recovery table not allowlisted');
    }
    return 'ydy_' . $short;
};
$observe = static function (PDO $pdo) use ($journal, $marker, $table): array {
    $q = $pdo->prepare('SELECT ID,UUID,ACCOUNT,TYPE,CREATE_BY,IS_DELETE FROM '
        . $table('user') . ' WHERE ID=? OR UUID=? OR ACCOUNT=? OR CREATE_BY=?');
    $q->execute([(int)$journal['actor_id'], $marker, $marker, $marker]);
    $actors = $q->fetchAll(PDO::FETCH_ASSOC);
    $q = $pdo->prepare('SELECT ID,UUID,PUBLIC_TOKEN,CREATE_BY,STORE_ID,STATUS,'
        . ' IS_DELETE,DISPLAY_MODE,LEFT_RESTAURANT_ID,RIGHT_RESTAURANT_ID,'
        . ' CAROUSEL_INTERVAL FROM ' . $table('electronic_menu')
        . ' WHERE ID=? OR UUID=? OR PUBLIC_TOKEN=? OR CREATE_BY=?');
    $q->execute([(int)$journal['menu_id'], $journal['uuid'],
        $journal['public_token'], $marker]);
    $menus = $q->fetchAll(PDO::FETCH_ASSOC);
    $relations = [];
    $unchanged = true;
    foreach (['electronic_menu_bind_code', 'electronic_menu_device',
        'electronic_menu_device_log'] as $short) {
        $q = $pdo->prepare('SELECT COUNT(*) FROM ' . $table($short)
            . ' WHERE MENU_ID=?');
        $q->execute([(int)$journal['menu_id']]);
        $relations[$short] = (int)$q->fetchColumn();
        $actual = hash('sha256', serialize($pdo->query('SELECT * FROM '
            . $table($short) . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC)));
        $unchanged = $unchanged && hash_equals(
            (string)($journal['source_before'][$short] ?? ''), $actual);
    }
    $full = false;
    if ($actors === [] && $menus === [] && array_sum($relations) === 0
        && $unchanged) {
        $actual = hash('sha256', serialize($pdo->query('SELECT * FROM '
            . $table('electronic_menu') . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC)));
        $full = hash_equals((string)($journal['source_before']['electronic_menu'] ?? ''),
            $actual);
    }
    return ['actor_rows' => $actors, 'menu_rows' => $menus,
        'relations' => $relations, 'unmodified_relation_hashes' => $unchanged,
        'full_baseline_restored' => $full];
};
$stage = 'exact_fixture_readback';
$seen = $observe($db) + ['same_worker_generation' => true,
    'same_source' => true, 'two_ordered_boundaries' => true,
    'dev_db_identity' => true];
$decision = dualSwappedARecoveryDecision($journal, $seen);
if ($decision === 'deny') {
    throw new RuntimeException('Exact owned fixture recovery denied');
}
if ($mode === '--inspect') {
    echo json_encode(['status' => 'exact_recovery_ready', 'action' => $decision,
        'lane' => 'A', 'actor_rows' => count($seen['actor_rows']),
        'menu_rows' => count($seen['menu_rows']),
        'relation_rows' => array_sum($seen['relations']),
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(0);
}
if ($decision === 'exact_delete') {
    $stage = 'exact_two_row_transaction';
    $db->beginTransaction();
    try {
        $locked = $observe($db) + ['same_worker_generation' => true,
            'same_source' => true, 'two_ordered_boundaries' => true,
            'dev_db_identity' => true];
        if (dualSwappedARecoveryDecision($journal, $locked) !== 'exact_delete') {
            throw new RuntimeException('Owned rows drifted before DELETE');
        }
        $q = $db->prepare('DELETE FROM ' . $table('electronic_menu')
            . ' WHERE ID=? AND UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=?'
            . ' AND STORE_ID=10001 AND DISPLAY_MODE=2');
        $q->execute([(int)$journal['menu_id'], $journal['uuid'],
            $journal['public_token'], $marker]);
        if ($q->rowCount() !== 1) throw new RuntimeException('Exact menu DELETE not one');
        $q = $db->prepare('DELETE FROM ' . $table('user')
            . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
        $q->execute([(int)$journal['actor_id'], $marker, $marker, $marker]);
        if ($q->rowCount() !== 1) throw new RuntimeException('Exact actor DELETE not one');
        if (!$db->commit()) throw new RuntimeException('Exact two-row commit uncertain');
    } catch (Throwable $error) {
        if ($db->inTransaction()) $db->rollBack();
        throw $error;
    }
}
$stage = 'independent_audit_required';
$journal['phase'] = 'recovery_cleanup_checked_awaiting_independent_audit';
$text = json_encode($journal, JSON_UNESCAPED_SLASHES);
if (!is_string($text) || file_put_contents($path, $text, LOCK_EX) === false
    || !chmod($path, 0600)) {
    throw new RuntimeException('Recovery journal phase write failed');
}
echo json_encode(['status' => 'exact_two_row_cleanup_pending_independent_audit',
    'lane' => 'A', 'action' => $decision,
    'actor_rows_deleted' => $decision === 'exact_delete' ? 1 : 0,
    'menu_rows_deleted' => $decision === 'exact_delete' ? 1 : 0,
    'journal' => $path, 'business_http_sent' => false,
    'independent_zero_required' => true], JSON_UNESCAPED_SLASHES), PHP_EOL;
