<?php
declare(strict_types=1);

// Two bounded read routes only. No business DML; one short isolated File token.
$mode = (string)($argv[1] ?? '');
$lane = (string)getenv('CDX_READ6_SAFE2_LANE');
if (!in_array($mode, ['--preflight', '--read', '--token-zero'], true)
    || !in_array($lane, ['A', 'B', 'C'], true)
    || ($mode !== '--preflight'
        && ((string)getenv('CDX_READ6_SAFE2_APPROVED') !== 'ROOT_APPROVED_READ6_SAFE2_V1'
            || (string)getenv('CDX_READ6_SAFE2_SHA') !== hash_file('sha256', __FILE__)))) {
    echo json_encode(['status' => 'default_deny', 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
$identity = $lane === 'C'
    ? '/tmp/zhct-ai-read2-VgKO4CDY/read2_schema_preflight.php'
    : __DIR__ . '/../parallel-ai-next-read-safe4/read2_schema_preflight.php';
if (!is_file($identity) || hash_file('sha256', $identity)
    !== '8641ee015ad537d300de9c00f2c882e654b739d7c2194dd13ef4635ca9df1af0') {
    throw new RuntimeException('Identity preflight source drift');
}
putenv('CDX_READ2_TARGET=' . $lane);
putenv('CDX_READ2_SCHEMA_PREFLIGHT=1');
require $identity;
$stage = 'safe2_runtime';
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'class' => get_class($e), 'code' => (string)$e->getCode(),
        'reason_sha256' => hash('sha256', $e->getMessage()), 'dml' => 0]), PHP_EOL;
    exit(2);
});
$root = ['A' => '/private/tmp/zhct-http-ai-macos.zjaU8h',
    'B' => '/private/tmp/zhct-http-ai-macos.x2CCZX',
    'C' => '/tmp/zhct-ai-http-20260929.3XDj3w'][$lane];
$source = ['A' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/baseline-a-ai-api-V4.0.40',
    'B' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/ai_api',
    'C' => '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api'][$lane];
$cache = realpath($root . '/runtime/cache');
if (realpath($root) !== $root || $cache !== $root . '/runtime/cache'
    || (((int)fileperms($root) & 0077) !== 0)
    || hash_file('sha256', $root . '/config/cache.php')
        !== 'ccf1c56eb4178f3eaad8775e26a27742b2cacf662b46fb058cbb91d23783b8c6'
    || is_file($root . '/.env') || is_file($root . '/.env.poc')
    || getenv('CACHE_DRIVER') !== false || getenv('cache.driver') !== false) {
    throw new RuntimeException('Isolated File cache drift');
}
$sourceFiles = ['app/api/controller/Setting.php', 'app/api/service/Setting.php',
    'app/api/controller/Cashout.php', 'app/api/service/Cashout.php',
    'app/api/model/User.php'];
$hashes = [];
foreach ($sourceFiles as $relative) {
    $path = $source . '/' . $relative;
    if (!is_file($path)) throw new RuntimeException('Relevant source absent');
    $hashes[] = hash_file('sha256', $path);
}
$sourceSha = hash('sha256', implode('|', $hashes));
if ($mode !== '--preflight'
    && !hash_equals((string)getenv('CDX_READ6_SAFE2_SOURCE_SHA'), $sourceSha)) {
    throw new RuntimeException('Relevant source drift since preflight');
}
foreach ($config as $key => $value) {
    if (strpos($key, 'POC_') === 0) putenv($key . '=' . $value);
}
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'class' => get_class($e), 'code' => (string)$e->getCode(),
        'reason_sha256' => hash('sha256', $e->getMessage()), 'dml' => 0]), PHP_EOL;
    exit(2);
});
if (strtolower((string)\think\facade\Config::get('cache.default')) !== 'file'
    || realpath((string)\think\facade\Config::get('cache.stores.file.path')) !== $cache
    || (string)\think\facade\Config::get('cache.stores.file.prefix') !== '') {
    throw new RuntimeException('CLI and HTTP File cache differ');
}
$ai->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$store->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$select = static function (PDO $pdo, string $sql, array $bind = [], int $limit = 101): array {
    $statement = $pdo->prepare($sql);
    $statement->execute($bind);
    $rows = $statement->fetchAll(PDO::FETCH_ASSOC);
    if (count($rows) > $limit) throw new RuntimeException('Owned source row cap exceeded');
    foreach ($rows as &$row) {
        $row = array_change_key_case($row, CASE_LOWER);
        foreach ($row as &$value) if (is_resource($value)) $value = stream_get_contents($value);
        unset($value);
        ksort($row, SORT_STRING);
    }
    unset($row);
    usort($rows, static function (array $a, array $b): int {
        return strcmp(serialize($a), serialize($b));
    });
    return $rows;
};
$name = $lane === 'C' ? 'CDXPARMOB9411C2CB' : 'CDXPARMOB426CA4B0';
$aiTable = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YOSHOP_' . strtoupper($name)
        : 'yoshop_' . strtolower($name);
};
$storeTable = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YDY_' . strtoupper($name)
        : 'ydy_' . strtolower($name);
};
$user = $select($ai, 'SELECT * FROM ' . $aiTable('user')
    . ' WHERE NICK_NAME=? AND STORE_ID=10001 AND IS_DELETE=0', [$name], 1);
