<?php

declare(strict_types=1);

const OM_STORE_SHA = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
const OM_AI_SHA = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
const OM_SCHEMA_SHA = 'bd47ee6f43cf6cf3ce62159e7c6175faa1791516367d72f063fb1023c74e7393';
const OM_USER_ID = 97264;
const OM_RESTAURANT_ID = 23;

function omRuntimeCheck(string $lane): array
{
    $targets = [
        'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023,
            '5a4c8eadccae2789c2fd42fb68a0ba55771524031a877958ff42a0b70b49aba9'],
        'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001,
            '40385c2e05ad9f8abcdfb14e5cb0e7dce442be0bbc9e71bbffad9e26f898734c'],
    ];
    if (!isset($targets[$lane])) throw new RuntimeException('Unapproved A/B lane');
    [$root, $base, $pid, $orderSha] = $targets[$lane];
    if (is_link($root)) throw new RuntimeException('Runtime root is symlink');
    foreach ([
        'application/api/controller/OrderMachine.php' => '7d54990994b6f6dbb36a4e66461f136c568d7c9a673a7d8741bc92b058fc11ca',
        'application/api/service/OrderMachineOrder.php' => $orderSha,
        'application/api/service/Consume.php' => $lane === 'A'
            ? '5afd1f01b119cf001d871f7e360f76afa295d134c8499226b8a518b004e87e6f'
            : '753d0d2b8ef605fe6bf373d1bb6f18cf41c227e1caa8b5def0a93eff21498c02',
        'application/common/service/MealOrderPickup.php' => 'f7da820d6c534a8c883c062e782d9cefb81eba96edb7187978717f8d09306f19',
        'application/cron/command/MealOrder.php' => '583642393d4de0c710306eba5e6e45c36b16254e77d74a14a60a03560ab689cb',
        'application/cron/command/JsrRecoverConsumeOrderWork.php' => 'e8d77df6136a4f6f26818c624ef7a971f17ffa53d2243981d829a69ecabc4d0d',
        'application/cron/command/Jx206RefundDuplicateOffline.php' => 'c0a4b6bb42c13c4ebdb1b9dba122a5bf1edf175054958161be3ee5f796aa2719',
        'application/cron/command/MealOrderPrintRetry.php' => 'bd74acb67baa232f89b15c9a2c49b8be1c0adc9f6f5e9c8571589a8be54290e3',
        'application/cron/command/MealOrderPickupComplete.php' => 'e602b42deece3baefc8a885069bd722cf0e43014a966fbf216662ca2abc1907d',
        'application/common/enum/order/PrintStatus.php' => '5aa84eb3b36511f0468dc3c999709acbc06de2eb066f92ce575c984fbef1c7a9',
        'application/common/enum/order/PrepareStatus.php' => 'e5df5485b86821f31ee20615b32a46f9c8f4d2c401eeae6803df36cbbc5db10b',
        'application/common/service/wanyisheng/Dispatcher.php' => $lane === 'A'
            ? '0ddcd79fd1c6d47c5d29d0ebb5adcbda3c329f530ffbe0aae4d30abd87c96f7f'
            : 'a23342151cfcb12f19f2bbac4a821ad7b142d3865ef8795901033115cd9abb5d',
        'external_stubs.php' => '137eb59f11e2257a6ee55bba5b382cc13570e108c4e4bb9b358c3f0d1e104108',
        'public/router.php' => '2d444eeca77269f60826653e5d0d4a51071f7d7f1826b623dda2391dd2f35380',
    ] as $path => $sha) {
        if (!is_file($root . '/' . $path)
            || hash_file('sha256', $root . '/' . $path) !== $sha) {
            throw new RuntimeException('Approved runtime source changed: ' . $path);
        }
    }
    $cmdline = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $envRaw = (string)@file_get_contents('/proc/' . $pid . '/environ');
    $children = trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
    $port = (int)parse_url($base, PHP_URL_PORT);
    $hex = strtoupper(str_pad(dechex($port), 4, '0', STR_PAD_LEFT));
    $inode = '';
    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') {
            $inode = (string)($parts[9] ?? '');
            break;
        }
    }
    $ownsPort = false;
    foreach (@scandir('/proc/' . $pid . '/fd') ?: [] as $fd) {
        if ($fd !== '.' && $fd !== '..' && $inode !== ''
            && @readlink('/proc/' . $pid . '/fd/' . $fd) === 'socket:[' . $inode . ']') {
            $ownsPort = true;
            break;
        }
    }
    $env = [];
    foreach (explode("\0", $envRaw) as $entry) {
        $parts = explode('=', $entry, 2);
        if (count($parts) === 2) $env[$parts[0]] = $parts[1];
    }
    if (strpos($cmdline, '127.0.0.1:' . $port) === false
        || strpos($cmdline, $root . '/public') === false
        || !$ownsPort || $children !== ''
        || (int)($env['PHP_CLI_SERVER_WORKERS'] ?? 1) > 1
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1'
        || hash('sha256', ($env['POC_REDIS_HOST'] ?? '') . '|'
            . ($env['POC_REDIS_PORT'] ?? ''))
            !== 'dbe7225f994a25bffe3842028a9556b8ca74763e036c3e398e08d4d96a6cf9a1') {
        throw new RuntimeException('A/B loopback, single worker, or external stubs drift');
    }
    return [$root, $base, $pid];
}

