<?php
declare(strict_types=1);

// Shared offline-reviewed primitives. Inclusion alone has no IO side effects.
const CAT8_CACHE_CONFIG_SHA = 'ccf1c56eb4178f3eaad8775e26a27742b2cacf662b46fb058cbb91d23783b8c6';
const CAT8_URLS = [
    '/dishes.dishes/list', '/dishes.dishes/detail', '/dishes.dishes/cate',
    '/dishes.dishes/dishLibrary', '/dishes.foods/list',
    '/dishes.foods/detail', '/dishes.foods/all', '/dishes.foods/cate',
];

function cat8_context(string $target): array
{
    $targets = [
        'A' => ['/private/tmp/zhct-http-ai-macos.zjaU8h', 18105],
        'B' => ['/private/tmp/zhct-http-ai-macos.x2CCZX', 18101],
        'C' => ['/tmp/zhct-ai-http-20260929.3XDj3w', 18102],
    ];
    if (!isset($targets[$target])) throw new RuntimeException('Target denied');
    [$root, $port] = $targets[$target];
    if (realpath($root) !== $root && realpath($root) !== str_replace('/private/tmp/', '/tmp/', $root)) {
        throw new RuntimeException('Runtime root mismatch');
    }
    if ((fileperms($root) & 0077) !== 0
        || hash_file('sha256', $root . '/config/cache.php') !== CAT8_CACHE_CONFIG_SHA
        || is_file($root . '/.env') || is_file($root . '/.env.poc')
        || getenv('CACHE_DRIVER') !== false || getenv('cache.driver') !== false) {
        throw new RuntimeException('Private file-cache boundary mismatch');
    }

    $env = [];
    if ($target === 'C') {
        $pid = 53772;
        $cmd = (string)file_get_contents('/proc/' . $pid . '/cmdline');
        foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
            if (strpos($item, '=') === false) continue;
            [$key, $value] = explode('=', $item, 2);
            $env[$key] = $value;
        }
        $expected = [
            'POC_TARGET_ROOT' => $root, 'POC_DIALECT' => 'dm',
            'POC_EXTERNAL_STUBS' => '1',
            'POC_AI_HOST' => '127.0.0.1', 'POC_AI_PORT' => '5238',
            'POC_AI_SCHEMA' => 'AIZHCT_APP', 'POC_AI_USER' => 'AIZHCT_APP',
            'POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
            'POC_SCHEMA' => 'ZHCT_APP',
        ];
        if (strpos($cmd, '127.0.0.1:' . $port) === false
            || strpos($cmd, $root . '/public') === false
            || isset($env['CACHE_DRIVER']) || isset($env['cache.driver'])) {
            throw new RuntimeException('C isolated process mismatch');
        }
        foreach ($expected as $key => $value) {
            if (($env[$key] ?? null) !== $value) throw new RuntimeException('C identity mismatch');
        }
        if (empty($env['POC_AI_PASSWORD'])) throw new RuntimeException('C private secret unavailable');
    } else {
        $local = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';
        if (realpath($local) !== $local
            || !hash_equals((string)getenv('CDX_CAT8_DEV_CONFIG_SHA256'),
                (string)hash_file('sha256', $local))) {
            throw new RuntimeException('Reviewed development config SHA required');
        }
        $ini = parse_ini_file($local, true, INI_SCANNER_RAW);
        if (!is_array($ini) || ($ini['DATABASE']['TYPE'] ?? null) !== 'mysql'
            || ($ini['DATABASE_ZHCT']['TYPE'] ?? null) !== 'mysql') {
            throw new RuntimeException('Local development dual MySQL config required');
        }
        foreach (['DATABASE' => 'POC_AI_', 'DATABASE_ZHCT' => 'POC_'] as $section => $prefix) {
            foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT',
                'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER',
                'PASSWORD' => 'PASSWORD'] as $field => $suffix) {
                $env[$prefix . $suffix] = (string)($ini[$section][$field] ?? '');
            }
        }
        $pidText = trim((string)shell_exec('/usr/sbin/lsof -t -nP -iTCP:' . $port
            . ' -sTCP:LISTEN 2>/dev/null'));
        if (!ctype_digit($pidText)) throw new RuntimeException('Unique A/B listener required');
        $cmd = (string)shell_exec('/bin/ps eww -p ' . $pidText . ' -o command=');
        $displayRoot = str_replace('/private/tmp/', '/tmp/', $root);
        if (strpos($cmd, '-S 127.0.0.1:' . $port) === false
            || (strpos($cmd, $root . '/public') === false
                && strpos($cmd, $displayRoot . '/public') === false)
            || strpos($cmd, 'CACHE_DRIVER=') !== false
            || strpos($cmd, 'cache.driver=') !== false) {
            throw new RuntimeException('A/B isolated process mismatch');
        }
        foreach ($env as $key => $value) {
            if ($value === '' || preg_match('/(?:^|\s)'
                . preg_quote($key . '=' . $value, '/') . '(?=\s|$)/', $cmd) !== 1) {
                throw new RuntimeException('A/B process not using code local MySQL');
            }
        }
        $env['POC_DIALECT'] = 'mysql';
        $env['POC_TARGET_ROOT'] = $root;
        $env['POC_EXTERNAL_STUBS'] = '1';
    }
    $boundary = json_decode((string)file_get_contents('http://127.0.0.1:'
        . $port . '/__poc_boundary'), true);
    if (!is_array($boundary) || ($boundary['external_stubs'] ?? null) !== true) {
        throw new RuntimeException('External stub boundary missing');
    }
    $pdo = $target === 'C'
        ? 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])
        : new PDO('mysql:host=' . $env['POC_AI_HOST'] . ';port=' . $env['POC_AI_PORT']
            . ';dbname=' . $env['POC_AI_SCHEMA'] . ';charset=utf8',
            $env['POC_AI_USER'], $env['POC_AI_PASSWORD'],
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
    $actualSchema = $target === 'C'
        ? (string)$pdo->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
            ->fetchColumn()
        : (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
    if ($actualSchema !== $env['POC_AI_SCHEMA']) {
        throw new RuntimeException('Actual AI schema mismatch');
    }

    foreach ($env as $key => $value) {
        if (strpos($key, 'POC_') === 0) putenv($key . '=' . $value);
    }
    if (!defined('TP_ENV')) define('TP_ENV', 'poc');
    if (!defined('BUSINESS')) define('BUSINESS', 'aizhct');
    if (!defined('ENTERPRISE_NO')) define('ENTERPRISE_NO', '10001');
    require_once $root . '/vendor/autoload.php';
    (new \think\App($root))->setEnvName('poc')->initialize();
    $cache = config('cache');
    $filePath = realpath((string)($cache['stores']['file']['path'] ?? ''));
    $expectedPath = realpath($root . '/runtime/cache');
    if (($cache['default'] ?? null) !== 'file'
        || ($cache['stores']['file']['prefix'] ?? null) !== ''
        || !$filePath || !$expectedPath || $filePath !== $expectedPath
        || strpos($filePath . '/', realpath($root) . '/') !== 0) {
        throw new RuntimeException('Effective cache not isolated File');
    }
    return ['target' => $target, 'root' => $root, 'port' => $port,
        'schema' => $actualSchema, 'pdo' => $pdo];
}

function cat8_rows(PDO $pdo, string $sql, array $bind = []): array
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($bind);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

function cat8_one(PDO $pdo, string $sql, array $bind = []): array
{
    $rows = cat8_rows($pdo, $sql, $bind);
    if (count($rows) !== 1) throw new RuntimeException('Exact one-row precondition failed');
    return $rows[0];
}

function cat8_count(PDO $pdo, string $sql, array $bind = []): int
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($bind);
    return (int)$stmt->fetchColumn();
}

