<?php

declare(strict_types=1);

// B/C only: existing synthetic recharge source, no DB write or export queue.
$lane = (string)getenv('CDX_RECHARGE_READ_LANE');
$mode = (string)($argv[1] ?? '');
$targets = [
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 18081, 10001, 'mysql'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 18080, 112175, 'dm'],
];
if (!isset($targets[$lane])
    || !in_array($mode, ['--preflight', '--execute-readonly'], true)
    || ($mode === '--execute-readonly'
        && ((string)getenv('CDX_RECHARGE_READ_EXECUTE') !== 'ROOT_APPROVED_READONLY_V1'
            || (string)getenv('CDX_RECHARGE_READ_SHA')
                !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Recharge report HTTP disabled\n");
    exit(78);
}
[$root, $port, $pid, $dialect] = $targets[$lane];
$base = 'http://127.0.0.1:' . $port;
$logicSha = '7dfdbe023dea3ea951380e6b0aed32d045778d654785195b75462a59a9aa786c';
if (is_link($root) || !is_dir($root . '/public')
    || hash_file('sha256', $root . '/application/p/controller/Report.php')
        !== '1673113e55dc7f247f697a77ece16ebdb21d1572d8a202c40abb8764cbc83f7e'
    || hash_file('sha256', $root . '/application/p/logic/RechargeOrder.php')
        !== $logicSha
    || hash_file('sha256', $root . '/application/model/Excel.php')
        !== '468f4d22f73e0bf3e6e5fac3551c690f0aafb60678b4ec38ca88ce60afad52d0') {
    throw new RuntimeException('Recharge report source/runtime mismatch');
}
$runtimeReady = static function () use ($root, $base, $pid, $port): bool {
    $cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $env = (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',
        false, stream_context_create(['http' => ['timeout' => 8,
            'follow_location' => 0, 'ignore_errors' => true]])), true);
    $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;
        }
    }
    $ownsSocket = false;
    if ($inode !== '') foreach (@scandir('/proc/' . $pid . '/fd') ?: [] as $fd) {
        if ($fd === '.' || $fd === '..') continue;
        if (@readlink('/proc/' . $pid . '/fd/' . $fd)
            === 'socket:[' . $inode . ']') {
            $ownsSocket = true;
            break;
        }
    }
    $multi = preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=(\d+)(?:\x00|$)/',
        $env, $m) && (int)$m[1] > 1;
    return strpos($cmd, '127.0.0.1:' . $port) !== false
        && strpos($cmd, $root . '/public') !== false
        && $env !== '' && trim($children) === '' && !$multi && $ownsSocket
        && is_array($boundary) && !empty($boundary['external_stubs'])
        && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']);
};
if (!$runtimeReady()) throw new RuntimeException('Isolated report listener unavailable');
$config = require $root . '/application/database.php';
if (!is_array($config) || (string)($config['prefix'] ?? '') !== 'ydy_') {
    throw new RuntimeException('Invalid Store runtime config');
}
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    if (!is_array($local) || (string)($config['type'] ?? '') !== 'mysql') {
        throw new RuntimeException('Code-local MySQL config absent');
    }
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($config[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('Store runtime differs from code local');
        }
    }
    if ((int)($config['hostport'] ?? 3306) !== (int)($local['hostport'] ?? 3306)) {
        throw new RuntimeException('Store runtime port differs from code local');
    }
    $aiConfig = array_merge($config, (array)($config['ai_zhct'] ?? []));
    $aiLocal = array_merge($local, (array)($local['ai_zhct'] ?? []));
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($aiConfig[$key] ?? null) !== ($aiLocal[$key] ?? null)) {
            throw new RuntimeException('AI runtime differs from code local');
        }
    }
    $makeDsn = static function (array $c): string {
        return 'mysql:host=' . $c['hostname']
            . ';port=' . (int)($c['hostport'] ?? 3306)
            . ';dbname=' . $c['database']
            . ';charset=' . ($c['charset'] ?? 'utf8');
    };
    $storeDsn = $makeDsn($config);
    $aiDsn = $makeDsn($aiConfig);
} else {
    if ((string)($config['type'] ?? '') !== 'app\\cores\\database\\Dm') {
        throw new RuntimeException('DM runtime type mismatch');
    }
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $field => $env) {
        if ((string)($config[$field] ?? '') !== (string)getenv($env)) {
            throw new RuntimeException('Store DM runtime context mismatch');
        }
    }
    $aiConfig = array_merge($config, (array)($config['ai_zhct'] ?? []));
    foreach (['hostname' => 'POC_AI_HOST', 'hostport' => 'POC_AI_PORT',
        'database' => 'POC_AI_SCHEMA', 'username' => 'POC_AI_USER',
        'password' => 'POC_AI_PASSWORD'] as $field => $env) {
        if ((string)($aiConfig[$field] ?? '') !== (string)getenv($env)) {
            throw new RuntimeException('AI DM runtime context mismatch');
        }
    }
    if ((string)getenv('POC_HOST') !== '127.0.0.1'
        || (string)getenv('POC_PORT') !== '5237'
        || strtoupper((string)getenv('POC_SCHEMA')) !== 'ZHCT_APP'
        || (string)getenv('POC_AI_HOST') !== '127.0.0.1'
        || (string)getenv('POC_AI_PORT') !== '5238'
        || strtoupper((string)getenv('POC_AI_SCHEMA')) !== 'AIZHCT_APP') {
        throw new RuntimeException('DM business pair mismatch');
    }
    $machineId = trim((string)@file_get_contents('/etc/machine-id'));
    if ($machineId === '' || gethostname() === false
        || hash('sha256', gethostname() . '|' . $machineId)
            !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('DM host identity mismatch');
    }
    $storeDsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $aiDsn = 'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP';
}
$connect = static function (string $dsn, array $cfg): PDO {
    try {
        return new PDO($dsn, (string)$cfg['username'], (string)$cfg['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER,
            PDO::ATTR_TIMEOUT => 5,
        ]);
    } catch (Throwable $e) {
        throw new RuntimeException('Approved DB connection failed: ' . get_class($e));
    }
};
$store = $connect($storeDsn, $config);
$ai = $connect($aiDsn, $aiConfig);
$mysqlIdentity = "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())";
$storeIdentity = $dialect === 'dm'
    ? 'ZHCT_APP|5237|' . (string)$store->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
    : (string)$store->query($mysqlIdentity)->fetchColumn();
