<?php
declare(strict_types=1);

// Current-PC cash-refund read-only HTTP batch. Never requests agree/reject/export.
$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_CR3_APPROVE') !== 'ROOT_CASHREFUND_READ3_V1'
        || !hash_equals(hash_file('sha256', __FILE__), (string)getenv('CDX_CR3_SHA'))))) {
    fwrite(STDERR, "CR3 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;
$expectedStoreSha = $lane === 'B'
    ? '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401'
    : 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
$sourceHashes = [
    '/application/route.php' => '081b0186b7f40f0bdd237cecb3e2d678fc9344c656932c0d3b3052404e4f1510',
    '/application/p/controller/CashRefund.php' => 'cd24ac67287603d536510ef7405b7fa3998c6bc99bc5a29399601cad0ea01172',
    '/application/p/logic/CashRefund.php' => 'b0bd6f2d9a956116538ae4326cd35ab8d1b68a4576a2cb265372bd23cefd7c3d',
];
$stage = 'boundary';
try {
    $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 (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400 || is_link($root)
        || !$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('business source drift');
        }
    }
    $stage = 'database';
    $cfg = require $root . '/application/database.php';
    if (!is_array($cfg) || ($cfg['prefix'] ?? '') !== 'ydy_'
        || ($cfg['ai_zhct']['prefix'] ?? '') !== 'yoshop_') {
        throw new RuntimeException('database config');
    }
    $ac = $cfg['ai_zhct'];
    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)
                || ($ac[$key] ?? null) !== ($local['ai_zhct'][$key] ?? null)) {
                throw new RuntimeException('not code-local MySQL');
            }
        }
        $storeDsn = 'mysql:host=' . $cfg['hostname'] . ';port=' . ($cfg['hostport'] ?? 3306)
            . ';dbname=' . $cfg['database'] . ';charset=utf8mb4';
        $aiDsn = 'mysql:host=' . $ac['hostname'] . ';port=' . ($ac['hostport'] ?? 3306)
            . ';dbname=' . $ac['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('Store DM config');
            }
        }
        foreach (['hostname' => 'POC_AI_HOST', 'hostport' => 'POC_AI_PORT',
            'database' => 'POC_AI_SCHEMA', 'username' => 'POC_AI_USER',
            'password' => 'POC_AI_PASSWORD'] as $key => $env) {
            if ((string)($ac[$key] ?? '') !== (string)getenv($env)) {
                throw new RuntimeException('AI DM config');
            }
        }
        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('not approved DM');
        }
        $storeDsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
        $aiDsn = 'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP';
    }
    $options = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5];
    $store = new PDO($storeDsn, (string)$cfg['username'], (string)$cfg['password'], $options);
    $ai = new PDO($aiDsn, (string)$ac['username'], (string)$ac['password'], $options);
    $storeIdentity = $lane === 'B'
        ? (string)$store->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn()
        : 'ZHCT_APP|5237|' . (string)$store->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
    if (!hash_equals($expectedStoreSha, hash('sha256', $storeIdentity))) {
        throw new RuntimeException('Store identity');
    }
    $aiIdentity = $lane === 'B'
        ? (string)$ai->query("SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn()
        : 'AIZHCT_APP|5238|' . (string)$ai->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
    if (($lane === 'B' && substr(hash('sha256', $aiIdentity), 0, 12) !== '210f925e093f')
        || ($lane === 'C' && $aiIdentity !== 'AIZHCT_APP|5238|AIZHCT_APP')) {
        throw new RuntimeException('AI identity');
    }
    $sp = $lane === 'B' ? 'ydy_' : 'YDY_';
    $ap = $lane === 'B' ? 'yoshop_' : 'YOSHOP_';
    $rows = static function (PDO $db, string $sql, array $args = [], int $cap = 1000): array {
        if (!preg_match('/^SELECT\b/i', $sql)) throw new RuntimeException('not SELECT');
        $stmt = $db->prepare($sql);
        $stmt->execute($args);
        $out = [];
        while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
            if (count($out) >= $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);
            $out[] = $row;
        }
        return $out;
    };
    $count = static function (PDO $db, string $sql, array $args = []) use ($rows): int {
        $result = $rows($db, $sql, $args, 1);
        return (int)(reset($result[0]) ?: 0);
    };
    $userTable = $sp . 'user';
    if ($mode === '--audit') {
        $marker = (string)getenv('CDX_CR3_MARKER');
        if (!preg_match('/^CDXCR3[A-F0-9]{12}$/', $marker)) exit(78);
        $n = $count($store, '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 = 'natural-source';
    $refundSql = 'SELECT * FROM ' . $ap . 'cash_refund_order'
        . ' WHERE STORE_ID=10001 AND IS_DELETE=0 ORDER BY APPLY_TIME DESC,ID DESC';
    $natural = $rows($ai, $refundSql, [], 200);
    if (count($natural) < 1 || count($natural) > 100) {
        throw new RuntimeException('natural refund source absent/unbounded');
    }
    $refund = null;
    foreach ($natural as $candidate) {
        $id = (int)$candidate['id'];
        $logsN = $count($ai, 'SELECT COUNT(*) FROM ' . $ap . 'cash_refund_order_log'
            . ' WHERE REFUND_ID=?', [$id]);
        if ((int)$candidate['status'] === 20 && $logsN >= 1 && $logsN <= 20) {
            $refund = $candidate;
            break;
        }
    }
    if (!$refund) throw new RuntimeException('successful refund with logs absent');
    $refundId = (int)$refund['id'];
    $staffUuid = (string)$refund['staff_uuid'];
    $snapshot = static function () use ($rows, $ai, $store, $refundSql, $ap,
        $sp, $refundId, $staffUuid): array {
        return [
            'refunds' => $rows($ai, $refundSql, [], 200),
            'logs' => $rows($ai, 'SELECT * FROM ' . $ap . 'cash_refund_order_log'
                . ' WHERE REFUND_ID=? ORDER BY ID ASC', [$refundId], 30),
            'staff' => $rows($store, 'SELECT UUID,NAME,MOBILE FROM ' . $sp . 'staff'
                . ' WHERE UUID=?', [$staffUuid], 2),
        ];
    };
    $before = $snapshot();
    $digest = static function (array $source): array {
        $out = [];
        foreach ($source as $key => $part) {
            $out[$key] = ['count' => count($part),
                'sha256' => hash('sha256', json_encode($part,
                    JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE))];
        }
        return $out;
    };
    $sourceBefore = $digest($before);
    $preflight = ['lane' => $lane, 'status' => 'preflight-pass',
        'source_counts' => array_map(static function ($v) { return count($v); }, $before),
        'selected_status' => (int)$refund['status'],
        'selected_id_sha12' => substr(hash('sha256', (string)$refundId), 0, 12),
        'old_actor_count' => $count($store, "SELECT COUNT(*) FROM " . $userTable
            . " WHERE ACCOUNT LIKE 'CDXCR3%'")];
    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 = 'CDXCR3' . strtoupper(bin2hex(random_bytes(6)));
    $actorId = 0;
    $failure = '';
    $result = ['lane' => $lane, 'marker' => $marker, 'routes' => [],
        'source_digest' => $sourceBefore, 'source_unchanged' => false, 'actor_zero' => false];
    $tree = static function ($value) use (&$tree) {
        if (is_int($value) || is_float($value)) return 'number';
        if (!is_array($value)) return gettype($value);
        if ($value === []) return [];
        $keys = array_keys($value);
        if ($keys === range(0, count($value) - 1)) return [$tree($value[0])];
        $out = [];
        foreach ($value as $key => $item) $out[(string)$key] = $tree($item);
        ksort($out);
        return $out;
    };
    $post = static function (string $path, array $form, string $token) use ($base, $runtimeReady): array {
        if (!in_array($path, ['/p/cashRefund/list', '/p/cashRefund/detail',
            '/p/cashRefund/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($store, 'SELECT COUNT(*) FROM ' . $userTable
            . ' WHERE UUID=? OR ACCOUNT=? OR NAME=?', [$marker, $marker, $marker]) !== 0) {
            throw new RuntimeException('actor collision');
        }
        $insert = $store->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($store, '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');
        $stage = 'list';
        $listResponse = $post('/p/cashRefund/list', ['page' => 1, 'page_size' => 5], $token);
        $page = $listResponse['data'];
        if (!is_array($page) || !is_array($page['data'] ?? null)
            || (int)($page['total'] ?? -1) !== count($before['refunds'])
            || count($page['data']) !== min(5, count($before['refunds']))) {
            throw new RuntimeException('list pagination/source count');
        }
        foreach ($page['data'] as $i => $row) {
            $source = $before['refunds'][$i];
            if ((int)($row['id'] ?? 0) !== (int)$source['id']
                || (int)($row['status'] ?? -1) !== (int)$source['status']
                || (string)($row['refund_amount'] ?? '')
                    !== number_format((float)$source['refund_amount'], 2, '.', '')) {
                throw new RuntimeException('list source ID/status/amount');
            }
        }
        $summary = $page['summary'] ?? null;
        $expectedAmount = 0.0;
        foreach ($before['refunds'] as $row) $expectedAmount += (float)$row['refund_amount'];
        if (!is_array($summary)
            || (int)($summary['total']['count'] ?? -1) !== count($before['refunds'])
            || (string)($summary['total']['amount'] ?? '')
                !== number_format($expectedAmount, 2, '.', '')) {
            throw new RuntimeException('list summary source');
        }
        $result['routes']['list'] = ['source_match' => true,
            'total' => (int)$page['total'], 'page_rows' => count($page['data']),
            'contract' => $tree($listResponse)];
        $stage = 'detail';
        $detailResponse = $post('/p/cashRefund/detail', ['refund_id' => $refundId], $token);
        $detail = $detailResponse['data'];
        if (!is_array($detail) || (int)($detail['id'] ?? 0) !== $refundId
            || (int)($detail['status'] ?? -1) !== (int)$refund['status']
            || (string)($detail['refund_amount'] ?? '')
                !== number_format((float)$refund['refund_amount'], 2, '.', '')
            || !is_array($detail['logs'] ?? null)
            || count($detail['logs']) !== count($before['logs'])) {
            throw new RuntimeException('detail source ID/status/amount/log count');
        }
        foreach ($detail['logs'] as $i => $log) {
            if ((int)($log['id'] ?? 0) !== (int)$before['logs'][$i]['id']
                || (int)($log['refund_id'] ?? 0) !== $refundId) {
                throw new RuntimeException('detail log source ID');
            }
        }
        $result['routes']['detail'] = ['source_match' => true,
            'logs' => count($detail['logs']), 'contract' => $tree($detailResponse)];
        $stage = 'logs';
        $logsResponse = $post('/p/cashRefund/logs', ['refund_id' => $refundId], $token);
        $logs = $logsResponse['data'];
        if (!is_array($logs) || count($logs) !== count($before['logs'])) {
            throw new RuntimeException('logs source count');
        }
        foreach ($logs as $i => $log) {
            if ((int)($log['id'] ?? 0) !== (int)$before['logs'][$i]['id']
                || (int)($log['refund_id'] ?? 0) !== $refundId) {
                throw new RuntimeException('logs source ID');
            }
        }
        $result['routes']['logs'] = ['source_match' => true,
            'rows' => count($logs), 'contract' => $tree($logsResponse)];
        $stage = 'source-after';
        $result['source_unchanged'] = $digest($snapshot()) === $sourceBefore;
        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($store, '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 = $store->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($store, 'SELECT COUNT(*) FROM ' . $userTable
            . ' WHERE UUID=? OR ACCOUNT=? OR NAME=?', [$marker, $marker, $marker]) === 0;
        if (!$result['source_unchanged']) {
            try { $result['source_unchanged'] = $digest($snapshot()) === $sourceBefore; }
            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);
}
