<?php
declare(strict_types=1);

// DEFAULT DENY until exact reviewed SHA and HTTP gate are supplied.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--probe', '--run', '--audit-token'], true)
    || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXGOODS[0-9A-F]{10}$/', $marker)) {
    fwrite(STDERR, "DENIED: phase/lane/marker required\n");
    exit(78);
}
if (in_array($phase, ['--run', '--audit-token'], true)) {
    $reviewed = (string)getenv('CDX_GOODS_HTTP_REVIEWED_SHA256');
    if ((string)getenv('CDX_GOODS_HTTP_APPROVAL') !== 'OWNED_GOODS_HTTP_V1'
        || !preg_match('/^[0-9a-f]{64}$/', $reviewed)
        || ($lane === 'B' && hash_file('sha256', __FILE__) !== $reviewed)
        || ($lane === 'C' && (string)getenv('CDX_GOODS_HTTP_SOURCE_CHECKED') !== '1')) {
        fwrite(STDERR, "DENIED: reviewed HTTP SHA/gate required\n");
        exit(78);
    }
}
$stage = 'start';
set_exception_handler(static function (Throwable $e) use (&$stage, $lane, $marker): void {
    fwrite(STDERR, 'GOODS_HTTP_STOP lane=' . $lane . ' marker=' . $marker
        . ' 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;
foreach ([
    'app/store/controller/Goods.php' => 'f96dd2dac9e05d3ca86cf3dc9630b601ab984fad9ff042ba49bbf1ed6d6f22a4',
    'app/store/model/Goods.php' => '808061924d69a82d1578661a35f87c725d1c65d8e204e8042250aa310a2669a9',
] as $file => $sha) {
    if (!is_file($root . '/' . $file) || hash_file('sha256', $root . '/' . $file) !== $sha) {
        throw new RuntimeException('Isolated business source drift');
    }
}
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 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 code 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';
    $table = ['goods' => 'yoshop_goods', 'sku' => 'yoshop_goods_sku',
        'image' => 'yoshop_goods_image', 'relation' => 'yoshop_goods_category_rel',
        'category' => 'yoshop_category', 'file' => 'yoshop_upload_file'];
} 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 drift');
    }
    $connect = static function () use ($env): PDO {
        $d = 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 ($d->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
            ->fetchColumn() !== 'AIZHCT_APP') throw new RuntimeException('C schema drift');
        return $d;
    };
    $envName = 'poc';
    $table = ['goods' => 'YOSHOP_GOODS', 'sku' => 'YOSHOP_GOODS_SKU',
        'image' => 'YOSHOP_GOODS_IMAGE', 'relation' => 'YOSHOP_GOODS_CATEGORY_REL',
        'category' => 'YOSHOP_CATEGORY', 'file' => 'YOSHOP_UPLOAD_FILE'];
}
$fixturePath = sys_get_temp_dir() . '/cdx-goods-' . strtolower($lane) . '-'
    . strtolower($marker) . '/journal.json';
if (!is_file($fixturePath)) throw new RuntimeException('Fixture journal absent');
$fixture = json_decode((string)file_get_contents($fixturePath), true);
if (!is_array($fixture) || ($fixture['lane'] ?? '') !== $lane
    || ($fixture['marker'] ?? '') !== $marker
    || ($fixture['phase'] ?? '') !== 'fixture_committed') {
    throw new RuntimeException('Fixture not committed');
}
$ids = $fixture['ids'] ?? [];
foreach (['goods', 'sku', 'image', 'relation', 'category', 'file'] as $key) {
    if (!isset($ids[$key]) || (int)$ids[$key] <= 0) throw new RuntimeException('Fixture ID absent');
    $ids[$key] = (int)$ids[$key];
}
$db = $connect();
$rows = static function (PDO $d, string $sql, array $args = []): array {
    $s = $d->prepare($sql);
    $s->execute($args);
    return $s->fetchAll(PDO::FETCH_ASSOC);
};
$own = static function (PDO $d) use ($rows, $ids, $table): array {
    $pk = ['goods' => 'GOODS_ID', 'sku' => 'ID', 'image' => 'ID',
        'relation' => 'ID', 'category' => 'CATEGORY_ID', 'file' => 'FILE_ID'];
    $found = [];
    foreach ($table as $logical => $name) {
        $found[$logical] = $rows($d, 'SELECT * FROM ' . $name
            . ' WHERE ' . $pk[$logical] . '=? AND STORE_ID=10001', [$ids[$logical]]);
        if (count($found[$logical]) !== 1) throw new RuntimeException('Fixture row missing');
    }
    return $found;
};
$fingerprint = static function (array $a): string {
    return hash('sha256', serialize($a));
};
$before = $own($db);
if ((string)$before['goods'][0]['goods_name'] !== $marker
    || (string)$before['sku'][0]['goods_sku_no'] !== $marker . 'SKU'
    || (string)$before['category'][0]['name'] !== $marker . 'CAT'
    || (string)$before['file'][0]['file_name'] !== $marker . '.png'
    || (int)$before['goods'][0]['status'] !== 20
    || (int)$before['goods'][0]['stock_total'] !== 0
    || abs((float)$before['goods'][0]['goods_price_min'] - 12.34) > 0.0001
    || abs((float)$before['sku'][0]['goods_price'] - 12.34) > 0.0001
    || (string)$before['file'][0]['storage'] !== 'local'
    || (int)$before['sku'][0]['goods_id'] !== $ids['goods']
    || (int)$before['sku'][0]['image_id'] !== $ids['file']
    || (int)$before['image'][0]['goods_id'] !== $ids['goods']
    || (int)$before['image'][0]['image_id'] !== $ids['file']
    || (int)$before['relation'][0]['goods_id'] !== $ids['goods']
    || (int)$before['relation'][0]['category_id'] !== $ids['category']) {
    throw new RuntimeException('Fixture cross-link/state mismatch');
}
if ($phase === '--probe') {
    echo json_encode(['phase' => $phase, 'lane' => $lane,
        'marker' => $marker, 'fixture_rows' => 6,
        'source_sha256' => $fingerprint($before),
        'business_http' => 0, 'business_dml' => 0]), "\n";
    exit(0);
}
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');
$tokenDir = sys_get_temp_dir() . '/cdx-goods-token-' . strtolower($lane)
    . '-' . strtolower($marker);
