<?php
declare(strict_types=1);

// The --race phase uses ONLY two already-staged, separately health-checked
// private workers. No worker creation, teardown, or wide cleanup occurs here.
$lane = (string)getenv('CDX_EM_RACE_LANE');
$journalPath = (string)getenv('CDX_EM_RACE_JOURNAL');
if (!in_array($lane, ['A', 'B', 'C'], true)
    || count($argv) !== 2 || ($argv[1] ?? '') !== '--race'
    || (string)getenv('CDX_EM_RACE_WRITE') !== 'ROOT_APPROVED_OWNED_TWO_REQUEST_RACE_V1'
    || !hash_equals((string)hash_file('sha256', __FILE__),
        (string)getenv('CDX_EM_RACE_WRITE_SHA'))
    || !preg_match('~^/tmp/cdx-em-race-barrier-[a-f0-9]{12}/recovery\.json$~D',
        $journalPath)) {
    echo json_encode(['status' => 'default-deny', 'db_connected' => false,
        'workers_started' => false, 'business_http_sent' => false,
        'dml_sent' => false]), PHP_EOL;
    exit(78);
}
const RACE_OWNED_WRITE_REVIEWED = false;
if (!RACE_OWNED_WRITE_REVIEWED) {
    echo json_encode(['status' => 'race-review-blocked', 'db_connected' => false,
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(78);
}
$stage = 'private_manifest_and_source';
$businessHttpSent = false;
set_exception_handler(static function (Throwable $error)
    use (&$stage, &$businessHttpSent): void {
    echo json_encode(['status' => 'redacted_stop', 'stage' => $stage,
        'error_class' => get_class($error), 'code' => (string)$error->getCode(),
        'business_http_sent' => $businessHttpSent,
        'recovery' => 'retain_journal_workers_no_blind_retry']), PHP_EOL;
    exit(2);
});
if (is_link($journalPath) || realpath($journalPath) !== $journalPath
    || is_link(dirname($journalPath))
    || (((int)fileperms(dirname($journalPath)) & 0077) !== 0)
    || (((int)fileperms($journalPath) & 0077) !== 0)) {
    throw new RuntimeException('Private manifest unsafe');
}
$journal = json_decode((string)file_get_contents($journalPath), true);
if (!is_array($journal) || ($journal['lane'] ?? null) !== $lane
    || ($journal['phase'] ?? null) !== 'healthy'
    || !preg_match('/^CDXEMRACE[A-F0-9]{12}$/D',
        (string)($journal['marker'] ?? ''))
    || !isset($journal['device_ids'][1], $journal['device_ids'][2],
        $journal['worker_roots'][1], $journal['worker_roots'][2],
        $journal['worker_pids'][1], $journal['worker_pids'][2],
        $journal['worker_ports'][1], $journal['worker_ports'][2],
        $journal['worker_start_ticks'][1], $journal['worker_start_ticks'][2])
    || (int)($journal['menu_id'] ?? -1) !== 0
    || (int)($journal['code_id'] ?? -1) !== 0) {
    throw new RuntimeException('Health-checked pre-DML phase absent');
}
$marker = (string)$journal['marker'];
$deviceIds = $journal['device_ids'];
$ports = $journal['worker_ports'];
$preflight = __DIR__ . '/preflight_bind_race.php';
if (!hash_equals('915131476db82d3ab5c1a22a8fc460d8980bab07785fe53a3556988861717887',
    (string)hash_file('sha256', $preflight))) {
    throw new RuntimeException('Frozen race preflight changed');
}
$exitPolicy = __DIR__ . '/race_worker_exit_policy.php';
if (!hash_equals('c0052624d502a095930368fd82d8e6429a387e183a163f0fa8f5b6f5d8ba16ba',
    (string)hash_file('sha256', $exitPolicy))) {
    throw new RuntimeException('Frozen worker generation policy changed');
}
require_once $exitPolicy;
putenv('CDX_EM_RACE_PREFLIGHT=BIND_RACE_SCHEMA_SELECT_ONLY_V1');
putenv('CDX_EM_RACE_PREFLIGHT_SHA=915131476db82d3ab5c1a22a8fc460d8980bab07785fe53a3556988861717887');
$outerArgv = $argv;
$argv[1] = '--preflight-only';
ob_start();
require $preflight;
$preflightText = (string)ob_get_clean();
$argv = $outerArgv;
restore_exception_handler();
$preflightResult = json_decode(trim($preflightText), true);
if (!is_array($preflightResult)
    || ($preflightResult['status'] ?? null) !== 'select_only_pass'
    || ($preflightResult['lane'] ?? null) !== $lane
    || ($preflightResult['bind_code_hash_unique'] ?? null) !== true
    || ($preflightResult['store_device_id_unique'] ?? null) !== true
    || ($preflightResult['old_owned_markers'] ?? null)
        !== ['menu' => 0, 'bind' => 0, 'device' => 0, 'log' => 0]) {
    throw new RuntimeException('Fresh same-lane schema/source gate failed');
}
$ownsListener = static function (int $pid, int $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;
};
$sourceRoot = $root;
$sourceConfigSha = (string)hash_file('sha256',
    $sourceRoot . '/application/database.php');
$checkWorkers = static function (bool $requirePristine = false) use ($journal,
    $ports, $ownsListener, $sourceConfigSha): void {
    foreach ([1, 2] as $side) {
        $pid = (int)$journal['worker_pids'][$side];
        $port = (int)$ports[$side];
        $root = (string)$journal['worker_roots'][$side];
        $birth = raceReadProcStat($pid);
        $cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
        $children = trim((string)@file_get_contents('/proc/' . $pid
            . '/task/' . $pid . '/children'));
        if ($pid <= 0 || $port <= 1024 || realpath($root) !== $root
            || !$ownsListener($pid, $port)
            || $birth === null
            || ($birth['start_ticks'] ?? null)
                !== (string)$journal['worker_start_ticks'][$side]
            || strpos($cmd, '127.0.0.1:' . $port) === false
            || strpos($cmd, $root . '/public') === false
            || strpos($cmd, __DIR__ . '/race_router.disabled.php') === false
            || $children !== ''
            || !hash_equals($sourceConfigSha,
                (string)hash_file('sha256', $root . '/application/database.php'))
            || !hash_equals('3335769aee94126f7abfae2f2a305ba09cca9270df2ab7590657c00fabef855f',
                (string)hash_file('sha256',
                    $root . '/application/common/service/ElectronicMenuDevice.php'))
            || !hash_equals('2c1df1125c5ced2abd4f97a460f4290950d8ca5fbb978d63800e76ba5d60e056',
                (string)hash_file('sha256',
                    $root . '/application/api/controller/ElectronicMenu.php'))) {
            throw new RuntimeException('Private worker socket/root changed');
        }
        $base = 'http://127.0.0.1:' . $port;
        $status = json_decode((string)@file_get_contents($base . '/__poc_race_status',
            false, stream_context_create(['http' => ['timeout' => 8]])), true);
        $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary',
            false, stream_context_create(['http' => ['timeout' => 8]])), true);
        if (!is_array($status) || !is_array($boundary)
            || (int)($status['pid'] ?? 0) !== $pid
            || (int)($status['side'] ?? 0) !== $side
            || ($status['private_race_router'] ?? null) !== true
            || ($boundary['external_stubs'] ?? null) !== true
            || ($boundary['redis_isolated'] ?? null) !== true
            || ($boundary['real_queue'] ?? true) !== false) {
            throw new RuntimeException('Private worker boundary changed');
        }
        if ($requirePristine && ((int)($status['arrived_count'] ?? -1) !== 0
            || ($status['released'] ?? null) !== false
            || ($status['aborted'] ?? null) !== false)) {
            throw new RuntimeException('Private router rendezvous not fresh');
        }
    }
};
$checkWorkers(true);
$writeJournal = static function (array $change) use (&$journal, $journalPath): void {
    $journal = array_replace_recursive($journal, $change);
    $json = json_encode($journal, JSON_UNESCAPED_SLASHES);
    if (!is_string($json) || file_put_contents($journalPath, $json, LOCK_EX) === false
        || !chmod($journalPath, 0600)) {
        throw new RuntimeException('Private recovery journal write failed');
    }
};
$lock = fopen(sys_get_temp_dir() . '/cdx-em-race-'
    . ($lane === 'C' ? 'c' : 'dev') . '.lock', 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    throw new RuntimeException('Same-DB race writer lock unavailable');
}
$t = static function (string $short) use ($dialect): string {
    if (!in_array($short, ['electronic_menu',
        'electronic_menu_bind_code', 'electronic_menu_device',
        'electronic_menu_device_log'], true)) throw new RuntimeException('Table not allowed');
    return $dialect === 'dm' ? strtoupper('ydy_' . $short) : 'ydy_' . $short;
};
$connect = static function () use ($dsn, $config, $dialect): PDO {
    return new PDO($dsn,
        $dialect === 'dm' ? (string)getenv('POC_USER') : (string)$config['username'],
        $dialect === 'dm' ? (string)getenv('POC_PASSWORD') : (string)$config['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
};
$stage = 'snapshot_then_owned_fixture';
$baseline = [];
foreach (['electronic_menu', 'electronic_menu_bind_code',
    'electronic_menu_device', 'electronic_menu_device_log'] as $short) {
    $baseline[$short] = hash('sha256', serialize($db->query('SELECT * FROM '
        . $t($short) . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC)));
}
$writeJournal(['source_before' => $baseline, 'phase' => 'race_started']);
$restaurant = $db->query('SELECT LEFT_RESTAURANT_ID FROM '
    . $t('electronic_menu')
    . ' WHERE STORE_ID=10001 AND STATUS=1 AND IS_DELETE=0 ORDER BY ID')
    ->fetch(PDO::FETCH_ASSOC);
$restaurantId = (int)($restaurant['left_restaurant_id'] ?? 0);
if ($restaurantId <= 0) throw new RuntimeException('No active source restaurant');
$q = $db->prepare('SELECT COUNT(*) FROM ' . $t('electronic_menu_device')
    . ' WHERE DEVICE_ID IN (?,?)');
$q->execute([$deviceIds[1], $deviceIds[2]]);
if ((int)$q->fetchColumn() !== 0) throw new RuntimeException('Device ID collision');
$code = '';
$codeHash = '';
for ($i = 0; $i < 20; $i++) {
    $candidate = (string)random_int(100000, 999999);
    $candidateHash = hash('sha256', $candidate);
    $q = $db->prepare('SELECT COUNT(*) FROM ' . $t('electronic_menu_bind_code')
        . ' WHERE CODE_HASH=?');
    $q->execute([$candidateHash]);
    if ((int)$q->fetchColumn() === 0) {
        $code = $candidate;
        $codeHash = $candidateHash;
        break;
    }
}
if ($code === '') throw new RuntimeException('No collision-free code');
$menuUuid = strtoupper(bin2hex(random_bytes(16)));
$publicToken = bin2hex(random_bytes(32));
$writeJournal(['menu_uuid' => $menuUuid, 'public_token' => $publicToken,
    'code_hash' => $codeHash, 'phase' => 'fixture_intent']);
$now = date('Y-m-d H:i:s');
$db->prepare('INSERT INTO ' . $t('electronic_menu')
    . ' (UUID,PUBLIC_TOKEN,STORE_ID,STATUS,IS_DELETE,DISPLAY_MODE,'
    . ' LEFT_RESTAURANT_ID,RIGHT_RESTAURANT_ID,CAROUSEL_INTERVAL,'
    . ' CREATE_BY,CREATE_TIME,UPDATE_TIME)'
    . ' VALUES (?,?,10001,1,0,1,?,0,8,?,?,?)')
    ->execute([$menuUuid, $publicToken, $restaurantId, $marker, $now, $now]);
$q = $connect()->prepare('SELECT ID FROM ' . $t('electronic_menu')
    . ' WHERE UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=?');
$q->execute([$menuUuid, $publicToken, $marker]);
$menuIds = $q->fetchAll(PDO::FETCH_COLUMN);
if (count($menuIds) !== 1 || (int)$menuIds[0] <= 0) {
    throw new RuntimeException('Owned menu insert ambiguous');
}
$menuId = (int)$menuIds[0];
$writeJournal(['menu_id' => $menuId, 'menu_uuid' => $menuUuid,
    'public_token' => $publicToken, 'phase' => 'menu_inserted']);
$expires = date('Y-m-d H:i:s', time() + 600);
$db->prepare('INSERT INTO ' . $t('electronic_menu_bind_code')
    . ' (MENU_ID,CODE_HASH,EXPIRES_AT,USED_AT,STATUS,STORE_ID,'
    . ' CREATE_BY,CREATE_TIME) VALUES (?,?,?,NULL,1,10001,?,?)')
    ->execute([$menuId, $codeHash, $expires, $marker, $now]);
$q = $connect()->prepare('SELECT ID FROM ' . $t('electronic_menu_bind_code')
    . ' WHERE MENU_ID=? AND CODE_HASH=? AND CREATE_BY=?');
$q->execute([$menuId, $codeHash, $marker]);
$codeIds = $q->fetchAll(PDO::FETCH_COLUMN);
if (count($codeIds) !== 1 || (int)$codeIds[0] <= 0) {
    throw new RuntimeException('Owned bind-code insert ambiguous');
}
$codeId = (int)$codeIds[0];
$writeJournal(['code_id' => $codeId, 'code_hash' => $codeHash,
    'phase' => 'bind_code_inserted']);
$stage = 'two_concurrent_private_http_requests';
if (!extension_loaded('curl')) throw new RuntimeException('cURL multi unavailable');
$writeJournal(['phase' => 'two_http_started']);
$multi = curl_multi_init();
$handles = [];
$bodies = [1 => '', 2 => ''];
foreach ([1, 2] as $side) {
    $handle = curl_init('http://127.0.0.1:' . (int)$ports[$side]
        . '/api/electronicMenu/device/activate');
    $form = http_build_query(['bind_code' => $code,
        'device_id' => $deviceIds[$side],
        'device_name' => 'Synthetic TV'], '', '&', PHP_QUERY_RFC3986);
    curl_setopt_array($handle, [CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $form, CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_TIMEOUT => 15, CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
        CURLOPT_WRITEFUNCTION => static function ($ch, string $chunk)
            use (&$bodies, $side): int {
            if (strlen($bodies[$side]) + strlen($chunk) > 1048576) return 0;
            $bodies[$side] .= $chunk;
            return strlen($chunk);
        }]);
    $handles[$side] = $handle;
    curl_multi_add_handle($multi, $handle);
}
$businessHttpSent = true;
$running = 0;
$startedAt = microtime(true);
do {
    $multiCode = curl_multi_exec($multi, $running);
    if ($multiCode !== CURLM_OK || microtime(true) - $startedAt > 20) break;
    if ($running > 0) curl_multi_select($multi, 0.1);
} while ($running > 0);
$responses = [];
foreach ($handles as $side => $handle) {
    $responses[$side] = ['http' => (int)curl_getinfo($handle, CURLINFO_HTTP_CODE),
        'curl_errno' => curl_errno($handle),
        'json' => json_decode($bodies[$side], true)];
    curl_multi_remove_handle($multi, $handle);
    curl_close($handle);
}
curl_multi_close($multi);
$failure = '';
try {
    if ($multiCode !== CURLM_OK || $running !== 0) {
        throw new RuntimeException('Two HTTP responses incomplete');
    }
    $winner = 0;
    $loser = 0;
    foreach ($responses as $side => $response) {
        $body = $response['json'];
        if ($response['curl_errno'] !== 0 || $response['http'] !== 200
            || !is_array($body)) {
            throw new RuntimeException('Concurrent HTTP transport/JSON invalid');
        }
        if (($body['code'] ?? null) === 0) {
            if ($winner !== 0) throw new RuntimeException('Two activation winners');
            $winner = $side;
        } elseif (($body['code'] ?? null) === 1
            && ($body['data'] ?? null) === []
            && in_array((string)($body['message'] ?? ''),
                ['绑定码无效或已过期', '绑定码已被使用'], true)) {
            if ($loser !== 0) throw new RuntimeException('Two expected losers');
            $loser = $side;
        } else {
            throw new RuntimeException('Unexpected activation business response');
        }
    }
    if ($winner === 0 || $loser === 0 || $winner === $loser) {
        throw new RuntimeException('One winner/one loser not established');
    }
    $token = (string)($responses[$winner]['json']['data']['device_token'] ?? '');
    if (!preg_match('/^[a-f0-9]{64}$/D', $token)) {
        throw new RuntimeException('Winner device token contract invalid');
    }
    $statePath = dirname($journalPath) . '/state.json';
    $state = json_decode((string)file_get_contents($statePath), true);
    if (!is_array($state) || empty($state['arrivals'][1])
        || empty($state['arrivals'][2]) || empty($state['dispatch'][1])
        || empty($state['dispatch'][2])
        || ($state['released'] ?? null) !== true
        || ($state['aborted'] ?? null) !== false) {
        throw new RuntimeException('Two private ingress arrivals not proven');
    }
    $dispatchDeltaMs = (int)round(abs($state['dispatch'][1]
        - $state['dispatch'][2]) * 1000);
    if ($dispatchDeltaMs > 500) {
        throw new RuntimeException('Router dispatch did not overlap sufficiently');
    }
    $read = $connect();
    $q = $read->prepare('SELECT STATUS,USED_AT,MENU_ID FROM '
        . $t('electronic_menu_bind_code') . ' WHERE ID=? AND CODE_HASH=?'
        . ' AND CREATE_BY=?');
    $q->execute([$codeId, $codeHash, $marker]);
    $claimed = $q->fetch(PDO::FETCH_ASSOC);
    if (!$claimed || (int)$claimed['status'] !== 2
        || $claimed['used_at'] === null
        || (int)$claimed['menu_id'] !== $menuId) {
        throw new RuntimeException('Code claim original row invalid');
    }
    $q = $read->prepare('SELECT DEVICE_ID,DEVICE_TOKEN_HASH,MENU_ID,STORE_ID,STATUS'
        . ' FROM ' . $t('electronic_menu_device')
        . ' WHERE DEVICE_ID IN (?,?)');
    $q->execute([$deviceIds[1], $deviceIds[2]]);
    $deviceRows = $q->fetchAll(PDO::FETCH_ASSOC);
    if (count($deviceRows) !== 1
        || (string)$deviceRows[0]['device_id'] !== $deviceIds[$winner]
        || (string)$deviceRows[0]['device_token_hash'] !== hash('sha256', $token)
        || (int)$deviceRows[0]['menu_id'] !== $menuId
        || (int)$deviceRows[0]['store_id'] !== 10001
        || (int)$deviceRows[0]['status'] !== 1) {
        throw new RuntimeException('Only winning virtual device not proven');
    }
} catch (Throwable $error) {
    $failure = get_class($error) . ':' . hash('sha256', $error->getMessage());
}
$stage = 'two_fixed_worker_completion_barriers';
$checkWorkers(); // Sequential status/boundary GET to each single worker.
$writeJournal(['phase' => 'both_worker_barriers_confirmed']);
$stage = 'exact_owned_transaction_cleanup';
$cleanup = $connect();
$q = $cleanup->prepare('SELECT ID,UUID,DEVICE_ID,DEVICE_TOKEN_HASH,MENU_ID,'
    . ' STORE_ID,CREATE_BY FROM ' . $t('electronic_menu_device')
    . ' WHERE DEVICE_ID IN (?,?) ORDER BY ID');
$q->execute([$deviceIds[1], $deviceIds[2]]);
$deviceRows = $q->fetchAll(PDO::FETCH_ASSOC);
if (count($deviceRows) > 1) throw new RuntimeException('Unexpected device cleanup scope');
foreach ($deviceRows as $row) {
    if (!in_array((string)$row['device_id'], $deviceIds, true)
        || (int)$row['menu_id'] !== $menuId
        || (int)$row['store_id'] !== 10001
        || (string)$row['create_by'] !== 'device-bind'
        || (string)$row['uuid'] === '') {
        throw new RuntimeException('Unknown device row before cleanup');
    }
}
$q = $cleanup->prepare('SELECT COUNT(*) FROM ' . $t('electronic_menu_device_log')
    . ' WHERE MENU_ID=?');
$q->execute([$menuId]);
if ((int)$q->fetchColumn() !== 0) throw new RuntimeException('Unexpected owned log');
$cleanup->beginTransaction();
foreach ($deviceRows as $row) {
    $q = $cleanup->prepare('DELETE FROM ' . $t('electronic_menu_device')
        . ' WHERE ID=? AND UUID=? AND DEVICE_ID=? AND DEVICE_TOKEN_HASH=?'
        . ' AND MENU_ID=? AND STORE_ID=10001 AND CREATE_BY=?');
    $q->execute([(int)$row['id'], (string)$row['uuid'],
        (string)$row['device_id'], (string)$row['device_token_hash'],
        $menuId, 'device-bind']);
    if ($q->rowCount() !== 1) throw new RuntimeException('Owned device delete not one');
}
$q = $cleanup->prepare('DELETE FROM ' . $t('electronic_menu_bind_code')
    . ' WHERE ID=? AND MENU_ID=? AND CODE_HASH=? AND CREATE_BY=?');
$q->execute([$codeId, $menuId, $codeHash, $marker]);
if ($q->rowCount() !== 1) throw new RuntimeException('Owned code delete not one');
$q = $cleanup->prepare('DELETE FROM ' . $t('electronic_menu')
    . ' WHERE ID=? AND UUID=? AND PUBLIC_TOKEN=? AND CREATE_BY=?');
$q->execute([$menuId, $menuUuid, $publicToken, $marker]);
if ($q->rowCount() !== 1) throw new RuntimeException('Owned menu delete not one');
if (!$cleanup->commit()) throw new RuntimeException('Owned cleanup commit uncertain');
$writeJournal(['phase' => 'cleanup_committed']);
echo json_encode(['status' => $failure === ''
        ? 'race_scoped_pass_pending_audit'
        : 'race_scoped_fail_pending_audit',
    'lane' => $lane, 'failure' => $failure,
    'concurrent_entry_observed' => $failure === '',
    'dispatch_delta_ms' => $dispatchDeltaMs ?? null,
    'owned_device_rows_deleted' => count($deviceRows),
    'journal' => $journalPath,
    'independent_zero_required_before_stop' => true],
    JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($failure !== '') exit(1);
