<?php

declare(strict_types=1);

// Read-only, bounded A/B/C menu-snapshot HTTP contract and source-table verifier.
$lane = (string)getenv('CDX_SNAPSHOT_LANE');
$action = (string)($argv[1] ?? '');
$method = (string)(getenv('CDX_SNAPSHOT_METHOD') ?: 'GET');
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023,
        'mysql', '2026-10-11', 25, 2,
        '306f0fd3c2602f3f1c84b6c45b48f9f8456a56669a9028025724d05611962270'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001,
        'mysql', '2026-10-11', 25, 2,
        'e6071d5bd58547b0ea372a2e638af32f0805001aa55a851b0370e6379fe3721d'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175,
        'dm', '2025-01-16', 1, 2,
        'e6071d5bd58547b0ea372a2e638af32f0805001aa55a851b0370e6379fe3721d'],
];
if (!isset($targets[$lane]) || !in_array($method, ['GET', 'POST'], true)
    || !in_array($action, ['--preflight', '--execute'], true)
    || ($action === '--execute'
        && ((string)getenv('CDX_SNAPSHOT_EXECUTE') !== 'APPROVED_READONLY_V1'
            || (string)getenv('CDX_SNAPSHOT_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Snapshot execution disabled\n");
    exit(78);
}
[$root, $base, $pid, $dialect, $date, $restaurantId, $mealTimes, $logicSha] = $targets[$lane];
if ((string)getenv('POC_APP_ROOT') !== $root
    || (string)getenv('POC_BASE_URL') !== $base
    || (string)getenv('POC_DIALECT') !== $dialect
    || is_link($root)
    || hash_file('sha256', $root . '/application/m/controller/ControlScreen.php')
        !== '64726620cbdc77b12e0ab247425ad34d08c05d55cecbc24c1414627b4dcb884f'
    || hash_file('sha256', $root . '/application/m/logic/ControlScreen.php') !== $logicSha
    || hash_file('sha256', $root . '/application/model/Recipes.php')
        !== 'cd0bf238f33277efbbf0bed8fde4c7c6b3774018ea7fe2dea89aa0e8017ddc7f'
    || hash_file('sha256', $root . '/application/model/Dishes.php')
        !== 'e6f7a03b0adc458a3ed2042b6fead04b479e1793a79528f3e66ac641446a60b9') {
    throw new RuntimeException('Approved snapshot source/runtime mismatch');
}

$runtimeReady = static function () use ($root, $base, $pid): bool {
    $cmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $environment = (string)@file_get_contents('/proc/' . $pid . '/environ');
    $children = (string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children');
    $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $port = (int)parse_url($base, PHP_URL_PORT);
    $hex = strtoupper(str_pad(dechex($port), 4, '0', STR_PAD_LEFT));
    $socketInode = '';
    foreach (@file('/proc/net/tcp', FILE_IGNORE_NEW_LINES) ?: [] as $line) {
        $parts = preg_split('/\s+/', trim($line));
        if (($parts[1] ?? '') === '0100007F:' . $hex && ($parts[3] ?? '') === '0A') {
            $socketInode = (string)($parts[9] ?? '');
            break;
        }
    }
    $ownsPort = false;
    if ($socketInode !== '') {
        foreach (@scandir('/proc/' . $pid . '/fd') ?: [] as $fd) {
            if ($fd !== '.' && $fd !== '..'
                && @readlink('/proc/' . $pid . '/fd/' . $fd)
                    === 'socket:[' . $socketInode . ']') {
                $ownsPort = true;
                break;
            }
        }
    }
    $multiWorker = preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=(\d+)(?:\x00|$)/',
        $environment, $match) && (int)$match[1] > 1;
    return strpos($cmdline, '127.0.0.1:' . $port) !== false
        && strpos($cmdline, $root . '/public') !== false
        && $environment !== '' && trim($children) === '' && !$multiWorker
        && $ownsPort && is_array($boundary)
        && !empty($boundary['external_stubs'])
        && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']);
};
if (!$runtimeReady()) throw new RuntimeException('Snapshot isolated HTTP boundary absent');

$runtime = (static function (string $path): array {
    $value = require $path;
    if (!is_array($value)) throw new RuntimeException('Invalid snapshot DB config');
    return $value;
})($root . '/application/database.php');
if ((string)($runtime['prefix'] ?? '') !== 'ydy_') {
    throw new RuntimeException('Snapshot table prefix mismatch');
}
if ($dialect === 'mysql') {
    $local = (static function (): array {
        $value = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
        if (!is_array($value)) throw new RuntimeException('Invalid local snapshot DB config');
        return $value;
    })();
    if ((string)($runtime['type'] ?? '') !== 'mysql') {
        throw new RuntimeException('Snapshot MySQL driver mismatch');
    }
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('Snapshot runtime differs from code local');
        }
    }
    if ((int)($runtime['hostport'] ?? 3306) !== (int)($local['hostport'] ?? 3306)) {
        throw new RuntimeException('Snapshot MySQL port mismatch');
    }
    $dsn = 'mysql:host=' . $runtime['hostname'] . ';port='
        . (int)($runtime['hostport'] ?? 3306) . ';dbname=' . $runtime['database']
        . ';charset=' . (string)($runtime['charset'] ?? 'utf8');
    $username = (string)$runtime['username'];
    $password = (string)$runtime['password'];
} else {
    if ((string)($runtime['type'] ?? '') !== 'app\\cores\\database\\Dm'
        || (string)getenv('POC_HOST') !== '127.0.0.1'
        || (string)getenv('POC_PORT') !== '5237'
        || strtoupper((string)getenv('POC_SCHEMA')) !== 'ZHCT_APP'
        || strtoupper((string)getenv('POC_USER')) !== 'ZHCT_APP') {
        throw new RuntimeException('Snapshot DM target mismatch');
    }
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $key => $envKey) {
        if ((string)($runtime[$key] ?? '') !== (string)getenv($envKey)) {
            throw new RuntimeException('Snapshot DM worker context mismatch');
        }
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $username = (string)getenv('POC_USER');
    $password = (string)getenv('POC_PASSWORD');
}

$connect = static function () use ($dsn, $username, $password, $dialect): PDO {
    $pdo = new PDO($dsn, $username, $password,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
    if ($dialect === 'mysql') {
        $identity = (string)$pdo->query(
            "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())"
        )->fetchColumn();
        $approved = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
    } else {
        $schema = (string)$pdo->query(
            "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL"
        )->fetchColumn();
        if (strtoupper($schema) !== 'ZHCT_APP') {
            throw new RuntimeException('Snapshot DM schema mismatch');
        }
        $identity = 'ZHCT_APP|5237|' . $schema;
        $approved = 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
        $machineId = trim((string)@file_get_contents('/etc/machine-id'));
        if ($machineId === '' || gethostname() === false
            || hash('sha256', gethostname() . '|' . $machineId)
                !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
            throw new RuntimeException('Snapshot DM host mismatch');
        }
        $lines = [];
        $code = 0;
        @exec('/usr/sbin/ss -ltnp 2>/dev/null', $lines, $code);
        $localDm = false;
        if ($code === 0) {
            foreach ($lines as $line) {
                if (preg_match('/\*:5237\s+.*users:\(\("dmserver",pid=(\d+),/', $line, $match)) {
                    $exe = @readlink('/proc/' . $match[1] . '/exe');
                    $localDm = is_string($exe) && basename($exe) === 'dmserver';
                    break;
                }
            }
        }
        if (!$localDm) throw new RuntimeException('Snapshot DM listener not local');
    }
    if (!hash_equals($approved, hash('sha256', $identity))) {
        throw new RuntimeException('Snapshot approved DB identity mismatch');
    }
    return $pdo;
};

$value = static function ($input): string {
    return is_resource($input) ? (string)stream_get_contents($input) : (string)$input;
};
$fetchSource = static function () use ($connect, $dialect, $date, $restaurantId,
    $mealTimes, $value): array {
    $pdo = $connect();
    $prefix = $dialect === 'dm' ? 'YDY_' : 'ydy_';
    $sql = 'SELECT r.ID AS RECIPE_ID,r.DISHES_UUID,r.MEAL_TIMES,r.TYPE,'
        . 'r.LIMIT_NUM,d.NAME,d.FILE_PATH,d.PRICE FROM ' . $prefix . 'recipes r'
        . ' LEFT JOIN ' . $prefix . 'dishes d ON r.DISHES_UUID=d.UUID'
        . ' WHERE r.RESTAURANT_ID=? AND r.MENU_DATE=? AND r.MEAL_TIMES=?'
        . ' AND d.IS_DEL=1 ORDER BY r.TYPE ASC,r.ID ASC';
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$restaurantId, $date, $mealTimes]);
    $rows = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $normal = [];
        foreach (array_change_key_case($row, CASE_LOWER) as $key => $fieldValue) {
            $normal[$key] = $fieldValue === null ? null : $value($fieldValue);
        }
        $rows[] = $normal;
        if (count($rows) > 50) throw new RuntimeException('Snapshot source exceeds 50 rows');
    }
    $seen = [];
    $expected = [];
    $categories = [1 => '主食', 2 => '素菜', 3 => '荤菜',
        4 => '蛋奶坚果', 5 => '水果', 6 => '汤饮'];
    foreach ($rows as $row) {
        $uuid = (string)$row['dishes_uuid'];
        if ($uuid === '' || isset($seen[$uuid])) continue;
        $seen[$uuid] = true;
        $type = (int)$row['type'];
        $expected[] = [
            'id' => $uuid,
            'name' => (string)$row['name'],
            'category' => $categories[$type] ?? '其他',
            'price' => ($row['price'] === '' || $row['price'] === null)
                ? null : (float)$row['price'],
            'limit_num' => (int)$row['limit_num'],
            'file_path' => (string)$row['file_path'],
        ];
    }
    if (count($rows) < 1 || count($expected) < 1 || count($expected) > 20) {
        throw new RuntimeException('Snapshot natural source absent or too large');
    }
    return [$rows, $expected];
};

