<?php

declare(strict_types=1);

// Two read-only business routes. The only writes are one disabled synthetic admin
// token actor per run and its exact-key deletion in finally.
set_exception_handler(static function (Throwable $e): void {
    fwrite(STDERR, 'NUTRITION_HTTP_FAIL ' . get_class($e) . ':'
        . hash('sha256', $e->getMessage()) . PHP_EOL);
    exit(1);
});
$lane = (string)getenv('CDX_NEXT5_LANE');
$mode = (string)($argv[1] ?? '');
if (!in_array($lane, ['A', 'B', 'C'], true)
    || !in_array($mode, ['--preflight', '--execute'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_NUTRITION_EXECUTE') !== 'APPROVED_NUTRITION_READ2'
            || (string)getenv('CDX_NUTRITION_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Nutrition HTTP/actor disabled\n");
    exit(78);
}
$preflight = $lane === 'C'
    ? '/tmp/zhct-next5-readonly-20261004/preflight_next5.php'
    : '/workspace/zhct/zhctprompt/work_store/apifox/2026-09-30-dm8-interface-acceptance/parallel-store-next5-readonly/preflight_next5.php';
if (!is_file($preflight) || is_link($preflight)
    || hash_file('sha256', $preflight)
        !== '8f262f3645d6c7e082dc5a2d7c32d893d32eb91a3dd2365f7d88cec0db25c4d6') {
    throw new RuntimeException('Frozen source/boundary gate absent or drifted');
}
$argv[1] = '--preflight';
require $preflight;
$argv[1] = $mode;

$sourceFiles = [
    'application/p/controller/NutritionScheme.php',
    'application/p/logic/NutritionScheme.php',
    'application/model/NutritionScheme.php',
    'application/model/NutritionConsume.php',
    'application/model/NutritionStaff.php',
    'application/base/Security.php',
];
$sourceHashes = [];
foreach ($sourceFiles as $file) {
    $path = $root . '/' . $file;
    if (!is_file($path) || is_link($path)) throw new RuntimeException('Route source absent');
    $sourceHashes[$file] = hash_file('sha256', $path);
}
$currentLogic = '/workspace/zhct/zhctproject/store/application/p/logic/NutritionScheme.php';
if ($lane !== 'C' && hash_file('sha256', $currentLogic)
    !== $sourceHashes['application/p/logic/NutritionScheme.php']) {
    throw new RuntimeException('HTTP lane and active logic differ');
}
$scalar = static function (PDO $pdo, string $sql, array $args = []) {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    return $stmt->fetchColumn();
};
$connect = static function () use ($dsn, $runtime): PDO {
    return 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 = static function (PDO $pdo) use ($dialect): string {
    return $dialect === 'dm'
        ? 'ZHCT_APP|5237|' . (string)$pdo->query(
            "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
        : (string)$pdo->query(
            "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
            ->fetchColumn();
};
$sameIdentity = static function (PDO $pdo) use ($identity, $approvedDbSha): bool {
    return hash_equals($approvedDbSha, hash('sha256', $identity($pdo)));
};
if (!$sameIdentity($db)) throw new RuntimeException('Target DB identity drift');

$fingerprint = static function (PDO $pdo) use ($table): array {
    $out = [];
    foreach (['nutrition_consume', 'nutrition_staff', 'nutrition_scheme',
        'nutrition_scheme_ext'] as $name) {
        $stmt = $pdo->query('SELECT * FROM ' . $table($name));
        $rows = [];
        while (($raw = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
            if (count($rows) >= 6000) throw new RuntimeException('Nutrition fingerprint cap exceeded');
            $row = [];
            foreach (array_change_key_case($raw, CASE_LOWER) as $key => $value) {
                $row[$key] = is_resource($value) ? stream_get_contents($value) : $value;
            }
            ksort($row, SORT_STRING);
            $rows[] = serialize($row);
        }
        sort($rows, SORT_STRING);
        $out[$name] = ['rows' => count($rows),
            'sha' => hash('sha256', implode("\n", $rows))];
    }
    return $out;
};
$before = $fingerprint($db);
$actorPrefix = 'CDXNS' . $lane;
$existingActors = (int)$scalar($db, 'SELECT COUNT(*) FROM ' . $table('user')
    . ' WHERE ACCOUNT LIKE ?', [$actorPrefix . '%']);
if ($existingActors !== 0) throw new RuntimeException('Prior nutrition actor residue');
$pre = ['lane' => $lane, 'dialect' => $dialect,
    'db_sha' => $approvedDbSha, 'source_sha' => $sourceHashes,
    'nutrition_source' => $before, 'prior_actor' => 0,
    'business_http' => 0, 'dml' => 0];
if ($mode === '--preflight') {
    echo json_encode($pre, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
if ((string)getenv('CDX_NUTRITION_DB_SHA') !== $approvedDbSha
    || ($lane === 'C' && (string)getenv('CDX_NUTRITION_HOST_SHA')
        !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9')) {
    throw new RuntimeException('Explicit DB/host approval absent');
}
if ((int)$scalar($db, 'SELECT COUNT(*) FROM ' . $table('user')
    . ' WHERE ACCOUNT LIKE ?', [$actorPrefix . '%']) !== 0) {
    throw new RuntimeException('Actor state drift');
}
$marker = $actorPrefix . strtoupper(bin2hex(random_bytes(6)));
$actorId = null;
$results = [];
$failure = null;
$http = static function (string $path, array $form, string $token) use ($base): array {
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($token !== '') $headers .= 'token: ' . $token . "\r\n";
    $body = http_build_query($form);
    $context = stream_context_create(['http' => [
        'method' => 'POST', 'header' => $headers, 'content' => $body,
        'timeout' => 10, 'ignore_errors' => true]]);
    $raw = @file_get_contents($base . $path, false, $context);
    if (!is_string($raw) || strlen($raw) > 1048576) {
        throw new RuntimeException('HTTP transport/response cap failure');
    }
    $decoded = json_decode($raw, true);
    if (!is_array($decoded) || !array_key_exists('code', $decoded)
        || !array_key_exists('data', $decoded)) {
        throw new RuntimeException('HTTP JSON envelope invalid');
    }
    return [$decoded, hash('sha256', $raw)];
};
try {
    $fresh = $connect();
    if (!$sameIdentity($fresh) || $fingerprint($fresh) !== $before) {
        throw new RuntimeException('Nutrition source drift before request');
    }
    $stmt = $db->prepare('INSERT INTO ' . $table('user')
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $stmt->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    if ($stmt->rowCount() !== 1) throw new RuntimeException('Actor insert row mismatch');
    $actorId = (int)$scalar($db, 'SELECT ID FROM ' . $table('user')
        . ' WHERE UUID=? AND ACCOUNT=? AND CREATE_BY=?', [$marker, $marker, 'CODEX']);
    if ($actorId <= 0) throw new RuntimeException('Inserted actor not found');
    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('Test token unavailable');
    }
    foreach ([
        'lists' => ['/p/NutritionScheme/lists', ['page' => '1', 'page_size' => '2']],
        'schemeList' => ['/p/NutritionScheme/schemeList', ['page' => '1']],
    ] as $name => [$path, $form]) {
        [$negative] = $http($path, $form, '');
        if ((int)$negative['code'] === 0) {
            throw new RuntimeException('Unauthenticated nutrition route accepted');
        }
        [$positive, $rawSha] = $http($path, $form, $token);
        $data = $positive['data'];
        if ((int)$positive['code'] !== 0 || !is_array($data)) {
            $results[$name] = ['code' => $positive['code'],
                'message_sha' => hash('sha256', (string)$positive['message']),
                'response_sha' => $rawSha, 'business_success' => false];
            continue;
        }
        $rows = $name === 'lists' ? ($data['list'] ?? null) : ($data['rows'] ?? null);
        $total = $data['total'] ?? null;
        if (!is_array($rows) || !is_numeric($total)) {
            throw new RuntimeException('Nutrition success data shape invalid');
        }
        $fieldTypes = [];
        foreach ($rows as $row) {
            foreach ($row as $key => $value) {
                $fieldTypes[$key][gettype($value)] = true;
            }
        }
        foreach ($fieldTypes as &$types) {
            $types = array_keys($types);
            sort($types, SORT_STRING);
        }
        unset($types);
        $results[$name] = [
            'code' => $positive['code'], 'code_type' => gettype($positive['code']),
            'data_type' => gettype($data), 'rows' => count($rows),
            'total' => (int)$total, 'total_type' => gettype($total),
            'row_keys' => $rows ? array_keys($rows[0]) : [],
            'row_type' => $rows ? gettype($rows[0]) : null,
            'row_field_type_sets' => $fieldTypes,
            'response_sha' => $rawSha, 'business_success' => true,
        ];
    }
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    $cleanup = $connect();
    if (!$sameIdentity($cleanup)) throw new RuntimeException('Cleanup target identity drift');
    if ($actorId !== null) {
        $stmt = $cleanup->prepare('DELETE FROM ' . $table('user')
            . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND CREATE_BY=? AND PWD=?');
        $stmt->execute([$actorId, $marker, $marker, 'CODEX', 'DISABLED']);
        if ($stmt->rowCount() !== 1) throw new RuntimeException('Exact actor cleanup failed');
    }
    if ((int)$scalar($cleanup, 'SELECT COUNT(*) FROM ' . $table('user')
        . ' WHERE ACCOUNT LIKE ?', [$actorPrefix . '%']) !== 0
        || $fingerprint($cleanup) !== $before) {
        throw new RuntimeException('Actor/source cleanup readback mismatch');
    }
}
$out = $pre;
$out['business_http'] = count($results);
$out['business_dml'] = 0;
$out['fixture_dml_insert_and_delete'] = $actorId !== null ? 2 : 0;
unset($out['dml']);
$out['auth_negative_checked'] = count($results);
$out['actor_inserted_then_exact_deleted'] = $actorId !== null;
$out['nutrition_source_unchanged'] = true;
$out['routes'] = $results;
$out['failure_sha'] = $failure;
echo json_encode($out, JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($failure !== null) exit(1);