function cat8_manifest(string $path): array
{
    $resolved = realpath($path);
    if (!is_string($resolved)
        || !preg_match('#^/(?:private/)?tmp/cdx-cat8-[a-f0-9]{16}/manifest\.json$#', $resolved)
        || (fileperms($resolved) & 0077) !== 0
        || (fileperms(dirname($resolved)) & 0077) !== 0) {
        throw new RuntimeException('Private fixture journal required');
    }
    $data = json_decode((string)file_get_contents($resolved), true);
    if (!is_array($data)
        || !preg_match('/^CDXCAT8U[A-F0-9]{16}$/', (string)($data['user_name'] ?? ''))
        || !preg_match('/^CDXCAT8T[A-F0-9]{32}$/', (string)($data['token'] ?? ''))
        || !in_array(($data['target'] ?? null), ['A', 'B', 'C'], true)
        || (int)($data['store_id'] ?? 0) !== 10001) {
        throw new RuntimeException('Fixture journal identity mismatch');
    }
    $data['_path'] = $resolved;
    return $data;
}

function cat8_save_manifest(array &$data): void
{
    $path = $data['_path'];
    $stored = $data;
    unset($stored['_path']);
    $tmp = tempnam(dirname($path), 'next-');
    if (!is_string($tmp) || !chmod($tmp, 0600)) {
        throw new RuntimeException('Private journal temp failed');
    }
    $json = json_encode($stored, JSON_UNESCAPED_SLASHES);
    if (!is_string($json) || file_put_contents($tmp, $json, LOCK_EX) !== strlen($json)
        || !rename($tmp, $path)) {
        throw new RuntimeException('Atomic journal update failed');
    }
}

function cat8_cache_ttl(string $token): array
{
    $driver = \think\facade\Cache::store('file');
    if (!method_exists($driver, 'getCacheKey')) {
        throw new RuntimeException('File cache key unavailable');
    }
    $path = $driver->getCacheKey($token);
    if (!is_file($path)) throw new RuntimeException('Synthetic cache file missing');
    $handle = fopen($path, 'rb');
    if (!$handle) throw new RuntimeException('Synthetic cache header unreadable');
    $header = fread($handle, 32);
    fclose($handle);
    if (!is_string($header) || !preg_match('/^<\?php\n\/\/(\d{12})\n/', $header, $m)
        || (int)$m[1] !== 300) {
        throw new RuntimeException('Cache TTL header not 300 seconds');
    }
    clearstatcache(true, $path);
    $mtime = filemtime($path);
    if ($mtime === false || abs(time() - $mtime) > 10) {
        throw new RuntimeException('Cache TTL mtime not recent');
    }
    return ['expires_at' => $mtime + 300,
        'cache_file_sha256' => hash('sha256', $path)];
}
