<?php
declare(strict_types=1);

// Default deny: this is a reviewed-test candidate, never a production command.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--preflight', '--execute', '--audit-token'], true)
    || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) {
    fwrite(STDERR, "DENIED: exact phase/lane/marker required\n");
    exit(78);
}
if ($phase !== '--preflight') {
    $reviewed = (string)getenv('CDX_GOODS_W5_REVIEWED_SHA256');
    if ((string)getenv('CDX_GOODS_W5_APPROVAL') !== 'GOODS_BASIC_STATE_DELETE_V1'
        || !preg_match('/^[0-9a-f]{64}$/', $reviewed)
        || ($lane === 'B' && hash_file('sha256', __FILE__) !== $reviewed)
        || ($lane === 'C' && (string)getenv('CDX_GOODS_W5_SOURCE_CHECKED') !== '1')) {
        fwrite(STDERR, "DENIED: exact reviewed SHA/write-HTTP gate required\n");
        exit(78);
    }
}
$stage = 'start';
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    fwrite(STDERR, 'GOODS_W5_STOP stage=' . $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_file($root . '/public/index.php') || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('Isolated HTTP root absent');
}
if ($lane === 'B') {
    $cfg = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW)['DATABASE'] ?? null;
    $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'));
    if (!is_array($cfg) || 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) {
        throw new RuntimeException('B worker/config drift');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        $value = (string)($cfg[$from] ?? '');
        if ($value === '' || strpos($ps, 'POC_AI_' . $to . '=' . $value) === false) {
            throw new RuntimeException('B worker differs from local config');
        }
    }
    $connect = static function () use ($cfg): PDO {
        return 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]);
    };
    $envName = 'local';
    $goodsTable = 'yoshop_goods';
} else {
    $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/config drift');
    }
    $connect = static function () use ($env): PDO {
        $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 schema drift');
        return $db;
    };
    $envName = 'poc';
    $goodsTable = 'YOSHOP_GOODS';
}
$db = $connect();
$stage = 'preflight';
$fixtureJournal = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane)
    . '-' . strtolower($marker) . '/journal.json';
$fixture = is_file($fixtureJournal)
    ? json_decode((string)file_get_contents($fixtureJournal), true) : null;
$fixtureReady = is_array($fixture) && ($fixture['lane'] ?? '') === $lane
    && ($fixture['marker'] ?? '') === $marker
    && ($fixture['phase'] ?? '') === 'fixture_committed'
    && (int)($fixture['ids']['goods'] ?? 0) > 0;
if ($phase === '--preflight') {
    echo json_encode(['lane' => $lane, 'phase' => $phase,
        'fixture_ready' => $fixtureReady, 'business_http' => 0,
        'business_dml' => 0]), "\n";
    exit(0);
}
if (!$fixtureReady) throw new RuntimeException('Owned fixture absent');
$goodsId = (int)$fixture['ids']['goods'];
$fetchGoods = static function (PDO $d) use ($goodsTable, $goodsId, $marker): array {
    $s = $d->prepare('SELECT GOODS_ID,GOODS_NAME,STATUS,STOCK_TOTAL,IS_DELETE,GOODS_PRICE_MIN FROM '
        . $goodsTable . ' WHERE GOODS_ID=? AND STORE_ID=10001 AND GOODS_NAME=?');
    $s->execute([$goodsId, $marker]);
    $r = $s->fetchAll(PDO::FETCH_ASSOC);
    if (count($r) !== 1) throw new RuntimeException('Owned goods row absent/ambiguous');
    return $r[0];
};
$before = $fetchGoods($db);
$tokenDir = sys_get_temp_dir() . '/cdx-goods-w5-token-' . strtolower($lane)
    . '-' . strtolower($marker);