$aiIdentity = $dialect === 'dm'
    ? 'AIZHCT_APP|5238|' . (string)$ai->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
    : (string)$ai->query($mysqlIdentity)->fetchColumn();
$approved = $dialect === 'dm'
    ? ['bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194',
        '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e']
    : ['003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401',
        '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b'];
if (!hash_equals($approved[0], hash('sha256', $storeIdentity))
    || !hash_equals($approved[1], hash('sha256', $aiIdentity))) {
    throw new RuntimeException('Business DB identity differs from approved pair');
}
$s = $dialect === 'dm' ? 'YDY_' : 'ydy_';
$a = $dialect === 'dm' ? 'YOSHOP_' : 'yoshop_';
$userTable = $s . ($dialect === 'dm' ? 'USER' : 'user');
$staffTable = $s . ($dialect === 'dm' ? 'STAFF' : 'staff');
$rechargeTable = $a . ($dialect === 'dm' ? 'RECHARGE_ORDER' : 'recharge_order');
$exportTable = $s . ($dialect === 'dm' ? 'EXPORT_LOG' : 'export_log');
$store->query('SELECT ID,UUID,ACCOUNT,NAME,TYPE,IS_DELETE FROM '
    . $userTable . ' WHERE 1=0');
$ai->query('SELECT ORDER_ID,USER_ID,PAY_METHOD,PLATFORM,PAY_PRICE,'
    . ' GIFT_MONEY,ACTUAL_MONEY,RECHARGE_DATE,PAY_STATUS,IS_DELETE FROM '
    . $rechargeTable . ' WHERE 1=0');
$candidateSql = 'SELECT u.ACCOUNT,s.AI_USER_ID,s.NAME,s.MOBILE,s.UUID FROM '
    . $userTable . ' u JOIN ' . $staffTable . ' s ON s.UUID=u.STAFF_UUID'
    . " WHERE u.ACCOUNT LIKE 'CDXPAY%' AND s.NAME=u.ACCOUNT"
    . ($dialect === 'dm' ? ' AND ROWNUM<=50 ORDER BY u.ACCOUNT'
        : ' ORDER BY u.ACCOUNT LIMIT 50');