[$beforeRows, $expected] = $fetchSource();
$beforeSha = hash('sha256', serialize($beforeRows));
$result = ['lane' => $lane, 'dialect' => $dialect, 'date' => $date,
    'restaurant_id' => $restaurantId, 'meal_times' => $mealTimes,
    'source_rows' => count($beforeRows), 'unique_dishes' => count($expected),
    'source_sha' => $beforeSha, 'http_executed' => false];
if ($action === '--preflight') {
    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$httpCall = static function (array $params) use ($base, $runtimeReady, $method): array {
    if (!$runtimeReady()) throw new RuntimeException('Snapshot runtime drift before HTTP');
    $form = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    $url = $base . '/m/controlScreen/menuSnapshot'
        . ($method === 'GET' ? '?' . $form : '');
    $ctx = stream_context_create(['http' => ['method' => $method,
        'header' => $method === 'POST'
            ? "Content-Type: application/x-www-form-urlencoded\r\n" : '',
        'content' => $method === 'POST' ? $form : '',
        'timeout' => 20, 'ignore_errors' => true]]);
    $stream = @fopen($url, 'rb', false, $ctx);
    if (!$stream) throw new RuntimeException('Snapshot HTTP open failed');
    $headers = $http_response_header ?? [];
    $body = stream_get_contents($stream, 262145);
    fclose($stream);
    if (!is_string($body) || strlen($body) > 262144
        || strpos((string)($headers[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('Snapshot HTTP status or response bound mismatch');
    }
    $decoded = json_decode($body, true);
    if (!is_array($decoded)) throw new RuntimeException('Snapshot HTTP JSON invalid');
    return $decoded;
};

$valid = $httpCall(['restaurant_id' => $restaurantId,
    'menu_date' => $date, 'meal_times' => $mealTimes]);
if (array_keys($valid) !== ['code', 'message', 'data']
    || ($valid['code'] ?? null) !== 0
    || ($valid['message'] ?? null) !== '获取成功'
    || !is_array($valid['data'] ?? null)) {
    throw new RuntimeException('Snapshot success envelope mismatch');
}
$data = $valid['data'];
if (array_keys($data) !== ['restaurant_id', 'menu_date', 'meal_times',
    'updated_at', 'total', 'dishes']
    || !is_int($data['restaurant_id']) || $data['restaurant_id'] !== $restaurantId
    || !is_string($data['menu_date']) || $data['menu_date'] !== $date
    || !is_int($data['meal_times']) || $data['meal_times'] !== $mealTimes
    || !is_string($data['updated_at'])
    || !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/D', $data['updated_at'])
    || !is_int($data['total']) || $data['total'] !== count($expected)
    || !is_array($data['dishes']) || count($data['dishes']) !== count($expected)) {
    throw new RuntimeException('Snapshot response contract mismatch');
}
$priceTypes = [];
foreach ($expected as $index => $source) {
    $dish = $data['dishes'][$index] ?? null;
    if (!is_array($dish)
        || array_keys($dish) !== ['id', 'name', 'category', 'image', 'price', 'limit_num']
        || !is_string($dish['id']) || $dish['id'] !== $source['id']
        || !is_string($dish['name']) || $dish['name'] !== $source['name']
        || !is_string($dish['category']) || $dish['category'] !== $source['category']
        || !is_string($dish['image'])
        || !is_int($dish['limit_num']) || $dish['limit_num'] !== $source['limit_num']) {
        throw new RuntimeException('Snapshot per-dish identity/count/shape mismatch');
    }
    if ($source['price'] === null) {
        if ($dish['price'] !== null) throw new RuntimeException('Snapshot null price mismatch');
    } elseif ((!is_int($dish['price']) && !is_float($dish['price']))
        || abs((float)$dish['price'] - $source['price']) > 0.000001) {
        throw new RuntimeException('Snapshot per-dish price mismatch');
    }
    if ($source['file_path'] === '') {
        if ($dish['image'] !== '') throw new RuntimeException('Snapshot empty image mismatch');
    } else {
        $path = str_replace('\\', '/', $source['file_path']);
        if ($dish['image'] === '' || substr($dish['image'], -strlen($path)) !== $path) {
            throw new RuntimeException('Snapshot image path mismatch');
        }
    }
    $priceTypes[] = gettype($dish['price']);
}

$invalid = $httpCall(['restaurant_id' => $restaurantId,
    'menu_date' => $date, 'meal_times' => 4]);
if (array_keys($invalid) !== ['code', 'message', 'data']
    || ($invalid['code'] ?? null) !== 1
    || ($invalid['message'] ?? null) !== '参数不正确'
    || ($invalid['data'] ?? null) !== []) {
    throw new RuntimeException('Snapshot invalid meal_times rejection mismatch');
}
[$afterRows, $afterExpected] = $fetchSource();
$afterSha = hash('sha256', serialize($afterRows));
if (!hash_equals($beforeSha, $afterSha) || count($afterExpected) !== count($expected)) {
    throw new RuntimeException('Snapshot source changed during read-only HTTP');
}
$normalized = $valid;
unset($normalized['data']['updated_at']);
$result['http_executed'] = true;
$result['method'] = $method;
$result += [
    'success_code' => $valid['code'],
    'invalid_code' => $invalid['code'],
    'per_dish_source_match' => true,
    'source_unchanged' => true,
    'invalid_zero_business_query' => 'static-gated-before-logic',
    'price_json_types' => array_values(array_unique($priceTypes)),
    'normalized_response_sha' => hash('sha256', json_encode($normalized,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
];
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