$tokenJournal = $tokenDir . '/journal.json';
define('TP_ENV', $envName);
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require_once $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName($envName)->initialize();
if (config('cache.default') !== 'file') throw new RuntimeException('Private File cache absent');
if ($phase === '--audit-token') {
    if (!is_file($tokenJournal)) throw new RuntimeException('Token journal absent');
    $j = json_decode((string)file_get_contents($tokenJournal), true);
    if (!is_array($j) || ($j['lane'] ?? '') !== $lane
        || ($j['marker'] ?? '') !== $marker
        || (int)($j['goods_id'] ?? 0) !== $goodsId
        || !preg_match('/^CDXGOODSW5[0-9A-F]{32}$/', (string)($j['token'] ?? ''))) {
        throw new RuntimeException('Token journal identity invalid');
    }
    $boundary = @fopen($base . '/__poc_boundary', 'rb', false,
        stream_context_create(['http' => ['timeout' => 5,
            'ignore_errors' => true, 'follow_location' => 0]]));
    if ($boundary === false) throw new RuntimeException('Isolated worker completion boundary absent');
    $boundaryBody = stream_get_contents($boundary, 8193);
    $boundaryMeta = stream_get_meta_data($boundary);
    $boundaryEof = feof($boundary);
    fclose($boundary);
    $boundaryJson = is_string($boundaryBody) ? json_decode($boundaryBody, true) : null;
    if (!is_string($boundaryBody) || strlen($boundaryBody) > 8192
        || !$boundaryEof || ($boundaryMeta['timed_out'] ?? false)
        || ($lane === 'C' && (!is_array($boundaryJson)
            || empty($boundaryJson['external_stubs'])))) {
        throw new RuntimeException('Isolated worker completion boundary uncertain');
    }
    $tokenZero = !\think\facade\Cache::has($j['token']);
    $goods = $fetchGoods($connect());
    $allowed = [
        'before_http' => [[20, 0]], 'token_issued' => [[20, 0]],
        'before_state_on' => [[20, 0], [10, 0]],
        'after_state_on' => [[10, 0]],
        'before_state_off' => [[10, 0], [20, 0]],
        'after_state_off' => [[20, 0]],
        'before_delete' => [[20, 0], [20, 1]],
        'after_delete' => [[20, 1]], 'completed' => [[20, 1]],
    ];
    $state = [(int)$goods['status'], (int)$goods['is_delete']];
    $phaseKey = (string)($j['phase'] ?? '');
    $knownState = isset($allowed[$phaseKey])
        && in_array($state, $allowed[$phaseKey], true)
        && (int)$goods['stock_total'] === 0;
    if (!$tokenZero || !$knownState) {
        throw new RuntimeException('Token/business recovery state not independently proven');
    }
    $completed = $phaseKey === 'completed' && $state === [20, 1];
    if (!unlink($tokenJournal) || !rmdir($tokenDir)
        || file_exists($tokenJournal) || is_dir($tokenDir)) {
        throw new RuntimeException('Token journal exact removal failed');
    }
    echo json_encode(['lane' => $lane, 'phase' => $phase,
        'token_zero' => true, 'worker_boundary_confirmed' => true,
        'db_status' => $state[0], 'db_is_delete' => $state[1],
        'goods_soft_deleted' => $completed,
        'recovery_required' => !$completed,
        'journal_absent' => true, 'business_http' => 0,
        'business_dml' => 0]), "\n";
    exit(0);
}
if ((int)$before['status'] !== 20 || (int)$before['stock_total'] !== 0
    || (int)$before['is_delete'] !== 0 || file_exists($tokenDir)
    || !mkdir($tokenDir, 0700) || !chmod($tokenDir, 0700)) {
    throw new RuntimeException('Initial state/token journal not pristine');
}
$token = 'CDXGOODSW5' . strtoupper(bin2hex(random_bytes(16)));
$j = ['lane' => $lane, 'marker' => $marker, 'goods_id' => $goodsId,
    'token' => $token, 'phase' => 'before_http'];
$save = static function () use (&$j, $tokenJournal): void {
    $body = json_encode($j, JSON_UNESCAPED_SLASHES);
    $tmp = $tokenJournal . '.tmp';
    if (!is_string($body) || file_put_contents($tmp, $body, LOCK_EX) !== strlen($body)
        || !chmod($tmp, 0600) || !rename($tmp, $tokenJournal)) {
        throw new RuntimeException('Token/recovery journal write failed');
    }
};
$save();
$shape = null;
$shape = static function ($value) use (&$shape) {
    if (is_array($value)) {
        if (array_values($value) === $value) {
            return ['@array' => count($value), '@items' => array_map($shape, $value)];
        }
        $out = [];
        foreach ($value as $key => $item) $out[(string)$key] = $shape($item);
        ksort($out);
        return $out;
    }
    return gettype($value);
};
$request = static function (string $action, string $method, array $params, ?string $auth)
    use ($base): array {
    $url = $base . '/index.php?s=/store/goods/' . $action;
    $body = $method === 'POST' ? json_encode($params, JSON_UNESCAPED_SLASHES) : '';
    if ($method === 'GET' && $params) $url .= '&' . http_build_query($params);
    $headers = $auth === null ? '' : 'Access-Token: ' . $auth . "\r\n";
    if ($method === 'POST') $headers .= "Content-Type: application/json\r\n";
    $ctx = stream_context_create(['http' => ['method' => $method,
        'header' => $headers, 'content' => $body,
        'timeout' => 20, 'ignore_errors' => true,
        'follow_location' => 0]]);
    $start = microtime(true);
    $h = @fopen($url, 'rb', false, $ctx);
    if ($h === false) return ['complete' => false, 'http' => 0, 'json' => null];
    $raw = stream_get_contents($h, 1048577);
    $meta = stream_get_meta_data($h);
    $eof = feof($h);
    fclose($h);
    $lines = $meta['wrapper_data'] ?? [];
    preg_match('/^HTTP\/\S+\s+(\d{3})(?:\s|$)/', (string)($lines[0] ?? ''), $m);
    $http = isset($m[1]) ? (int)$m[1] : 0;
    $lengthOkay = true;
    foreach ($lines as $line) {
        if (preg_match('/^Content-Length:\s*(\d+)\s*$/i', (string)$line, $n)) {
            $lengthOkay = is_string($raw) && strlen($raw) === (int)$n[1];
        }
    }
    $json = is_string($raw) && strlen($raw) <= 1048576 ? json_decode($raw, true) : null;
    $complete = $http > 0 && is_string($raw) && strlen($raw) <= 1048576
        && $eof && !($meta['timed_out'] ?? false) && $lengthOkay
        && is_array($json) && json_last_error() === JSON_ERROR_NONE;
    return ['complete' => $complete, 'http' => $http,
        'json' => $complete ? $json : null,
        'elapsed_ms' => (int)round((microtime(true) - $start) * 1000)];
};
$issued = false;
$out = ['lane' => $lane, 'marker' => $marker, 'routes' => [],
    'token_deleted' => false, 'business_dml_only_http' => true,
    'no_file_or_oss_request' => true];