$tokenJournal = $tokenDir . '/journal.json';
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
        || !preg_match('/^CDXGOODSTOKEN[0-9A-F]{32}$/', (string)($j['token'] ?? ''))) {
        throw new RuntimeException('Token journal invalid');
    }
    $zero = !\think\facade\Cache::has($j['token']);
    if (!$zero || $fingerprint($own($connect())) !== ($j['source_sha256'] ?? '')) {
        throw new RuntimeException('Independent token/source zero failed');
    }
    if (!unlink($tokenJournal) || !rmdir($tokenDir)
        || is_file($tokenJournal) || is_dir($tokenDir)) {
        throw new RuntimeException('Token journal exact removal failed');
    }
    echo json_encode(['phase' => $phase, 'lane' => $lane,
        'token_zero' => true, 'source_unchanged' => true,
        'journal_absent' => true, 'business_http' => 0,
        'business_dml' => 0]), "\n";
    exit(0);
}
if (file_exists($tokenDir) || !mkdir($tokenDir, 0700)
    || !chmod($tokenDir, 0700)) throw new RuntimeException('Token journal not pristine');
$token = 'CDXGOODSTOKEN' . strtoupper(bin2hex(random_bytes(16)));
$j = ['lane' => $lane, 'marker' => $marker,
    'token' => $token, 'source_sha256' => $fingerprint($before),
    'phase' => 'before_token'];
$writeTokenJournal = 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 journal write failed');
    }
};
$writeTokenJournal();
$shape = null;
$shape = static function ($v) use (&$shape) {
    if (is_array($v)) {
        if (array_values($v) === $v) {
            return ['@array' => count($v), '@items' => array_map($shape, $v)];
        }
        $out = [];
        foreach ($v as $k => $value) $out[(string)$k] = $shape($value);
        ksort($out);
        return $out;
    }
    return gettype($v);
};
$request = static function (string $path, array $query, ?string $auth) use ($base): array {
    $url = $base . '/index.php?s=/store/goods/' . $path;
    if ($query) $url .= '&' . http_build_query($query);
    $header = $auth === null ? '' : 'Access-Token: ' . $auth . "\r\n";
    $ctx = stream_context_create(['http' => ['method' => 'GET',
        'header' => $header, 'timeout' => 20,
        'ignore_errors' => true, 'follow_location' => 0]]);
    $start = microtime(true);
    $stream = @fopen($url, 'rb', false, $ctx);
    if ($stream === false) {
        return ['http' => 0, 'json' => null, 'complete' => false,
            'reason' => 'open_failed', 'elapsed_ms' => (int)round((microtime(true) - $start) * 1000)];
    }
    $limit = 1048576;
    $raw = stream_get_contents($stream, $limit + 1);
    $meta = stream_get_meta_data($stream);
    $eof = feof($stream);
    fclose($stream);
    $headers = $meta['wrapper_data'] ?? [];
    $statusLine = is_array($headers) ? (string)($headers[0] ?? '') : '';
    preg_match('/^HTTP\/\S+\s+(\d{3})(?:\s|$)/', $statusLine, $m);
    $http = isset($m[1]) ? (int)$m[1] : 0;
    $timedOut = (bool)($meta['timed_out'] ?? false);
    $oversize = !is_string($raw) || strlen($raw) > $limit;
    $lengthMatches = true;
    if (is_array($headers)) {
        foreach ($headers as $line) {
            if (preg_match('/^Content-Length:\s*(\d+)\s*$/i', (string)$line, $len)) {
                $lengthMatches = is_string($raw) && strlen($raw) === (int)$len[1];
            }
        }
    }
    $decoded = is_string($raw) && !$oversize
        ? json_decode($raw, true) : null;
    $jsonValid = is_array($decoded) && json_last_error() === JSON_ERROR_NONE;
    $complete = $http > 0 && is_string($raw) && $eof && !$timedOut
        && !$oversize && $lengthMatches && $jsonValid;
    unset($raw);
    return ['http' => $http, 'json' => $jsonValid ? $decoded : null,
        'complete' => $complete, 'timed_out' => $timedOut,
        'oversize' => $oversize, 'eof' => $eof,
        'content_length_matches' => $lengthMatches,
        'elapsed_ms' => (int)round((microtime(true) - $start) * 1000)];
};
$out = ['lane' => $lane, 'marker' => $marker,
    'routes' => [], 'source_sha256' => $fingerprint($before),
    'token_deleted' => false, 'source_unchanged' => false,
    'business_dml' => 0, 'no_file_or_oss_request' => true];