function omConnectStore(): PDO
{
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    $a = require '/tmp/zhct-http-store-a.l5omH8/application/database.php';
    $b = require '/tmp/zhct-http-store-mysql.dQTTBP/application/database.php';
    if (!is_array($local) || !is_array($a) || !is_array($b)
        || ($a['type'] ?? '') !== 'mysql' || ($b['type'] ?? '') !== 'mysql'
        || ($a['prefix'] ?? '') !== 'ydy_' || ($b['prefix'] ?? '') !== 'ydy_') {
        throw new RuntimeException('Not approved code-local Store MySQL configuration');
    }
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($local[$key] ?? null) !== ($a[$key] ?? null)
            || ($local[$key] ?? null) !== ($b[$key] ?? null)) {
            throw new RuntimeException('A/B DB configuration differs from code local');
        }
    }
    if ((int)($a['hostport'] ?? 3306) !== (int)($b['hostport'] ?? 3306)) {
        throw new RuntimeException('A/B DB port differs');
    }
    $dsn = 'mysql:host=' . $local['hostname'] . ';port='
        . (int)($a['hostport'] ?? 3306) . ';dbname=' . $local['database'] . ';charset=utf8';
    $db = new PDO($dsn, (string)$local['username'], (string)$local['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())"
    )->fetchColumn();
    if (!hash_equals(OM_STORE_SHA, hash('sha256', $identity))) {
        throw new RuntimeException('Unapproved Store MySQL instance');
    }
    return $db;
}

function omConnectAi(): PDO
{
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    $ai = $local['ai_zhct'] ?? null;
    if (!is_array($ai) || ($ai['prefix'] ?? '') !== 'yoshop_') {
        throw new RuntimeException('Code-local AI database config absent');
    }
    $dsn = 'mysql:host=' . $ai['hostname'] . ';port=3306;dbname='
        . $ai['database'] . ';charset=utf8';
    $db = new PDO($dsn, (string)$ai['username'], (string)$ai['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())"
    )->fetchColumn();
    if (!hash_equals(OM_AI_SHA, hash('sha256', $identity))) {
        throw new RuntimeException('Unapproved AI development MySQL instance');
    }
    return $db;
}

function omSchemaFingerprint(PDO $db): string
{
    $metadata = [];
    $stmt = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME,IS_NULLABLE,COLUMN_DEFAULT,'
        . 'DATA_TYPE,EXTRA,CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?)'
        . ' ORDER BY TABLE_NAME,ORDINAL_POSITION');
    $stmt->execute(['ydy_meal_order', 'ydy_meal_order_pickup']);
    foreach ($stmt as $row) {
        $metadata[(string)$row['table_name']][] = [
            'name' => (string)$row['column_name'],
            'nullable' => (string)$row['is_nullable'],
            'default_present' => $row['column_default'] !== null,
            'type' => (string)$row['data_type'],
            'extra' => (string)$row['extra'],
            'max_length' => $row['character_maximum_length'] === null
                ? null : (int)$row['character_maximum_length'],
        ];
    }
    $one = static function (string $sql, array $args) use ($db): int {
        $query = $db->prepare($sql);
        $query->execute($args);
        return (int)$query->fetchColumn();
    };
    $tables = ['ydy_meal_order', 'ydy_meal_order_pickup'];
    $triggerCount = $one('SELECT COUNT(*) FROM information_schema.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (?,?)', $tables);
    $fkCount = $one('SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?)'
        . ' AND REFERENCED_TABLE_NAME IS NOT NULL', $tables);
    $indexes = [];
    $stmt = $db->prepare('SELECT TABLE_NAME,INDEX_NAME,GROUP_CONCAT(COLUMN_NAME'
        . ' ORDER BY SEQ_IN_INDEX) AS COLS FROM information_schema.STATISTICS'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?) AND NON_UNIQUE=0'
        . ' GROUP BY TABLE_NAME,INDEX_NAME ORDER BY TABLE_NAME,INDEX_NAME');
    $stmt->execute($tables);
    foreach ($stmt as $row) {
        $indexes[] = [
            'table' => (string)$row['table_name'],
            'index' => (string)$row['index_name'],
            'columns' => (string)$row['cols'],
        ];
    }
    $engines = [];
    $stmt = $db->prepare('SELECT TABLE_NAME,ENGINE FROM information_schema.TABLES'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME IN (?,?)');
    $stmt->execute($tables);
    foreach ($stmt as $row) $engines[(string)$row['table_name']] = (string)$row['engine'];
    $sha = hash('sha256', serialize([$metadata, $indexes, $triggerCount, $fkCount, $engines]));
    if (!hash_equals(OM_SCHEMA_SHA, $sha) || $triggerCount !== 0 || $fkCount !== 0
        || ($engines['ydy_meal_order'] ?? '') !== 'InnoDB'
        || ($engines['ydy_meal_order_pickup'] ?? '') !== 'InnoDB') {
        throw new RuntimeException('Order/pickup schema or consumer DML boundary changed');
    }
    return $sha;
}

