<?php
declare(strict_types=1);

// Two current ai_store GET calls only. No business DML or external calls.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
if (!in_array($phase, ['--probe', '--run'], true)
    || !in_array($lane, ['B', 'C'], true)
    || ($phase === '--run' && (string)getenv('CDX_EXPRESS_READ2_RUN')
        !== 'TWO_NATURAL_GETS_V1')) {
    fwrite(STDERR, "DENIED: exact phase/lane/run gate required\n");
    exit(78);
}
$GLOBALS['stage'] = 'setup';
set_exception_handler(static function (Throwable $e): void {
    fwrite(STDERR, 'EXPRESS_READ2_FAILED stage=' . $GLOBALS['stage']
        . ' class=' . get_class($e) . "\n");
    exit(3);
});

$root = $lane === 'B'
    ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$pid = $lane === 'B' ? 13127 : 53772;
$port = $lane === 'B' ? 18101 : 18102;
$base = 'http://127.0.0.1:' . $port;
if (!is_dir($root) || !is_file($root . '/public/index.php')
    || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('Isolated HTTP root absent');
}

if ($lane === 'B') {
    $GLOBALS['stage'] = 'B-identity';
    $config = parse_ini_file(
        '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local',
        true, INI_SCANNER_RAW);
    if (!is_array($config) || !isset($config['DATABASE'], $config['DATABASE_ZHCT'])) {
        throw new RuntimeException('Code local database config absent');
    }
    $cfg = $config['DATABASE'];
    $ps = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:'
        . $port . ' -sTCP:LISTEN 2>/dev/null'));
    $has = static function (string $key, string $value) use ($ps): bool {
        return $value !== '' && strpos($ps, $key . '=' . $value) !== false;
    };
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT',
        'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER',
        'PASSWORD' => 'PASSWORD'] as $from => $to) {
        if (!$has('POC_AI_' . $to, (string)($cfg[$from] ?? ''))) {
            throw new RuntimeException('B worker differs from code .env.local');
        }
    }
    if (strtolower((string)($cfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($cfg['HOSTPORT'] ?? '') !== '3306'
        || $owner !== (string)$pid
        || strpos($ps, 'POC_DIALECT=mysql') === false
        || strpos($ps, 'POC_EXTERNAL_STUBS=1') === false
        || hash_file('sha256', $root . '/config/database.php')
            !== '8c46a6d9cd277f2950818d09162f639a62f135a5eb6553e6c61ce2c19167fd02'
        || hash_file('sha256', $root . '/config/cache.php')
            !== '98fa13484feb3db94bc4ebdd3de2f0b4ebc2c80d1cccb7a283a16f45eb9095a4'
        || hash_file('sha256', $root . '/public/index.php')
            !== '87ff41c14c0380cfba8cb99b02c8e440ad90f975e2e129dfb6e5657a36a7868d') {
        throw new RuntimeException('B worker/config boundary drift');
    }
    $db = new PDO('mysql:host=' . $cfg['HOSTNAME']
        . ';port=' . $cfg['HOSTPORT'] . ';dbname=' . $cfg['DATABASE']
        . ';charset=utf8', $cfg['USERNAME'], $cfg['PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $table = 'yoshop_express';
} else {
    $GLOBALS['stage'] = 'C-identity';
    $env = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ'))
        as $item) {
        $pair = explode('=', $item, 2);
        if (count($pair) === 2) $env[$pair[0]] = $pair[1];
    }
    if (($env['POC_TARGET_ROOT'] ?? '') !== $root
        || ($env['POC_BASE_URL'] ?? '') !== $base
        || ($env['POC_DIALECT'] ?? '') !== 'dm'
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1'
        || ($env['POC_AI_HOST'] ?? '') !== '127.0.0.1'
        || ($env['POC_AI_PORT'] ?? '') !== '5238'
        || ($env['POC_AI_SCHEMA'] ?? '') !== 'AIZHCT_APP'
        || ($env['POC_AI_USER'] ?? '') !== 'AIZHCT_APP'
        || !in_array('dm', PDO::getAvailableDrivers(), true)) {
        throw new RuntimeException('C worker identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        $env['POC_AI_USER'], $env['POC_AI_PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
        ->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C effective schema drift');
    }
    $table = 'YOSHOP_EXPRESS';
}

$GLOBALS['stage'] = 'original-source';
$snapshot = static function () use ($db, $table): array {
    $rows = $db->query('SELECT * FROM ' . $table
        . ' WHERE STORE_ID=10001 ORDER BY EXPRESS_ID')->fetchAll(PDO::FETCH_ASSOC);
    if (count($rows) < 1 || count($rows) > 50) {
        throw new RuntimeException('Natural Express source absent/unbounded');
    }
    $ids = array_map(static function (array $r): int {
        return (int)$r['express_id'];
    }, $rows);
    return ['ids' => $ids, 'digest' => hash('sha256', json_encode($rows,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))];
};
$before = $snapshot();
if ($phase === '--probe') {
    echo json_encode(['lane' => $lane, 'source_rows' => count($before['ids']),
        'source_sha256' => $before['digest'], 'business_http' => 0,
        'business_dml' => 0]), "\n";
    exit(0);
}

define('TP_ENV', $lane === 'B' ? 'local' : 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require_once $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName($lane === 'B' ? 'local' : 'poc')->initialize();
if (config('cache.default') !== 'file') {
    throw new RuntimeException('Isolated File cache unavailable');
}
$shape = null;
$shape = static function ($v) use (&$shape) {
    if (is_array($v)) {
        if (array_values($v) === $v) {
            return ['@array' => count($v), '@item' => count($v) ? $shape($v[0]) : null];
        }
        $r = [];
        foreach ($v as $k => $item) $r[(string)$k] = $shape($item);
        ksort($r);
        return $r;
    }
    if (is_int($v)) return 'integer';
    if (is_float($v)) return 'double';
    if (is_bool($v)) return 'boolean';
    if (is_string($v)) return 'string';
    return 'null';
};
$token = 'CDXEXPRESS' . strtoupper(bin2hex(random_bytes(16)));
$actor = ['user' => ['store_user_id' => 2147480003,
    'store_id' => 10001, 'user_name' => 'CDX_EXPRESS_TEST',
    'real_name' => 'CDX_EXPRESS_TEST', 'is_super' => 1],
    'store_id' => 10001, 'is_login' => true];
$request = static function (string $action, ?string $search, ?string $token)
    use ($base): array {
    $url = $base . '/index.php?s=/store/setting.express/' . $action;
    if ($search !== null) $url .= '&search=' . rawurlencode($search);
    $header = $token === null ? '' : 'Access-Token: ' . $token . "\r\n";
    $context = stream_context_create(['http' => ['method' => 'GET',
        'header' => $header, 'timeout' => 15,
        'ignore_errors' => true, 'follow_location' => 0]]);
    $start = microtime(true);
    $raw = @file_get_contents($url, false, $context);
    preg_match('/HTTP\/\S+\s+(\d+)/', $http_response_header[0] ?? '', $m);
    $json = is_string($raw) ? json_decode($raw, true) : null;
    unset($raw);
    return ['http' => isset($m[1]) ? (int)$m[1] : 0,
        'json' => is_array($json) ? $json : null,
        'elapsed_ms' => (int)round((microtime(true) - $start) * 1000)];
};
$out = ['lane' => $lane, 'source_rows' => count($before['ids']),
    'source_sha256' => $before['digest'], 'routes' => [],
    'business_dml' => 0, 'token_deleted' => false];
$issued = false;
try {
    $GLOBALS['stage'] = 'token';
    if (!\think\facade\Cache::set($token, $actor, 300)
        || !\think\facade\Cache::has($token)) {
        throw new RuntimeException('Short File token unavailable');
    }
    $issued = true;
    $unauth = $request('list', null, null);
    $out['unauth_list'] = ['http' => $unauth['http'],
        'status' => $unauth['json']['status'] ?? null,
        'rejected' => is_array($unauth['json'])
            && (int)($unauth['json']['status'] ?? 200) !== 200];
    if (!$out['unauth_list']['rejected']) {
        throw new RuntimeException('Unauthenticated list not rejected');
    }
    foreach (['list', 'all', 'search_absent'] as $action) {
        $GLOBALS['stage'] = $action;
        $route = $action === 'search_absent' ? 'list' : $action;
        $search = $action === 'search_absent' ? 'CDXEXPRESSNOTFOUND' : null;
        $result = $request($route, $search, $token);
        $json = $result['json'];
        $data = is_array($json) ? ($json['data'] ?? null) : null;
        $items = $action === 'all' ? ($data['list'] ?? null)
            : ($data['list']['data'] ?? null);
        $returned = is_array($items) ? array_map(static function ($item): int {
            return is_array($item) ? (int)($item['express_id'] ?? 0) : 0;
        }, $items) : [];
        sort($returned);
        $expected = $action === 'search_absent' ? [] : $before['ids'];
        sort($expected);
        $after = $snapshot();
        $match = $result['http'] === 200 && is_array($json)
            && (int)($json['status'] ?? -1) === 200
            && $returned === $expected;
        $unchanged = $after['digest'] === $before['digest'];
        $out['routes'][$action] = ['http' => $result['http'],
            'status' => is_array($json) ? ($json['status'] ?? null) : null,
            'elapsed_ms' => $result['elapsed_ms'],
            'returned_count' => count($returned),
            'source_count' => count($before['ids']),
            'ids_match_source' => $match, 'source_unchanged' => $unchanged,
            'json_type_shape' => is_array($json) ? $shape($json) : null];
        if (!$match || !$unchanged) {
            throw new RuntimeException('Express response/source mismatch');
        }
    }
} catch (Throwable $e) {
    $out['failure_stage'] = $GLOBALS['stage'];
    $out['failure_class'] = get_class($e);
} finally {
    if ($issued) {
        \think\facade\Cache::delete($token);
        $out['token_deleted'] = !\think\facade\Cache::has($token);
    }
    $out['final_source_unchanged'] = $snapshot()['digest'] === $before['digest'];
}
$out['pass'] = !isset($out['failure_class']) && $out['token_deleted']
    && $out['final_source_unchanged'] && count($out['routes']) === 3;
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit($out['pass'] ? 0 : 3);