try {
    $stage = 'token';
    if (!\think\facade\Cache::set($token, ['user' => [
            'store_user_id' => 2147480008, 'store_id' => 10001,
            'user_name' => $marker, 'real_name' => $marker,
            'is_super' => 1], 'store_id' => 10001,
            'is_login' => true], 300)
        || !\think\facade\Cache::has($token)) {
        throw new RuntimeException('Short File token unavailable');
    }
    $issued = true;
    $j['phase'] = 'token_issued'; $save();
    $unauth = $request('basic', 'GET', ['goodsId' => $goodsId], null);
    if (!$unauth['complete'] || (int)($unauth['json']['status'] ?? 200) === 200) {
        throw new RuntimeException('Unauthenticated basic not conclusively rejected');
    }
    $stage = 'basic';
    $basic = $request('basic', 'GET', ['goodsId' => $goodsId], $token);
    $detail = $basic['json']['data']['detail'] ?? null;
    if (!$basic['complete'] || $basic['http'] !== 200
        || (int)($basic['json']['status'] ?? -1) !== 200
        || !is_array($detail) || (int)($detail['goods_id'] ?? 0) !== $goodsId
        || (string)($detail['goods_name'] ?? '') !== $marker
        || (string)($detail['goods_price_min'] ?? '') !== '12.34'
        || (int)($detail['skuList'][0]['id'] ?? 0) !== (int)$fixture['ids']['sku']
        || (int)($detail['goods_images'][0]['file_id'] ?? 0) !== (int)$fixture['ids']['file']) {
        throw new RuntimeException('Basic HTTP/source mismatch');
    }
    $out['routes']['basic'] = ['http' => 200, 'status' => 200,
        'elapsed_ms' => $basic['elapsed_ms'], 'source_goods_id' => $goodsId,
        'goods_price_type' => gettype($detail['goods_price_min']),
        'full_json_key_type_shape' => $shape($basic['json'])];
    foreach ([['state_on', 'state', ['goodsIds' => [$goodsId], 'state' => true], 10, 0],
              ['state_off', 'state', ['goodsIds' => [$goodsId], 'state' => false], 20, 0],
              ['delete', 'delete', ['goodsIds' => [$goodsId]], 20, 1]]
             as [$key, $action, $input, $wantStatus, $wantDelete]) {
        $stage = $key;
        $j['phase'] = 'before_' . $key; $save();
        $r = $request($action, 'POST', $input, $token);
        if (!$r['complete'] || $r['http'] !== 200
            || (int)($r['json']['status'] ?? -1) !== 200) {
            throw new RuntimeException('Write HTTP result failed/uncertain');
        }
        $current = $fetchGoods($connect());
        if ((int)$current['status'] !== $wantStatus
            || (int)$current['stock_total'] !== 0
            || (int)$current['is_delete'] !== $wantDelete) {
            throw new RuntimeException('Write original-table readback mismatch');
        }
        $out['routes'][$key] = ['http' => 200, 'status' => 200,
            'elapsed_ms' => $r['elapsed_ms'],
            'db_status' => $wantStatus, 'db_is_delete' => $wantDelete,
            'full_json_key_type_shape' => $shape($r['json'])];
        $j['phase'] = 'after_' . $key; $save();
    }
    $j['phase'] = 'completed'; $save();
} catch (Throwable $e) {
    $out['failure_stage'] = $stage;
    $out['failure_class'] = get_class($e);
} finally {
    if ($issued) {
        \think\facade\Cache::delete($token);
        $out['token_deleted'] = !\think\facade\Cache::has($token);
    }
}
$out['pass'] = !isset($out['failure_class']) && $out['token_deleted']
    && count($out['routes']) === 4;
echo json_encode($out, JSON_UNESCAPED_SLASHES), "\n";
exit($out['pass'] ? 0 : 3);