function omSource(PDO $store, PDO $ai): array
{
    $stmt = $store->prepare('SELECT id,account,name,type,status,is_delete,'
        . 'balance,pay_money,ai_user_id,mobile FROM ydy_user WHERE id=?');
    $stmt->execute([OM_USER_ID]);
    $user = $stmt->fetch();
    if (!is_array($user)
        || !preg_match('/^CDXPAY[A-F0-9]{8}$/D', (string)$user['account'])
        || (string)$user['name'] !== (string)$user['account']
        || (int)$user['type'] !== 1 || (int)$user['status'] !== 1
        || (int)$user['is_delete'] !== 0 || (int)$user['ai_user_id'] <= 0
        || (string)$user['mobile'] !== '') {
        throw new RuntimeException('Synthetic user ownership changed');
    }
    $stmt = $store->prepare('SELECT id,name,stall_type,is_delete,status,'
        . 'morning_start,morning_end,noon_start,noon_end,night_start,night_end'
        . ' FROM ydy_restaurants WHERE id=?');
    $stmt->execute([OM_RESTAURANT_ID]);
    $stall = $stmt->fetch();
    if (!is_array($stall) || (int)$stall['stall_type'] !== 2
        || (int)$stall['is_delete'] !== 2 || (int)$stall['status'] !== 1) {
        throw new RuntimeException('Test stall ownership changed');
    }
    $stmt = $store->prepare('SELECT COUNT(*) FROM ydy_equipment WHERE restaurant_id=?'
        . ' AND del_flag=0');
    $stmt->execute([OM_RESTAURANT_ID]);
    if ((int)$stmt->fetchColumn() !== 0) {
        throw new RuntimeException('Candidate stall now has an active device/printer');
    }
    $stmt = $ai->prepare('SELECT user_id,balance,withdrawable_balance,'
        . 'activity_principal_balance,subsidy_balance,gift_balance'
        . ' FROM yoshop_user WHERE user_id=?');
    $stmt->execute([(int)$user['ai_user_id']]);
    $aiUser = $stmt->fetch();
    if (!is_array($aiUser) || (int)$aiUser['user_id'] !== (int)$user['ai_user_id']) {
        throw new RuntimeException('Synthetic user AI counterpart absent');
    }
    $ledgers = [];
    foreach (['user_balance_log', 'user_subsidy_log', 'user_gift_log'] as $table) {
        $stmt = $ai->prepare('SELECT COUNT(*) AS n,COALESCE(SUM(money),0) AS total,'
            . 'COALESCE(MAX(log_id),0) AS max_id FROM yoshop_' . $table
            . ' WHERE user_id=?');
        $stmt->execute([(int)$user['ai_user_id']]);
        $aggregate = $stmt->fetch();
        if (!is_array($aggregate) || (int)$aggregate['n'] > 1000) {
            throw new RuntimeException('Synthetic account ledger snapshot bound exceeded');
        }
        $stmt = $ai->prepare('SELECT * FROM yoshop_' . $table
            . ' WHERE user_id=? ORDER BY log_id ASC');
        $stmt->execute([(int)$user['ai_user_id']]);
        $rows = $stmt->fetchAll();
        if (count($rows) !== (int)$aggregate['n']) {
            throw new RuntimeException('Synthetic account ledger changed during snapshot');
        }
        $aggregate['rows_sha'] = hash('sha256', serialize($rows));
        $ledgers[$table] = $aggregate;
    }
    return ['user' => $user, 'stall' => $stall, 'ai_user' => $aiUser,
        'ledgers' => $ledgers];
}

function omMarkerRows(PDO $store, string $orderNo): array
{
    if (!preg_match('/^CDXPAROM[A-F0-9]{8}A$/D', $orderNo)) {
        throw new RuntimeException('Synthetic order marker invalid');
    }
    $stmt = $store->prepare('SELECT * FROM ydy_meal_order WHERE order_no=? LIMIT 2');
    $stmt->execute([$orderNo]);
    $orders = $stmt->fetchAll();
    $stmt = $store->prepare('SELECT * FROM ydy_meal_order_pickup WHERE order_no=? LIMIT 2');
    $stmt->execute([$orderNo]);
    $pickups = $stmt->fetchAll();
    if (count($orders) > 1 || count($pickups) > 1) {
        throw new RuntimeException('Synthetic marker cardinality drift');
    }
    return [$orders, $pickups];
}
