<?php

declare(strict_types=1);

// C-only runner. Default denies starting either real think-poc command.
$mode = (string)($argv[1] ?? '');
$modes = ['--preflight' => null,
    '--run-balance' => 'balance', '--run-subsidy' => 'subsidy'];
if (count($argv) !== 2 || !array_key_exists($mode, $modes)
    || ($mode === '--preflight'
        && (string)getenv('CDX_AI_LOG_CLI_PREFLIGHT')
            !== 'ROOT_APPROVED_C_READONLY_PREFLIGHT_V1')
    || ($mode === '--preflight'
        && (string)getenv('CDX_AI_LOG_CLI_SHA')
            !== hash_file('sha256', __FILE__))
    || ($mode !== '--preflight'
        && ((string)getenv('CDX_AI_LOG_CLI_EXECUTE')
                !== 'ROOT_APPROVED_C_CHECK_ONLY_V1'
            || (string)getenv('CDX_AI_LOG_CLI_SHA')
                !== hash_file('sha256', __FILE__)))) {
    echo json_encode(['status' => 'default-deny',
        'cli_started' => false, 'db_connected' => false,
        'business_http_sent' => false, 'dml_sent' => false]), PHP_EOL;
    exit(78);
}
if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 70500
    || realpath(PHP_BINARY) !== '/www/server/php/74/bin/php') {
    throw new RuntimeException('Approved PHP7.4 CLI interpreter mismatch');
}
$root = '/tmp/zhct-ai-http-20260929.3XDj3w';
$preflightFile = '/tmp/zhct-ai-log-command-gate-20261003/preflight_log_commands.php';
$phpIni = '/www/server/php/74/etc/php-cli.ini';
$dmLib = '/home/dmdba/dmdbms/drivers/php_pdo/libphp74_dm.so';
$dmPdo = '/home/dmdba/dmdbms/drivers/php_pdo/php74_pdo_dm.so';
if (is_link($root) || !is_file($root . '/think-poc')
    || !is_file($root . '/config/database.php')
    || !is_file($phpIni) || !is_file($dmLib) || !is_file($dmPdo)
    || !is_file($preflightFile)
    || hash_file('sha256', $root . '/think-poc')
        !== 'cbd8c0b81a4ffe64b68ed931bf1bbb08bbe5e7537f991d45c0be018d48e6e5cb'
    || hash_file('sha256', $root . '/config/database.php')
        !== 'e7402b1ad50fcf4205326d93dc22e4f491c5052c60fcbe36214cd4e07b55343c'
    || hash_file('sha256', $preflightFile)
        !== '775432cec6190bdec5313675cece87d1c26c57144e2d60b236cceaadca57ffde') {
    throw new RuntimeException('Approved C CLI root/source/preflight mismatch');
}
if (!in_array('dm', PDO::getAvailableDrivers(), true)
    || !extension_loaded('dm')
    || (string)getenv('LD_LIBRARY_PATH') === ''
    || (string)getenv('DM_SVC_PATH') === '') {
    throw new RuntimeException('Approved C DM CLI runtime unavailable');
}
$rawWorkerEnv = (string)@file_get_contents('/proc/53772/environ');
$workerCmd = (string)@file_get_contents('/proc/53772/cmdline');
if ($rawWorkerEnv === ''
    || strpos($workerCmd, '127.0.0.1:18102') === false
    || strpos($workerCmd, $root . '/public') === false) {
    throw new RuntimeException('Approved C HTTP/CLI worker missing');
}
$worker = [];
foreach (explode("\0", $rawWorkerEnv) as $entry) {
    $at = strpos($entry, '=');
    if ($at !== false) $worker[substr($entry, 0, $at)] = substr($entry, $at + 1);
}
foreach (['POC_DIALECT' => 'dm', 'POC_AI_HOST' => '127.0.0.1',
    'POC_AI_PORT' => '5238', 'POC_AI_SCHEMA' => 'AIZHCT_APP',
    'POC_AI_USER' => 'AIZHCT_APP', 'POC_HOST' => '127.0.0.1',
    'POC_PORT' => '5237', 'POC_SCHEMA' => 'ZHCT_APP'] as $key => $expected) {
    if (($worker[$key] ?? null) !== $expected) {
        throw new RuntimeException('C worker DB target changed');
    }
}
foreach (['POC_AI_PASSWORD', 'POC_USER', 'POC_PASSWORD',
    'LD_LIBRARY_PATH', 'DM_SVC_PATH'] as $key) {
    if (!isset($worker[$key]) || $worker[$key] === '') {
        throw new RuntimeException('C worker CLI setting absent');
    }
}
if (getenv('LD_LIBRARY_PATH') !== $worker['LD_LIBRARY_PATH']
    || getenv('DM_SVC_PATH') !== $worker['DM_SVC_PATH']) {
    throw new RuntimeException('C CLI DM library path differs from worker');
}
// The already-reviewed SELECT-only preflight prints one JSON object. Capture it
// privately so this runner only emits bounded fields after all checks pass.
putenv('CDX_AI_LOG_LANE=C');
putenv('CDX_AI_LOG_PREFLIGHT=READ_ONLY_TWO_COMMANDS_V1');
$outerArgv = $argv;
$argv[1] = '--preflight-only';
ob_start();
require $preflightFile;
$preflightOutput = (string)ob_get_clean();
$argv = $outerArgv;
$preflight = json_decode(trim($preflightOutput), true);
if (!is_array($preflight) || ($preflight['status'] ?? null) !== 'preflight-only'
    || ($preflight['lane'] ?? null) !== 'C'
    || ($preflight['database_fingerprint'] ?? null)
        !== '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e'
    || (int)($preflight['subsidy_check_only_expected']['total_count'] ?? 10001) > 10000) {
    throw new RuntimeException('C log command SELECT-only preflight did not pass');
}
if ($mode === '--preflight') {
    echo json_encode(['status' => 'runner-preflight-only', 'lane' => 'C',
        'cli_started' => false, 'db_approved' => true,
        'balance_source' => $preflight['balance_check_only_source'],
        'subsidy_expected' => $preflight['subsidy_check_only_expected'],
        'business_http_sent' => false, 'dml_sent' => false],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if ($modes[$mode] === 'balance') {
    if (($selected['marker'] ?? null) !== 'CDXPAY1B4D604E'
        || (int)($selected['store_id'] ?? 0) !== 10001
        || (int)($selected['user_id'] ?? 0) !== 1849
        || (int)($selected['cutoff_log_id'] ?? 0) !== 5383
        || (int)($selected['owned_log_rows'] ?? 0) !== 3
        || (int)($selected['expected_missing_count'] ?? -1) !== 0
        || ($selected['source_digest'] ?? null)
            !== '4e73f19b06a91193148eee1b54d9880348fd528fea326fdd64bd90e822e4c709') {
        throw new RuntimeException('C synthetic balance source changed');
    }
} else {
    if ($subsidyTotal !== 7445 || $subsidyMissingBalance !== 3
        || $subsidyMissingOperator !== 3
        || $subsidySourceDigest
            !== '316e3c44329369dd97c21b176b748af982040b8bc38a9bf52fde72cae30871f6') {
        throw new RuntimeException('C subsidy log source changed');
    }
}

$userStmt = $db->prepare('SELECT USER_ID,STORE_ID,NICK_NAME,BALANCE,'
    . ' SUBSIDY_BALANCE FROM YOSHOP_USER WHERE USER_ID=? AND STORE_ID=?');
$userStmt->execute([1849, 10001]);
$userBefore = $userStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($userBefore) !== 1
    || (string)($userBefore[0]['nick_name'] ?? '') !== 'CDXPAY1B4D604E') {
    throw new RuntimeException('C synthetic user no longer unique');
}
$userSha = hash('sha256', serialize($userBefore[0]));
$childEnv = ['PATH' => '/usr/bin:/bin', 'LANG' => 'C.UTF-8',
    'POC_DIALECT' => 'dm', 'POC_EXTERNAL_STUBS' => '1'];
foreach (['POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER',
    'POC_AI_PASSWORD', 'POC_HOST', 'POC_PORT', 'POC_SCHEMA',
    'POC_USER', 'POC_PASSWORD', 'LD_LIBRARY_PATH', 'DM_SVC_PATH'] as $key) {
    $childEnv[$key] = $worker[$key];
}
$command = [PHP_BINARY, '-c', $phpIni,
    '-d', 'extension=' . $dmLib,
    '-d', 'extension=' . $dmPdo,
    $root . '/think-poc',
    $modes[$mode] === 'balance' ? 'initBalanceLogFields' : 'initSubsidyLogFields',
    '--check-only'];
if ($modes[$mode] === 'balance') {
    array_push($command, '--store-id=10001', '--user-id=1849',
        '--cutoff-log-id=5383');
}
$runExact = static function (array $fixedCommand, array $env, string $cwd): array {
    $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
    $proc = proc_open($fixedCommand, $descriptors, $pipes, $cwd, $env,
        ['bypass_shell' => true]);
    if (!is_resource($proc)) throw new RuntimeException('Approved CLI could not start');
    fclose($pipes[0]);
    stream_set_blocking($pipes[1], false);
    stream_set_blocking($pipes[2], false);
    $stdout = '';
    $stderr = '';
    $exitCode = -1;
    $deadline = microtime(true) + 25.0;
    $timedOut = false;
    try {
        while (true) {
            $stdout .= (string)stream_get_contents($pipes[1]);
            $stderr .= (string)stream_get_contents($pipes[2]);
            if (strlen($stdout) > 8192 || strlen($stderr) > 8192) {
                throw new RuntimeException('CLI output exceeds redacted bound');
            }
            $status = proc_get_status($proc);
            if (!$status['running']) {
                $exitCode = (int)$status['exitcode'];
                break;
            }
            if (microtime(true) > $deadline) {
                $timedOut = true;
                proc_terminate($proc);
                throw new RuntimeException('CLI timeout; do not retry');
            }
            usleep(100000);
        }
    } finally {
        $last = proc_get_status($proc);
        if ($last['running']) {
            proc_terminate($proc, 9);
        }
        if (is_resource($pipes[1])) fclose($pipes[1]);
        if (is_resource($pipes[2])) fclose($pipes[2]);
        $close = proc_close($proc);
        if ($exitCode < 0 && !$timedOut) $exitCode = (int)$close;
    }
    if ($exitCode !== 0 || $stderr !== '') {
        throw new RuntimeException('CLI error: exit=' . $exitCode
            . ' stderr_present=' . ($stderr === '' ? 'no' : 'yes')
            . ' stderr_sha=' . hash('sha256', $stderr));
    }
    $lines = array_values(array_filter(array_map('trim',
        preg_split('/\r?\n/', $stdout)), static function (string $line): bool {
        return $line !== '';
    }));
    return $lines;
};
$lines = $runExact($command, $childEnv, $root);
$expectedLines = $modes[$mode] === 'balance'
    ? ['missing_count=0']
    : ['total_count=7445', 'missing_balance_count=3',
        'missing_operator_count=3'];
if ($lines !== $expectedLines) {
    throw new RuntimeException('CLI check-only output differs from own source');
}

// Independent new PDO after the command: no mutable application ORM state.
$after = new PDO($dsn, $config['POC_AI_USER'], $config['POC_AI_PASSWORD'], [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_CASE => PDO::CASE_LOWER,
    PDO::ATTR_TIMEOUT => 5,
]);
$afterSchema = (string)$after->query(
    "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
if (hash('sha256', 'AIZHCT_APP|5238|' . $afterSchema)
    !== '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e') {
    throw new RuntimeException('Post-command AI DB identity drift');
}
$afterUserStmt = $after->prepare('SELECT USER_ID,STORE_ID,NICK_NAME,BALANCE,'
    . ' SUBSIDY_BALANCE FROM YOSHOP_USER WHERE USER_ID=? AND STORE_ID=?');
$afterUserStmt->execute([1849, 10001]);
$userAfter = $afterUserStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($userAfter) !== 1
    || hash('sha256', serialize($userAfter[0])) !== $userSha) {
    throw new RuntimeException('Synthetic user balance changed during check-only CLI');
}
$balanceStmt = $after->prepare('SELECT LOG_ID,USER_ID,STORE_ID,MONEY,BALANCE_AFTER'
    . ' FROM YOSHOP_USER_BALANCE_LOG WHERE USER_ID=? AND STORE_ID=?'
    . ' ORDER BY LOG_ID DESC FETCH FIRST 201 ROWS ONLY');
$balanceStmt->execute([1849, 10001]);
$balanceRows = $balanceStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($balanceRows) !== 3
    || $digestRows($balanceRows)
        !== '4e73f19b06a91193148eee1b54d9880348fd528fea326fdd64bd90e822e4c709') {
    throw new RuntimeException('Synthetic balance log source changed');
}
$missingStmt = $after->prepare('SELECT COUNT(*) FROM YOSHOP_USER_BALANCE_LOG'
    . ' WHERE BALANCE_AFTER IS NULL AND STORE_ID=? AND USER_ID=?'
    . ' AND LOG_ID<=?');