$accounts = $store->query($candidateSql)->fetchAll(PDO::FETCH_ASSOC);
$byUser = $ai->prepare('SELECT ORDER_ID,USER_ID,PAY_METHOD,PLATFORM,PAY_PRICE,'
    . ' GIFT_MONEY,ACTUAL_MONEY,RECHARGE_DATE,PAY_STATUS,IS_DELETE FROM '
    . $rechargeTable . ' WHERE USER_ID=? AND IS_DELETE=0 AND PAY_STATUS=20'
    . ' ORDER BY ORDER_ID DESC'
    . ($dialect === 'dm' ? ' FETCH FIRST 2 ROWS ONLY' : ' LIMIT 2'));
$sourceStaff = null;
$sourceRow = null;
foreach ($accounts as $account) {
    if (!preg_match('/^CDXPAY[A-F0-9]{8}$/D', (string)$account['account'])
        || (int)$account['ai_user_id'] <= 0) continue;
    $byUser->execute([(int)$account['ai_user_id']]);
    $rows = $byUser->fetchAll(PDO::FETCH_ASSOC);
    if (count($rows) !== 1) continue;
    $sourceStaff = $account;
    $sourceRow = $rows[0];
    break;
}
if ($sourceStaff === null || $sourceRow === null) {
    throw new RuntimeException('Unique existing synthetic recharge source unavailable');
}
$marker = (string)$sourceStaff['account'];
$userId = (int)$sourceStaff['ai_user_id'];
$date = substr((string)$sourceRow['recharge_date'], 0, 10);
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/D', $date)) {
    throw new RuntimeException('Synthetic recharge date invalid');
}
$nameStmt = $store->prepare('SELECT AI_USER_ID FROM ' . $staffTable
    . ' WHERE NAME LIKE ?'
    . ($dialect === 'dm' ? ' AND ROWNUM<=2' : ' LIMIT 2'));
$nameStmt->execute(['%' . $marker . '%']);
$sameName = $nameStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($sameName) !== 1 || (int)$sameName[0]['ai_user_id'] !== $userId) {
    throw new RuntimeException('Nickname filter is not unique');
}
$boundedSql = 'SELECT ORDER_ID,USER_ID,PAY_METHOD,PLATFORM,PAY_PRICE,'
    . ' GIFT_MONEY,ACTUAL_MONEY,RECHARGE_DATE,PAY_STATUS,IS_DELETE FROM '
    . $rechargeTable . ' WHERE USER_ID=? AND IS_DELETE=0 AND PAY_STATUS=20'
    . ' AND RECHARGE_DATE BETWEEN ? AND ? AND PAY_METHOD=?'
    . ($dialect === 'dm' ? ' AND ROWNUM<=2' : ' LIMIT 2');
$bounded = $ai->prepare($boundedSql);
$readSource = static function () use ($bounded, $userId, $date, $sourceRow): array {
    $bounded->execute([$userId, $date, $date, (string)$sourceRow['pay_method']]);
    $rows = $bounded->fetchAll(PDO::FETCH_ASSOC);
    if (count($rows) !== 1) throw new RuntimeException('Recharge filter row count drift');
    return $rows[0];
};
$source = $readSource();
$sourceRowSha = hash('sha256', serialize($source));
$staffSha = hash('sha256', serialize($sourceStaff));
$readQueue = static function () use ($store, $exportTable): array {
    $row = $store->query('SELECT COUNT(*) AS TOTAL,'
        . ' SUM(CASE WHEN STATUS=0 THEN 1 ELSE 0 END) AS PENDING FROM '
        . $exportTable)->fetch(PDO::FETCH_ASSOC);
    return [(int)($row['total'] ?? -1), (int)($row['pending'] ?? 0)];
};
$queueBefore = $readQueue();
if ($queueBefore[0] < 0 || $queueBefore[1] !== 0) {
    throw new RuntimeException('Export queue not idle');
}
$actors = $store->query('SELECT ID,UUID,NAME FROM ' . $userTable
    . " WHERE ACCOUNT='admin' AND TYPE=0 AND IS_DELETE=0")
    ->fetchAll(PDO::FETCH_ASSOC);