$issued = false;
try {
    $stage = 'token-issue';
    if (!\think\facade\Cache::set($token, ['user' => [
        'store_user_id' => 2147480006, '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';
    $writeTokenJournal();
    $unauth = $request('list', ['goodsName' => $marker], null);
    if (!$unauth['complete']) {
        $out['uncertain_http']['unauth_list'] = array_diff_key($unauth, ['json' => true]);
        throw new RuntimeException('Unauthenticated HTTP result incomplete');
    }
    if (!is_array($unauth['json'])
        || (int)($unauth['json']['status'] ?? 200) === 200) {
        throw new RuntimeException('Unauthenticated list not rejected');
    }
    $out['unauth_rejected'] = true;
    foreach ([
        'list' => ['list', ['goodsName' => $marker, 'listType' => 'all']],
        'listByIds' => ['listByIds', ['goodsIds' => [$ids['goods']]]],
        'detail' => ['detail', ['goodsId' => $ids['goods']]],
    ] as $key => [$path, $params]) {
        $stage = 'route-' . $key;
        $r = $request($path, $params, $token);
        $json = $r['json'];
        if (!$r['complete']) {
            $out['uncertain_http'][$key] = array_diff_key($r, ['json' => true]);
            throw new RuntimeException('Business HTTP result incomplete');
        }
        if ($r['http'] !== 200 || !is_array($json)
            || (int)($json['status'] ?? -1) !== 200) {
            throw new RuntimeException('Business HTTP/JSON status failed');
        }
        $data = $json['data'] ?? null;
        $item = $key === 'detail' ? ($data['goodsInfo'] ?? null)
            : ($key === 'list' ? ($data['list']['data'][0] ?? null)
                : ($data['list'][0] ?? null));
        $actualCount = $key === 'detail' ? 1
            : ($key === 'list' ? count($data['list']['data'] ?? [])
                : count($data['list'] ?? []));
        if (!is_array($item) || $actualCount !== 1
            || (int)($item['goods_id'] ?? 0) !== $ids['goods']
            || (string)($item['goods_name'] ?? '') !== $marker
            || (int)($item['status'] ?? 0) !== 20
            || (int)($item['stock_total'] ?? -1) !== 0
            || abs((float)($item['goods_price_min'] ?? -1)
                - (float)$before['goods'][0]['goods_price_min']) > 0.0001
            || abs((float)($item['goods_price_max'] ?? -1)
                - (float)$before['goods'][0]['goods_price_max']) > 0.0001
            || !is_array($item['goods_images'] ?? null)
            || count($item['goods_images']) !== 1
            || (int)($item['goods_images'][0]['file_id'] ?? 0) !== $ids['file']
            || !is_string($item['goods_image'] ?? null)) {
            throw new RuntimeException('Goods row/price/image response mismatch');
        }
        if ($key === 'detail') {
            if (!is_array($item['skuList'] ?? null)
                || count($item['skuList']) !== 1
                || (int)($item['skuList'][0]['id'] ?? 0) !== $ids['sku']
                || abs((float)($item['skuList'][0]['goods_price'] ?? -1)
                    - (float)$before['sku'][0]['goods_price']) > 0.0001
                || (int)($item['skuList'][0]['image']['file_id'] ?? 0) !== $ids['file']
                || !is_array($item['categoryIds'] ?? null)
                || array_map('intval', $item['categoryIds']) !== [$ids['category']]) {
                throw new RuntimeException('Detail SKU/category relation mismatch');
            }
        }
        $out['routes'][$key] = ['http' => $r['http'],
            'status' => $json['status'], 'elapsed_ms' => $r['elapsed_ms'],
            'source_goods_id' => $ids['goods'],
            'price_12_34' => true, 'one_image' => true,
            'full_json_key_type_shape' => $shape($json)];
        if ($fingerprint($own($connect())) !== $out['source_sha256']) {
            throw new RuntimeException('Business source changed during GET');
        }
    }
} 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);
    }
    $j['phase'] = 'token_delete_attempted';
    $writeTokenJournal();
    $out['source_unchanged'] = $fingerprint($own($connect()))
        === $out['source_sha256'];
}
$out['pass'] = !isset($out['failure_class']) && $out['token_deleted']
    && $out['source_unchanged'] && count($out['routes']) === 3;
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
exit($out['pass'] ? 0 : 3);
