<?php

declare(strict_types=1);

// Isolated, nonproduction A/B/C test. Default invocation is SELECT-only.
$lane = (string)getenv('CDX_EM_NATURAL_LANE');
$action = (string)($argv[1] ?? '');
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023, 'mysql'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001, 'mysql'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175, 'dm'],
];
if (!isset($targets[$lane]) || !in_array($action, ['--preflight', '--execute'], true)) {
    fwrite(STDERR, "DENIED: lane/action\n");
    exit(78);
}
if ($action === '--execute'
    && ((string)getenv('CDX_EM_NATURAL_APPROVE') !== 'ROOT_APPROVED_NATURAL_READ_V1'
        || (string)getenv('CDX_EM_NATURAL_SHA') !== hash_file('sha256', __FILE__))) {
    fwrite(STDERR, "DENIED: exact approval/SHA\n");
    exit(78);
}
[$root, $base, $pid, $dialect] = $targets[$lane];
if (is_link($root) || !is_dir($root) || !is_file($root . '/application/database.php')) {
    throw new RuntimeException('Isolated application root missing or linked');
}
$cmdline = is_file('/proc/' . $pid . '/cmdline') ? (string)file_get_contents('/proc/' . $pid . '/cmdline') : '';
if (strpos($cmdline, '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) === false
    || strpos($cmdline, $root . '/public') === false) {
    throw new RuntimeException('Listener PID/docroot drift');
}
$children = (string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children');
$environment = (string)@file_get_contents('/proc/' . $pid . '/environ');
if ($environment === '' || trim($children) !== ''
    || preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=([2-9]|\d\d+)(?:\x00|$)/', $environment)) {
    throw new RuntimeException('Single isolated worker not proven');
}
$boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated']) || !empty($boundary['real_queue'])) {
    throw new RuntimeException('External/Redis/queue isolation missing');
}
$expectedServiceHash = $lane === 'A'
    ? '525caaeb63bc83a4f401de077b0784de49b98572b3b7732577b143138d9e7e28'
    : '179869c96cfdaf5e9de1003ace06949dc654bc4876efe89ec53455a7721e47cd';
foreach ([
    'application/common/service/ElectronicMenu.php' => $expectedServiceHash,
    'application/p/controller/ElectronicMenu.php' => 'a93de90caced9884bb40374b9f3da7cbb04997930023e6cb229a7ca7ffefc2d7',
] as $path => $expected) {
    if (hash_file('sha256', $root . '/' . $path) !== $expected) {
        throw new RuntimeException('Electronic-menu runtime source drift: ' . $path);
    }
}
if ($dialect === 'dm') {
    $workerVariables = [];
    foreach (explode("\0", $environment) as $entry) {
        $separator = strpos($entry, '=');
        if ($separator !== false) $workerVariables[substr($entry, 0, $separator)] = substr($entry, $separator + 1);
    }
    foreach (['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD'] as $name) {
        if (!isset($workerVariables[$name]) || $workerVariables[$name] === '') {
            throw new RuntimeException('Isolated DM worker config missing: ' . $name);
        }
        putenv($name . '=' . $workerVariables[$name]);
    }
    unset($workerVariables);
}
$config = require $root . '/application/database.php';
if (!is_array($config)) throw new RuntimeException('Runtime DB config unavailable');
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    foreach (['hostname', 'hostport', 'database', 'username', 'password'] as $key) {
        if (($config[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('Not code-local development MySQL');
        }
    }
    $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . (int)$config['hostport']
        . ';dbname=' . $config['database'] . ';charset=' . ($config['charset'] ?? 'utf8');
} else {
    if (($config['hostname'] ?? null) !== '127.0.0.1'
        || (int)($config['hostport'] ?? 0) !== 5237
        || strtoupper((string)($config['database'] ?? '')) !== 'ZHCT_APP'
        || strtoupper((string)($config['username'] ?? '')) !== 'ZHCT_APP') {
        throw new RuntimeException('Not approved nonproduction DM business schema');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
if (!in_array($dialect, PDO::getAvailableDrivers(), true)) {
    fwrite(STDERR, "DENIED: required PDO driver unavailable\n");
    exit(70);
}
try {
    $db = new PDO($dsn, (string)$config['username'], (string)$config['password'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_CASE => PDO::CASE_LOWER,
        PDO::ATTR_TIMEOUT => 5,
    ]);
} catch (PDOException $error) {
    fwrite(STDERR, "DENIED: database connection failed; details redacted\n");
    exit(70);
}
if ($dialect === 'mysql') {
    $identity = (string)$db->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
} else {
    $schema = (string)$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
        ->fetchColumn();
    if (strtoupper($schema) !== 'ZHCT_APP') throw new RuntimeException('DM schema drift');
    $identity = 'ZHCT_APP|5237|' . $schema;
    $listeners = [];
    $status = 0;
    @exec('/usr/sbin/ss -ltnp 2>/dev/null', $listeners, $status);
    $localDm = false;
    foreach ($listeners as $line) {
        if (preg_match('/\*:5237\s+.*users:\(\("dmserver",pid=(\d+),/', $line, $match)) {
            $executable = @readlink('/proc/' . $match[1] . '/exe');
            $localDm = is_string($executable) && basename($executable) === 'dmserver';
            break;
        }
    }
    if ($status !== 0 || !$localDm) throw new RuntimeException('Local DM listener not proven');
}
$fingerprint = hash('sha256', $identity);
$prefix = $dialect === 'dm' ? 'YDY_' : 'ydy_';
$userTable = $prefix . 'user';
$menuTable = $prefix . 'electronic_menu';
$restaurantTable = $prefix . 'restaurants';
$menus = $db->query('SELECT ID,UUID,PUBLIC_TOKEN,LEFT_RESTAURANT_ID,RIGHT_RESTAURANT_ID,'
    . ' DISPLAY_MODE,STORE_ID,STATUS,IS_DELETE FROM ' . $menuTable
    . ' WHERE STORE_ID=10001 AND STATUS=1 AND IS_DELETE=0 ORDER BY ID')
    ->fetchAll(PDO::FETCH_ASSOC);
$dual = null;
foreach ($menus as $candidate) {
    if ((int)$candidate['display_mode'] === 2
        && (int)$candidate['left_restaurant_id'] > 0
        && (int)$candidate['right_restaurant_id'] > 0
        && (int)$candidate['left_restaurant_id'] !== (int)$candidate['right_restaurant_id']) {
        $dual = $candidate;
        break;
    }
}
if (!$dual) throw new RuntimeException('No natural dual-stall menu');
$active = $db->prepare('SELECT COUNT(*) FROM ' . $restaurantTable
    . ' WHERE ID IN (?,?) AND STATUS=1 AND IS_DELETE=2');
$active->execute([(int)$dual['left_restaurant_id'], (int)$dual['right_restaurant_id']]);
if ((int)$active->fetchColumn() !== 2) throw new RuntimeException('Natural dual stalls inactive');
$markerCount = (int)$db->query('SELECT COUNT(*) FROM ' . $userTable
    . " WHERE ACCOUNT LIKE 'CDXEMNAT%'")->fetchColumn();
if ($markerCount !== 0) throw new RuntimeException('Stale synthetic actor requires review');
if ($action === '--preflight') {
    $shape = $db->query('SELECT * FROM ' . $menuTable . ' WHERE 1=0');
    $columns = [];
    for ($index = 0; $index < $shape->columnCount(); $index++) {
        $meta = $shape->getColumnMeta($index);
        $columns[] = strtoupper((string)($meta['name'] ?? ''));
    }
    $tableDigest = hash('sha256', serialize($db->query('SELECT * FROM '
        . $menuTable . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC)));
    $rightFirstQuery = $db->prepare('SELECT DISPLAY_MODE,RIGHT_RESTAURANT_ID FROM '
        . $menuTable . ' WHERE STORE_ID=10001 AND IS_DELETE=0'
        . ' AND (LEFT_RESTAURANT_ID=? OR RIGHT_RESTAURANT_ID=?) ORDER BY ID DESC');
    $rightFirstQuery->execute([(int)$dual['right_restaurant_id'], (int)$dual['right_restaurant_id']]);
    $rightFirst = $rightFirstQuery->fetch(PDO::FETCH_ASSOC) ?: [];
    echo json_encode(['lane' => $lane, 'mode' => 'SELECT-only', 'isolated' => true,
        'database' => $dialect === 'dm' ? 'nonproduction-DM' : 'code-local-MySQL',
        'database_sha' => $fingerprint, 'natural_menu_count' => count($menus),
        'dual_stalls_present' => true, 'stale_actor_count' => $markerCount,
        'menu_column_count' => count($columns),
        'menu_business_date_column' => in_array('BUSINESS_DATE', $columns, true),
        'menu_original_columns_sha' => $tableDigest,
        'right_filter_first_display_mode' => (int)($rightFirst['display_mode'] ?? 0),
        'right_filter_first_right_id_zero' => (int)($rightFirst['right_restaurant_id'] ?? -1) === 0],
        JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}
if ((string)getenv('CDX_EM_NATURAL_DB_SHA') !== $fingerprint) {
    throw new RuntimeException('Approved DB fingerprint mismatch');
}
$tableHash = static function (PDO $pdo, string $table): string {
    return hash('sha256', serialize($pdo->query('SELECT * FROM ' . $table . ' ORDER BY ID')
        ->fetchAll(PDO::FETCH_ASSOC)));
};
$menuBefore = $tableHash($db, $menuTable);
$marker = 'CDXEMNAT' . strtoupper(bin2hex(random_bytes(6)));
$journal = '/tmp/' . strtolower($marker) . '.journal';
if (file_exists($journal)) throw new RuntimeException('Private journal collision');
if (file_put_contents($journal, $marker . "\n", LOCK_EX) === false) {
    throw new RuntimeException('Cannot create recovery journal');
}
chmod($journal, 0600);
$actorId = 0;
$failure = null;
$result = [];
try {
    $insert = $db->prepare('INSERT INTO ' . $userTable
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insert->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    $actor = $db->prepare('SELECT ID FROM ' . $userTable
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $actor->execute([$marker, $marker, 'CODEX']);
    $actorId = (int)$actor->fetchColumn();
    if ($actorId <= 0) throw new RuntimeException('Synthetic actor readback failed');
    file_put_contents($journal, $marker . "\n" . $actorId . "\n", LOCK_EX);
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t", [
        (string)$actorId, $marker, $marker, '0', '', '', $marker,
    ]));
    if (!is_string($token) || $token === '') throw new RuntimeException('Synthetic token missing');
    $post = static function (string $path, array $data) use ($base, $token): array {
        $body = @file_get_contents($base . $path, false, stream_context_create(['http' => [
            'method' => 'POST',
            'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: " . $token . "\r\n",
            'content' => http_build_query($data, '', '&', PHP_QUERY_RFC3986),
            'timeout' => 12, 'ignore_errors' => true,
        ]]));
        $headers = $http_response_header ?? [];
        preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
        $json = is_string($body) ? json_decode($body, true) : null;
        if ((int)($match[1] ?? 0) !== 200 || !is_array($json)
            || ($json['code'] ?? null) !== 0 || !is_array($json['data'] ?? null)) {
            throw new RuntimeException('Business HTTP not successful: ' . $path);
        }
        return $json;
    };
    $types = static function (array $value): array {
        $map = [];
        foreach ($value as $key => $entry) $map[$key] = gettype($entry);
        ksort($map);
        return $map;
    };
    $sourceRows = static function (int $filter) use ($db, $menuTable): array {
        $sql = 'SELECT ID FROM ' . $menuTable
            . ' WHERE STORE_ID=10001 AND IS_DELETE=0 AND'
            . ' (LEFT_RESTAURANT_ID=? OR RIGHT_RESTAURANT_ID=?) ORDER BY ID DESC';
        $stmt = $db->prepare($sql);
        $stmt->execute([$filter, $filter]);
        return array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
    };
    foreach (['left' => (int)$dual['left_restaurant_id'],
        'right' => (int)$dual['right_restaurant_id']] as $side => $restaurantId) {
        $expected = $sourceRows($restaurantId);
        if (!$expected) throw new RuntimeException('Natural side filter unexpectedly empty');
        $response = $post('/p/electronicMenu/list', [
            'restaurant_id' => $restaurantId, 'page' => 1, 'pageSize' => 1,
        ]);
        $data = $response['data'];
        $rows = $data['rows'] ?? null;
        if (!is_array($rows) || count($rows) !== 1
            || ($data['total'] ?? null) !== count($expected)
            || (int)($rows[0]['id'] ?? 0) !== $expected[0]
            || ($rows[0]['device_count'] ?? null) === null
            || !is_int($rows[0]['device_count'])) {
            throw new RuntimeException('Filtered natural menu pagination/source mismatch: ' . $side);
        }
        $result['list_' . $side] = [
            'total' => count($expected), 'first_id_matches_source' => true,
            'row_types' => $types($rows[0]),
            'response_sha' => hash('sha256', json_encode($response, JSON_UNESCAPED_UNICODE)),
        ];
    }
    $detail = $post('/p/electronicMenu/detail', ['id' => (int)$dual['id']]);
    $row = $detail['data'];
    if ((int)($row['id'] ?? 0) !== (int)$dual['id']
        || (string)($row['uuid'] ?? '') !== (string)$dual['uuid']
        || (string)($row['public_token'] ?? '') !== (string)$dual['public_token']
        || (int)($row['left_restaurant_id'] ?? 0) !== (int)$dual['left_restaurant_id']
        || (int)($row['right_restaurant_id'] ?? 0) !== (int)$dual['right_restaurant_id']
        || ($row['display_mode'] ?? null) !== 2) {
        throw new RuntimeException('Natural menu detail/source mismatch');
    }
    $result['detail'] = ['source_match' => true, 'row_types' => $types($row),
        'response_sha' => hash('sha256', json_encode($detail, JSON_UNESCAPED_UNICODE))];
} catch (Throwable $error) {
    $failure = $error->getMessage();
} finally {
    try {
        $delete = $db->prepare('DELETE FROM ' . $userTable
            . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
        $delete->execute([$marker, $marker, 'CODEX']);
        if ($delete->rowCount() > 1 || ($actorId > 0 && $delete->rowCount() !== 1)) {
            throw new RuntimeException('Actor exact delete count invalid');
        }
    } catch (Throwable $cleanupError) {
        $failure = ($failure ? $failure . '; ' : '') . 'CLEANUP: ' . $cleanupError->getMessage();
    }
}
$remaining = $db->prepare('SELECT COUNT(*) FROM ' . $userTable . ' WHERE UUID=? OR ACCOUNT=?');
$remaining->execute([$marker, $marker]);
$actorZero = (int)$remaining->fetchColumn() === 0;
$zero = $actorZero && $tableHash($db, $menuTable) === $menuBefore;
if ($zero) @unlink($journal);
$result['cleanup'] = ['actor_zero' => $actorZero,
    'source_hashes_restored' => $zero, 'journal_removed' => !file_exists($journal)];
$result['lane'] = $lane;
$result['status'] = $failure === null && $zero ? 'PASS' : 'FAIL';
if ($failure !== null) $result['failure'] = $failure;
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
exit($result['status'] === 'PASS' ? 0 : 1);