if (count($actors) !== 1 || (int)$actors[0]['id'] <= 0
    || (string)$actors[0]['uuid'] === '') {
    throw new RuntimeException('Existing read-only report actor unavailable');
}
if ($mode === '--preflight') {
    echo json_encode(['lane' => $lane, 'mode' => 'select-only-preflight',
        'store_db_approved' => true, 'ai_db_approved' => true,
        'source_marker' => $marker, 'source_date' => $date,
        'source_sha' => $sourceRowSha, 'queue_pending_zero' => true,
        'existing_actor_present' => true,
        'http_sent' => false, 'business_dml' => false],
        JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

require_once $root . '/application/base/Security.php';
$actor = $actors[0];
$token = \app\base\Security::encrypt(implode("\t", [
    (string)$actor['id'], (string)$actor['uuid'], (string)$actor['name'],
    '0', '', '', (string)$actor['name']]));
if (!is_string($token) || $token === '') {
    throw new RuntimeException('Read-only report token unavailable');
}
$http = static function (string $method, string $route, array $form, int $limit)
    use ($base, $token, $runtimeReady, $root, $logicSha): array {
    if (!in_array($route,
        ['rechargeStatisticList', 'rechargeStatisticExport'], true)
        || !in_array($method, ['POST', 'GET'], true)
        || !$runtimeReady()
        || hash_file('sha256', $root . '/application/p/logic/RechargeOrder.php')
            !== $logicSha) {
        throw new RuntimeException('Recharge HTTP runtime/source boundary drift');
    }
    $url = $base . '/p/report/' . $route;
    if ($method === 'GET') {
        $url .= '?' . http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    }
    $options = ['method' => $method, 'timeout' => 20,
        'ignore_errors' => true, 'follow_location' => 0,
        'header' => 'token: ' . $token . "\r\n"];
    if ($method === 'POST') {
        $options['header'] .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $options['content'] = http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    }
    $stream = @fopen($url, 'rb', false, stream_context_create(['http' => $options]));
    if (!is_resource($stream)) throw new RuntimeException('Recharge HTTP transport failed');
    $headers = stream_get_meta_data($stream)['wrapper_data'] ?? [];
    $body = stream_get_contents($stream, $limit + 1);
    fclose($stream);
    if (!is_string($body) || strlen($body) > $limit) {
        throw new RuntimeException('Recharge HTTP body exceeded safe bound');
    }
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $m);
    return [(int)($m[1] ?? 0), $headers, $body];
};
$money = static function ($value): string {
    $text = (string)$value;
    if (preg_match('/^-?\d+(?:\.\d+)?$/D', $text)) {
        return bcadd($text, '0', 2);
    }
    if (!is_numeric($value)) throw new RuntimeException('Non-numeric money');
    return number_format((float)$value, 2, '.', '');
};
$types = static function (array $row): array {
    $result = array_map('gettype', $row);
    ksort($result);
    return $result;
};
$form = ['start_date' => $date, 'end_date' => $date,
    'nickname' => $marker, 'pay_method' => (string)$source['pay_method']];
[$listHttp, $listHeaders, $listBody] = $http('POST',
    'rechargeStatisticList', $form + ['page' => '1'], 1024 * 1024);
$list = json_decode($listBody, true);
$data = is_array($list) ? ($list['data'] ?? null) : null;
$rows = is_array($data) ? ($data['data'] ?? null) : null;
if ($listHttp !== 200 || !is_array($list) || ($list['code'] ?? null) !== 0
    || ($list['message'] ?? null) !== '查询成功'
    || !is_array($data) || !is_array($rows) || count($rows) !== 1
    || (int)($data['total'] ?? 0) !== 1
    || (int)($data['per_page'] ?? 0) !== 15) {
    throw new RuntimeException('Recharge list HTTP/envelope mismatch');
}
$row = $rows[0];
$methodNames = ['wechat' => '微信支付', 'alipay' => '支付宝支付',
    'balance' => '余额支付', 'wechat_offline' => '线下微信支付',
    'alipay_offline' => '线下支付宝支付', 'cash' => '现金支付',
    'psbc' => '邮储支付', 'zhangjiagang_bank' => '张家港农商银行'];
$rawMethod = (string)$source['pay_method'];
if ((int)($row['user_id'] ?? 0) !== $userId
    || (string)($row['user']['name'] ?? '') !== $marker
    || (string)($row['mobile'] ?? '') !== (string)$sourceStaff['mobile']
    || (string)($row['recharge_date'] ?? '') !== $date . '至' . $date
    || (string)($row['pay_method'] ?? '') !== (string)$source['pay_method']
    || (string)($row['recharge_method'] ?? '')
        !== ($methodNames[$rawMethod] ?? $rawMethod)
    || (string)($row['recharge_type'] ?? '')
        !== ((string)$source['platform'] === 'PC' ? '线下' : '线上')) {
    throw new RuntimeException('Recharge list identity/enum mismatch');
}
foreach (['pay_amount' => 'pay_price', 'gift_amount' => 'gift_money',
    'recharge_amount' => 'actual_money'] as $responseField => $sourceField) {
    if (!isset($row[$responseField]) || !is_string($row[$responseField])
        || !preg_match('/^-?\d+\.\d{2}$/D', $row[$responseField])
        || $money($row[$responseField]) !== $money($source[$sourceField])) {
        throw new RuntimeException('Recharge list row amount/type mismatch');
    }
}
foreach (['total_pay_price' => 'pay_price',
    'total_gift_money' => 'gift_money',
    'total_price' => 'actual_money'] as $responseField => $sourceField) {
    if (!isset($data[$responseField])
        || $money($data[$responseField]) !== $money($source[$sourceField])) {
        throw new RuntimeException('Recharge list total mismatch');
    }
}
$listForHash = $list;
unset($listForHash['trace_id']);
$listResult = ['http' => $listHttp,
    'response_sha' => hash('sha256', json_encode($listForHash,
        JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE)),
    'top_types' => $types($data), 'row_types' => $types($row),
    'own_source_amounts_match' => true];

[$exportHttp, $exportHeaders, $xls] = $http('GET',
    'rechargeStatisticExport', $form, 2 * 1024 * 1024);
$mediaType = '';
$attachment = false;
foreach ($exportHeaders as $header) {
    if (stripos($header, 'Content-Type:') === 0) {
        $mediaType = trim((string)strtok(substr($header, 13), ';'));
    }
    if (stripos($header, 'Content-Disposition:') === 0
        && stripos($header, 'attachment;') !== false) $attachment = true;
}
if ($exportHttp !== 200
    || $mediaType !== 'application/vnd.ms-excel' || !$attachment
    || substr($xls, 0, 8) !== hex2bin('d0cf11e0a1b11ae1')) {
    throw new RuntimeException('Recharge synchronous Excel5 signature mismatch');
}
$temp = tempnam(sys_get_temp_dir(), 'cdxrechargexls-');
if (!is_string($temp)) throw new RuntimeException('Private XLS temp unavailable');
$cells = [];
try {
    chmod($temp, 0600);
    if (file_put_contents($temp, $xls) !== strlen($xls)) {
        throw new RuntimeException('Private XLS write incomplete');
    }
    require_once $root . '/vendor/autoload.php';
    $reader = \PHPExcel_IOFactory::createReader('Excel5');
    $book = $reader->load($temp);
    $sheet = $book->getActiveSheet();
    if ((int)$sheet->getHighestRow() !== 3
        || (string)$sheet->getHighestColumn() !== 'J') {
        throw new RuntimeException('Recharge XLS dimensions mismatch');
    }
    for ($r = 1; $r <= 3; $r++) {
        $line = [];
        for ($c = 0; $c < 10; $c++) {
            $line[] = $sheet->getCellByColumnAndRow($c, $r)->getValue();
        }
        $cells[] = $line;
    }
    $book->disconnectWorksheets();
} finally {
    if (is_file($temp)) unlink($temp);
}
$header = ['序号', '充值日期', '部门名称', '姓名', '手机号',
    '充值类型', '充值方式', '支付金额', '赠送金额', '实际到账金额'];
if ($cells[0] !== $header
    || (int)$cells[1][0] !== 1
    || (string)$cells[1][1] !== $date . '至' . $date
    || (string)$cells[1][2] !== (string)($row['department_name'] ?? '')
    || (string)$cells[1][3] !== $marker
    || (string)$cells[1][4] !== (string)$sourceStaff['mobile']
    || (string)$cells[1][5] !== (string)($row['recharge_type'] ?? '')
    || (string)$cells[1][6] !== (string)($row['recharge_method'] ?? '')
    || (string)$cells[2][0] !== '合计') {
    throw new RuntimeException('Recharge XLS text cell mismatch');
}
foreach (['pay_price', 'gift_money', 'actual_money'] as $i => $field) {
    $column = 7 + $i;
    if ($money($cells[1][$column]) !== $money($source[$field])
        || $money($cells[2][$column]) !== $money($source[$field])) {
        throw new RuntimeException('Recharge XLS money cell mismatch');
    }
}
for ($c = 1; $c <= 6; $c++) {
    if ($cells[2][$c] !== null && $cells[2][$c] !== '') {
        throw new RuntimeException('Recharge XLS summary blank cell mismatch');
    }
}
$storeAfter = $connect($storeDsn, $config);
$aiAfter = $connect($aiDsn, $aiConfig);
$storeAfterIdentity = $dialect === 'dm'
    ? 'ZHCT_APP|5237|' . (string)$storeAfter->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
    : (string)$storeAfter->query($mysqlIdentity)->fetchColumn();
$aiAfterIdentity = $dialect === 'dm'
    ? 'AIZHCT_APP|5238|' . (string)$aiAfter->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()
    : (string)$aiAfter->query($mysqlIdentity)->fetchColumn();
if (!hash_equals($approved[0], hash('sha256', $storeAfterIdentity))
    || !hash_equals($approved[1], hash('sha256', $aiAfterIdentity))) {
    throw new RuntimeException('Independent post-HTTP DB identity drift');
}
$sourceAfterStmt = $aiAfter->prepare($boundedSql);
$sourceAfterStmt->execute([$userId, $date, $date,
    (string)$sourceRow['pay_method']]);
$sourceAfter = $sourceAfterStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($sourceAfter) !== 1
    || hash('sha256', serialize($sourceAfter[0])) !== $sourceRowSha) {
    throw new RuntimeException('AI recharge source changed after read-only HTTP');
}
$staffAfterStmt = $storeAfter->prepare('SELECT u.ACCOUNT,s.AI_USER_ID,'
    . ' s.NAME,s.MOBILE,s.UUID FROM ' . $userTable . ' u JOIN '
    . $staffTable . ' s ON s.UUID=u.STAFF_UUID WHERE u.ACCOUNT=?');