$missingStmt->execute([10001, 1849, 5383]);
if ((int)$missingStmt->fetchColumn() !== 0) {
    throw new RuntimeException('Synthetic balance missing count changed');
}
$totals = [(int)$after->query('SELECT COUNT(*) FROM YOSHOP_USER_SUBSIDY_LOG')
        ->fetchColumn(),
    (int)$after->query('SELECT COUNT(*) FROM YOSHOP_USER_SUBSIDY_LOG'
        . ' WHERE SUBSIDY_BALANCE_AFTER IS NULL')->fetchColumn(),
    (int)$after->query('SELECT COUNT(*) FROM YOSHOP_USER_SUBSIDY_LOG'
        . " WHERE OPERATOR_NAME=''")->fetchColumn()];
if ($totals !== [7445, 3, 3]) {
    throw new RuntimeException('Subsidy log count changed during check-only CLI');
}
$subsidyRows = $after->query('SELECT LOG_ID,USER_ID,MONEY,'
    . ' SUBSIDY_BALANCE_AFTER,OPERATOR_NAME FROM YOSHOP_USER_SUBSIDY_LOG'
    . ' ORDER BY LOG_ID ASC FETCH FIRST 10001 ROWS ONLY')->fetchAll(PDO::FETCH_ASSOC);
if (count($subsidyRows) !== 7445
    || $digestRows($subsidyRows)
        !== '316e3c44329369dd97c21b176b748af982040b8bc38a9bf52fde72cae30871f6') {
    throw new RuntimeException('Subsidy log source digest changed');
}
echo json_encode(['status' => 'check-only-cli-passed', 'lane' => 'C',
    'mode' => $modes[$mode], 'strict_command_argv' => true,
    'output_fields' => $lines, 'approved_db_unchanged' => true,
    'synthetic_user_and_balance_logs_unchanged' => true,
    'subsidy_log_count_and_digest_unchanged' => true,
    'external_http_sent' => false, 'business_dml' => false],
    JSON_UNESCAPED_SLASHES), PHP_EOL;
