<?php
declare(strict_types=1);

// B/C real HTTP verification of current-PC billingRecord reads; no bill DML.
$lane = (string)($argv[1] ?? '');
$mode = (string)($argv[2] ?? '');
if (!in_array($lane, ['B', 'C'], true)
    || !in_array($mode, ['--preflight', '--execute', '--audit'], true)
    || ($mode === '--execute' && ((string)getenv('CDX_BILLREC_APPROVE') !== 'ROOT_BILLREC_READ5_V1'
        || !hash_equals(hash_file('sha256', __FILE__), (string)getenv('CDX_BILLREC_SHA'))))) {
    fwrite(STDERR, "BILLREC default deny\n");
    exit(78);
}
$root = $lane === 'B' ? '/tmp/zhct-http-store-mysql.dQTTBP'
    : '/tmp/zhct-dm-poc-20260928.fcpiiN/store';
$base = $lane === 'B' ? 'http://127.0.0.1:18081' : 'http://127.0.0.1:18080';
$pid = $lane === 'B' ? 10001 : 112175;
$expectedIdentity = $lane === 'B'
    ? '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401'
    : 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
$sourceHashes = [
    '/application/route.php' => '081b0186b7f40f0bdd237cecb3e2d678fc9344c656932c0d3b3052404e4f1510',
    '/application/p/controller/BillingRecord.php' => 'ef2e8f2a835f21ad92d6dfff7b49e71a79a9cbd2d2dfefc204f452ea4a2e1f1d',
    '/application/p/logic/BillingRecord.php' => '42bf383374c80a8774c014048c189f5c0af135ed30ef71715c05b0c97719f8ca',
];
$stage = 'boundary';
try {
    if (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400 || is_link($root)) {
        throw new RuntimeException('runtime/root drift');
    }
    $runtimeReady = static function () use ($pid, $root, $base): bool {
        $cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
        $children = trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
        $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
        return strpos($cmd, $root . '/public') !== false
            && strpos($cmd, (string)parse_url($base, PHP_URL_PORT)) !== false
            && $children === '' && is_array($boundary)
            && ($boundary['external_stubs'] ?? null) === true
            && ($boundary['redis_isolated'] ?? null) === true
            && ($boundary['real_queue'] ?? true) === false;
    };
    if (!$runtimeReady()) throw new RuntimeException('worker boundary');
    foreach ($sourceHashes as $file => $sha) {
        if (!is_file($root . $file) || is_link($root . $file)
            || !hash_equals($sha, hash_file('sha256', $root . $file))) {
            throw new RuntimeException('source drift');
        }
    }
    $stage = 'database';
    $cfg = require $root . '/application/database.php';
    if (!is_array($cfg) || ($cfg['prefix'] ?? '') !== 'ydy_') {
        throw new RuntimeException('database config');
    }
    if ($lane === 'B') {
        $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
        foreach (['hostname', 'database', 'username', 'password'] as $key) {
            if (($cfg[$key] ?? null) !== ($local[$key] ?? null)) {
                throw new RuntimeException('not code-local MySQL');
            }
        }
        $dsn = 'mysql:host=' . $cfg['hostname'] . ';port=' . ($cfg['hostport'] ?? 3306)
            . ';dbname=' . $cfg['database'] . ';charset=utf8mb4';
    } else {
        foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
            'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
            'password' => 'POC_PASSWORD'] as $key => $env) {
            if ((string)($cfg[$key] ?? '') !== (string)getenv($env)) {
                throw new RuntimeException('DM worker config');
            }
        }
        if ((string)getenv('POC_HOST') !== '127.0.0.1'
            || (string)getenv('POC_PORT') !== '5237'
            || strtoupper((string)getenv('POC_SCHEMA')) !== 'ZHCT_APP') {
            throw new RuntimeException('not approved DM');
        }
        $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    }
    $db = 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,
    ]);
    $identity = $lane === 'B'
        ? (string)$db->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn()
        : 'ZHCT_APP|5237|' . (string)$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
    if (!hash_equals($expectedIdentity, hash('sha256', $identity))) {
        throw new RuntimeException('DB identity');
    }
    $p = $lane === 'B' ? 'ydy_' : 'YDY_';
    $rows = static function (string $sql, array $args = [], int $cap = 1000) use ($db): array {
        if (!preg_match('/^SELECT\b/i', $sql)) throw new RuntimeException('not SELECT');
        $stmt = $db->prepare($sql);
        $stmt->execute($args);
        $result = [];
        while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
            if (count($result) >= $cap) throw new RuntimeException('source cap');
            $row = array_change_key_case($row, CASE_LOWER);
            foreach ($row as $key => $value) {
                if (is_resource($value)) $row[$key] = stream_get_contents($value);
            }
            ksort($row);
            $result[] = $row;
        }
        return $result;
    };
    $count = static function (string $sql, array $args = []) use ($rows): int {
        $v = $rows($sql, $args, 1);
        return (int)(reset($v[0]) ?: 0);
    };
    $userTable = $p . 'user';
    if ($mode === '--audit') {
        $marker = (string)getenv('CDX_BILLREC_MARKER');
        if (!preg_match('/^CDXBILLREC[A-F0-9]{12}$/', $marker)) exit(78);
        $n = $count('SELECT COUNT(*) FROM ' . $userTable
            . ' WHERE UUID=? OR ACCOUNT=? OR NAME=?', [$marker, $marker, $marker]);
        echo json_encode(['lane' => $lane, 'actor_zero' => $n === 0, 'actor_count' => $n]), PHP_EOL;
        exit($n === 0 ? 0 : 3);
    }
    $stage = 'source';
    $month = '2026-05';
    $billSql = 'SELECT * FROM ' . $p . 'billing_record'
        . ' WHERE STORE_ID=10001 AND IS_DELETE=0 AND BILL_MONTH=? ORDER BY ID DESC';
    $beforeBills = $rows($billSql, [$month], 200);
    if (count($beforeBills) < 1 || count($beforeBills) > 100) {
        throw new RuntimeException('unbounded/empty natural bills');
    }
    $bill = null;
    foreach ($beforeBills as $candidate) {
        $id = (int)$candidate['id'];
        $detailN = $count('SELECT COUNT(*) FROM ' . $p . 'billing_record_detail'
            . ' WHERE STORE_ID=10001 AND IS_DELETE=0 AND BILL_ID=?', [$id]);
        $logN = $count('SELECT COUNT(*) FROM ' . $p . 'billing_record_log'
            . ' WHERE STORE_ID=10001 AND BILL_ID=?', [$id]);
        if ($detailN >= 1 && $detailN <= 5 && $logN >= 1 && $logN <= 20) {
            $bill = $candidate;
            break;
        }
    }
    if (!$bill) throw new RuntimeException('natural detail/log absent');
    $billId = (int)$bill['id'];
    $snapshot = static function () use ($rows, $billSql, $month, $p, $billId): array {
        return [
            'bills' => $rows($billSql, [$month], 200),
            'details' => $rows('SELECT * FROM ' . $p . 'billing_record_detail'
                . ' WHERE STORE_ID=10001 AND IS_DELETE=0 AND BILL_ID=? ORDER BY MEAL_TYPE ASC', [$billId], 10),
            'logs' => $rows('SELECT * FROM ' . $p . 'billing_record_log'
                . ' WHERE STORE_ID=10001 AND BILL_ID=? ORDER BY ID ASC', [$billId], 30),
        ];
    };
    $sourceBefore = $snapshot();
    $digest = static function (array $source): array {
        $result = [];
        foreach ($source as $key => $value) {
            $result[$key] = ['count' => count($value),
                'sha256' => hash('sha256', json_encode($value,
                    JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE))];
        }
        return $result;
    };
    $original = $digest($sourceBefore);
    $preflight = ['lane' => $lane, 'status' => 'preflight-pass',
        'source_counts' => array_map(static function ($a) { return count($a); }, $sourceBefore),
        'selected_status' => (int)$bill['status'],
        'selected_id_sha12' => substr(hash('sha256', (string)$billId), 0, 12),
        'old_actor_count' => $count("SELECT COUNT(*) FROM " . $userTable
            . " WHERE ACCOUNT LIKE 'CDXBILLREC%'")];
    if ($preflight['old_actor_count'] !== 0) throw new RuntimeException('old actor exists');
    if ($mode === '--preflight') {
        echo json_encode($preflight), PHP_EOL;
        exit(0);
    }
    echo json_encode($preflight), PHP_EOL;
    $marker = 'CDXBILLREC' . strtoupper(bin2hex(random_bytes(6)));
    $actorId = 0;
    $failure = '';
    $result = ['lane' => $lane, 'marker' => $marker, 'routes' => [],
        'source_digest' => $original, 'source_unchanged' => false, 'actor_zero' => false];
    $shape = static function ($value): array {
        if (!is_array($value)) return ['_type' => gettype($value)];
        $out = [];
        foreach ($value as $key => $item) $out[(string)$key] = gettype($item);
        ksort($out);
        return $out;
    };
    $post = static function (string $path, array $form, string $token) use ($base, $runtimeReady): array {
        if (!in_array($path, ['/p/billingrecord/list', '/p/billingrecord/lists',
            '/p/billingrecord/statistics', '/p/billingrecord/detail', '/p/billingrecord/logs'], true)
            || !$runtimeReady()) throw new RuntimeException('HTTP boundary');
        $context = stream_context_create(['http' => ['method' => 'POST',
            'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: " . $token . "\r\n",
            'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
            'timeout' => 20, 'ignore_errors' => true]]);
        $stream = @fopen($base . $path, 'rb', false, $context);
        $body = is_resource($stream) ? stream_get_contents($stream, 2097153) : false;
        $meta = is_resource($stream) ? stream_get_meta_data($stream) : [];
        if (is_resource($stream)) fclose($stream);
        if (!is_string($body) || strlen($body) > 2097152 || !empty($meta['timed_out'])
            || strpos((string)($http_response_header[0] ?? ''), ' 200 ') === false) {
            throw new RuntimeException('HTTP incomplete');
        }
        $json = json_decode($body, true);
        if (!is_array($json) || ($json['code'] ?? null) !== 0 || !isset($json['data'])) {
            throw new RuntimeException('HTTP business response');
        }
        return $json;
    };
    try {
        $stage = 'actor';
        if ($count('SELECT COUNT(*) FROM ' . $userTable
            . ' WHERE UUID=? OR ACCOUNT=? OR NAME=?', [$marker, $marker, $marker]) !== 0) {
            throw new RuntimeException('actor collision');
        }
        $insert = $db->prepare('INSERT INTO ' . $userTable
            . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
            . ' VALUES (?,?,?,?,?,?,?,?,?)');
        $insert->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
            $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
        if ($insert->rowCount() !== 1) throw new RuntimeException('actor insert');
        $actor = $rows('SELECT ID FROM ' . $userTable
            . ' WHERE UUID=? AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?',
            [$marker, $marker, $marker, 'CODEX'], 2);
        if (count($actor) !== 1) throw new RuntimeException('actor identity');
        $actorId = (int)$actor[0]['id'];
        require_once $root . '/application/base/Security.php';
        $token = \app\base\Security::encrypt(implode("\t",
            [(string)$actorId, $marker, $marker, '0', '', '', $marker]));
        if (!is_string($token) || $token === '') throw new RuntimeException('token');
        $form = ['month' => $month, 'page' => 1, 'page_size' => 5];
        $expectedFirst = array_slice($sourceBefore['bills'], 0, 5);
        foreach (['list', 'lists'] as $route) {
            $stage = $route;
            $response = $post('/p/billingrecord/' . $route, $form, $token);
            $page = $response['data'];
            if (!is_array($page) || !is_array($page['data'] ?? null)
                || (int)($page['total'] ?? -1) !== count($sourceBefore['bills'])
                || count($page['data']) !== count($expectedFirst)) {
                throw new RuntimeException('list pagination/source count');
            }
            foreach ($page['data'] as $index => $row) {
                if ((int)($row['id'] ?? 0) !== (int)$expectedFirst[$index]['id']
                    || number_format((float)($row['amount'] ?? -1), 2, '.', '')
                        !== number_format((float)$expectedFirst[$index]['amount'], 2, '.', '')) {
                    throw new RuntimeException('list source ID/amount');
                }
            }
            $result['routes'][$route] = ['source_match' => true,
                'total' => (int)$page['total'], 'page_rows' => count($page['data']),
                'envelope' => $shape($response), 'pagination' => $shape($page),
                'bill' => $shape($page['data'][0])];
        }
        $stage = 'statistics';
        $response = $post('/p/billingrecord/statistics', $form, $token);
        $statistics = $response['data'];
        $expectedAmount = 0.0;
        foreach ($sourceBefore['bills'] as $row) $expectedAmount += (float)$row['amount'];
        if (!is_array($statistics)
            || (int)($statistics['total_people'] ?? -1) !== count($sourceBefore['bills'])
            || number_format((float)($statistics['total_amount'] ?? -1), 2, '.', '')
                !== number_format($expectedAmount, 2, '.', '')) {
            throw new RuntimeException('statistics source');
        }
        $result['routes']['statistics'] = ['source_match' => true,
            'total_people' => (int)$statistics['total_people'],
            'envelope' => $shape($response), 'data' => $shape($statistics)];
        $stage = 'detail';
        $response = $post('/p/billingrecord/detail', ['bill_id' => $billId], $token);
        $detail = $response['data'];
        if (!is_array($detail) || (int)($detail['id'] ?? 0) !== $billId
            || number_format((float)($detail['amount'] ?? -1), 2, '.', '')
                !== number_format((float)$bill['amount'], 2, '.', '')
            || !is_array($detail['meals'] ?? null)
            || count($detail['meals']) !== count($sourceBefore['details'])) {
            throw new RuntimeException('detail source ID/amount/count');
        }
        foreach ($detail['meals'] as $i => $meal) {
            if ((int)($meal['meal_type'] ?? 0) !== (int)$sourceBefore['details'][$i]['meal_type']
                || (int)($meal['count'] ?? -1) !== (int)$sourceBefore['details'][$i]['adjusted_count']
                || (string)($meal['amount'] ?? '')
                    !== number_format((float)$sourceBefore['details'][$i]['adjusted_amount'], 2, '.', '')) {
                throw new RuntimeException('detail meal source');
            }
        }
        $result['routes']['detail'] = ['source_match' => true,
            'meal_rows' => count($detail['meals']), 'envelope' => $shape($response),
            'bill' => $shape($detail), 'meal' => $shape($detail['meals'][0])];
        $stage = 'logs';
        $response = $post('/p/billingrecord/logs', ['bill_id' => $billId], $token);
        $logs = $response['data'];
        if (!is_array($logs) || count($logs) !== count($sourceBefore['logs'])) {
            throw new RuntimeException('logs source count');
        }
        foreach ($logs as $i => $log) {
            if ((int)($log['id'] ?? 0) !== (int)$sourceBefore['logs'][$i]['id']
                || (int)($log['bill_id'] ?? 0) !== $billId) {
                throw new RuntimeException('logs source ID');
            }
        }
        $result['routes']['logs'] = ['source_match' => true,
            'log_rows' => count($logs), 'envelope' => $shape($response),
            'log' => $shape($logs[0])];
        $stage = 'source-after';
        $result['source_unchanged'] = $digest($snapshot()) === $original;
        if (!$result['source_unchanged']) throw new RuntimeException('source changed');
    } catch (Throwable $error) {
        $failure = $stage . ':' . get_class($error) . ':' . (string)$error->getCode()
            . ':' . substr(hash('sha256', $error->getMessage()), 0, 12);
    } finally {
        if ($actorId > 0) {
            $actor = $rows('SELECT ID FROM ' . $userTable
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?',
                [$actorId, $marker, $marker, $marker, 'CODEX'], 2);
            if (count($actor) !== 1) throw new RuntimeException('actor cleanup identity');
            $delete = $db->prepare('DELETE FROM ' . $userTable
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?');
            $delete->execute([$actorId, $marker, $marker, $marker, 'CODEX']);
            if ($delete->rowCount() !== 1) throw new RuntimeException('actor cleanup affected');
        }
        $result['actor_zero'] = $count('SELECT COUNT(*) FROM ' . $userTable
            . ' WHERE UUID=? OR ACCOUNT=? OR NAME=?', [$marker, $marker, $marker]) === 0;
        if (!$result['source_unchanged']) {
            try { $result['source_unchanged'] = $digest($snapshot()) === $original; }
            catch (Throwable $sourceError) {
                $failure .= '|source-after:' . get_class($sourceError) . ':'
                    . substr(hash('sha256', $sourceError->getMessage()), 0, 12);
            }
        }
    }
    $result['failure'] = $failure;
    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE), PHP_EOL;
    exit($failure === '' && $result['source_unchanged'] && $result['actor_zero'] ? 0 : 1);
} catch (Throwable $error) {
    echo json_encode(['lane' => $lane, 'status' => 'blocked-before-http',
        'stage' => $stage, 'class' => get_class($error),
        'message_sha12' => substr(hash('sha256', $error->getMessage()), 0, 12)]), PHP_EOL;
    exit(2);
}