$staffAfterStmt->execute([$marker]);
$staffAfter = $staffAfterStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($staffAfter) !== 1
    || hash('sha256', serialize($staffAfter[0])) !== $staffSha) {
    throw new RuntimeException('Store staff source changed after read-only HTTP');
}
$queueAfterRow = $storeAfter->query('SELECT COUNT(*) AS TOTAL,'
    . ' SUM(CASE WHEN STATUS=0 THEN 1 ELSE 0 END) AS PENDING FROM '
    . $exportTable)->fetch(PDO::FETCH_ASSOC);
$queueAfter = [(int)($queueAfterRow['total'] ?? -1),
    (int)($queueAfterRow['pending'] ?? 0)];
if ($queueAfter !== $queueBefore || is_file($temp)) {
    throw new RuntimeException('Export queue/temp changed after synchronous read');
}
echo json_encode(['lane' => $lane, 'mode' => 'read-only-http',
    'source_marker' => $marker, 'source_date' => $date,
    'list' => $listResult,
    'export' => ['http' => $exportHttp, 'format' => 'Excel5',
        'content_type' => $mediaType, 'columns' => 10, 'rows' => 3,
        'all_cells_match_own_source' => true,
        'xls_sha' => hash('sha256', $xls)],
    'store_staff_unchanged' => true, 'ai_recharge_unchanged' => true,
    'export_queue_unchanged' => true, 'private_xls_removed' => true,
    'business_dml' => false], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    . PHP_EOL;