if (count($user) !== 1 || (int)$user[0]['user_id'] <= 0) {
    throw new RuntimeException('Owned AI synthetic identity not unique');
}
$userId = (int)$user[0]['user_id'];
$storeUser = $select($store, 'SELECT * FROM ' . $storeTable('user')
    . ' WHERE AI_USER_ID=? AND IS_DELETE=0', [$userId], 1);
if (count($storeUser) !== 1 || !isset($storeUser[0]['password_version'])) {
    throw new RuntimeException('Owned Store mapping/version not unique');
}
$version = (int)$storeUser[0]['password_version'];
$sourceState = static function () use ($ai, $store, $select, $aiTable,
    $storeTable, $userId, $name): array {
    $currentUser = $select($ai, 'SELECT * FROM ' . $aiTable('user')
        . ' WHERE NICK_NAME=? AND STORE_ID=10001 AND IS_DELETE=0', [$name], 1);
    $currentStore = $select($store, 'SELECT * FROM ' . $storeTable('user')
        . ' WHERE AI_USER_ID=? AND IS_DELETE=0', [$userId], 1);
    if (count($currentUser) !== 1 || count($currentStore) !== 1) {
        throw new RuntimeException('Owned actor changed during read window');
    }
    $settings = $select($ai, 'SELECT * FROM ' . $aiTable('wxapp_setting')
        . ' WHERE STORE_ID=10001', [], 100);
    $cashout = $select($ai, 'SELECT * FROM ' . $aiTable('cashout_order')
        . ' WHERE USER_ID=? AND IS_DELETE=0', [$userId], 100);
    return ['settings' => count($settings), 'cashout' => count($cashout),
        'sha256' => hash('sha256', serialize([
            $currentUser, $currentStore, $settings, $cashout]))];
};
$initial = $sourceState();
if ($mode === '--preflight') {
    echo json_encode(['status' => 'safe2_preflight_only', 'lane' => $lane,
        'owner_sha12' => substr(hash('sha256', (string)$userId), 0, 12),
        'source_sha256' => $sourceSha, 'settings_rows' => $initial['settings'],
        'owned_cashout_rows' => $initial['cashout'],
        'owned_source_sha256' => $initial['sha256'], 'http' => 0,
        'dml' => 0, 'token' => 0]), PHP_EOL;
    exit(0);
}
$journalPath = (string)getenv('CDX_READ6_SAFE2_JOURNAL');
if ($mode === '--token-zero') {
    if (!is_file($journalPath) || is_link($journalPath)
        || (((int)fileperms($journalPath) & 0077) !== 0)) {
        throw new RuntimeException('Private token journal missing');
    }
    $journal = json_decode((string)file_get_contents($journalPath), true);
    if (!is_array($journal) || ($journal['lane'] ?? '') !== $lane
        || ($journal['state'] ?? '') !== 'deleted_needs_zero'
        || !preg_match('/^CDXREAD6S2[0-9A-F]{36}$/', (string)($journal['token'] ?? ''))) {
        throw new RuntimeException('Token journal state drift');
    }
    if (\think\facade\Cache::has($journal['token'])) {
        throw new RuntimeException('Owned File token not independently zero');
    }
    echo json_encode(['status' => 'independent_token_zero', 'lane' => $lane,
        'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(0);
}
if ((string)getenv('CDX_READ6_SAFE2_EXPECT_SOURCE_SHA') !== $initial['sha256']) {
    throw new RuntimeException('Owned source drift since preflight');
}
$stage = 'journal_before_token';
$journalDir = sys_get_temp_dir() . '/cdx-read6-safe2-'
    . strtolower(bin2hex(random_bytes(8)));
if (!mkdir($journalDir, 0700) || !chmod($journalDir, 0700)) {
    throw new RuntimeException('Private recovery journal unavailable');
}
$journalPath = $journalDir . '/journal.json';
$token = 'CDXREAD6S2' . strtoupper(bin2hex(random_bytes(18)));
$journal = ['lane' => $lane, 'state' => 'before_token', 'token' => $token,
    'owner_sha12' => substr(hash('sha256', (string)$userId), 0, 12),
    'source_sha256' => $initial['sha256'], 'created_at' => time(),
    'expires_at' => time() + 300, 'routes' => []];
$save = static function () use (&$journal, $journalPath): void {
    $encoded = json_encode($journal, JSON_UNESCAPED_SLASHES);
    $temp = $journalPath . '.tmp';
    if (!is_string($encoded)
        || file_put_contents($temp, $encoded, LOCK_EX) !== strlen($encoded)
        || !chmod($temp, 0600) || !rename($temp, $journalPath)) {
        throw new RuntimeException('Journal write failed');
    }
};
$save();
$stage = 'short_file_token';
$model = \app\api\model\User::detail($userId);
if (empty($model) || (int)$model['user_id'] !== $userId) {
    throw new RuntimeException('Synthetic login model unavailable');
}
$journal['state'] = 'token_issued';
$save();
if (!\think\facade\Cache::set($token, ['user' => $model, 'store_id' => 10001,
        'is_login' => true, 'password_version' => $version, 'login_type' => 'sms'], 300)
    || !\think\facade\Cache::has($token)) {
    throw new RuntimeException('Short File token unavailable');
}
require_once __DIR__ . '/read6_safe2_contract.php';
$cases = [
    ['setting', 'POST', '/api/Setting/aiface'],
    ['cashout_list', 'GET', '/api/Cashout/list'],
];
foreach ($cases as [$route, $method, $path]) {
    $stage = 'http_' . $route;
    $before = $sourceState();
    if ($before['sha256'] !== $initial['sha256']) {
        throw new RuntimeException('Owned source changed before route');
    }
    $journal['state'] = 'request_started_' . $route;
    $save();
    $context = stream_context_create(['http' => ['method' => $method,
        'timeout' => 15, 'ignore_errors' => true, 'follow_location' => 0,
        'header' => 'Access-Token: ' . $token . "\r\n"
            . "platform: MP-WEIXIN\r\nversion: 2.0.51\r\nenv-version: develop\r\n",
        'content' => $method === 'POST' ? '' : null]]);
    $started = microtime(true);
    $stream = @fopen('http://127.0.0.1:' . $port . $path
        . '?storeId=10001', 'rb', false, $context);
    if ($stream === false) throw new RuntimeException('HTTP state unknown');
    try { $rawBody = stream_get_contents($stream, 65537); }
    finally { fclose($stream); }
    $elapsed = (int)round((microtime(true) - $started) * 1000);
    $httpStatus = isset($http_response_header[0])
        && preg_match('/HTTP\/\S+\s+(\d+)/', $http_response_header[0], $m)
        ? (int)$m[1] : 0;
    if (!is_string($rawBody) || strlen($rawBody) > 65536 || $elapsed >= 15000) {
        throw new RuntimeException('HTTP size/time state unknown');
    }
    $contract = read6_safe2_contract($route, $httpStatus, $rawBody);
    $after = $sourceState();
    if ($before['sha256'] !== $after['sha256']
        || $contract['state'] !== 'shape_ok'
        || ($route === 'cashout_list' && $initial['cashout'] === 0
            && $contract['branch'] !== 'empty_only')) {
        throw new RuntimeException('HTTP contract or owned DB postread mismatch');
    }
    $journal['routes'][$route] = ['http' => $httpStatus,
        'elapsed_ms' => $elapsed, 'contract' => $contract];
    $journal['state'] = 'route_verified_' . $route;
    $save();
}
$stage = 'token_delete';
if (!\think\facade\Cache::delete($token)) {
    throw new RuntimeException('Owned File token deletion uncertain');
}
$journal['state'] = 'deleted_needs_zero';
$save();
echo json_encode(['status' => 'safe2_http_verified_needs_independent_token_zero',
    'lane' => $lane, 'journal' => $journalPath,
    'settings_rows' => $initial['settings'],
    'owned_cashout_rows' => $initial['cashout'],
    'routes' => $journal['routes'], 'business_http' => 2, 'dml' => 0],
    JSON_UNESCAPED_SLASHES), PHP_EOL;
