<?php

declare(strict_types=1);

// Bounded, SELECT-only A/B/C HTTP harness. Never creates an operator or a DB row.
$lane = (string)getenv('CDX_SUBSIDY_LANE');
$mode = (string)($argv[1] ?? '');
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 18097, 21023, 'mysql',
        'fb3e2fb496b7b56a16969f2d01491d006aeb4cf2515fb48e3b62001468e74a1b',
        'CDXPAYD914599B', 'ddcbb37c0a978763f6b688dd31587bef9e44b1750de2b870d23951d17bb8f71d',
        '981c70270494a16318daee452dd08eb520c0d5e79e30ff261e49ff250796f50d'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 18081, 10001, 'mysql',
        '468f4d22f73e0bf3e6e5fac3551c690f0aafb60678b4ec38ca88ce60afad52d0',
        'CDXPAY7E0B0221', 'b60e795d5d7f3bbd0c500ce9347f90922126a56b84e8d0168b9f4d869c0a9e81',
        '1f37ae73638eab9701ff479ac4e2875d95551ea47fe49f6240c60248fb9957e1'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 18080, 112175, 'dm',
        '468f4d22f73e0bf3e6e5fac3551c690f0aafb60678b4ec38ca88ce60afad52d0',
        'CDXPAY03721E84', '81b808a6de6a841f410ba1313dd48ea2097e2eff262bbc533a6480a52ee33dbb',
        '1f37ae73638eab9701ff479ac4e2875d95551ea47fe49f6240c60248fb9957e1'],
];
if (!isset($targets[$lane]) || !in_array($mode, ['--preflight', '--execute-readonly'], true)
    || ($mode === '--execute-readonly'
        && ((string)getenv('CDX_SUBSIDY_EXECUTE') !== 'READONLY_HTTP_V1'
            || (string)getenv('CDX_SUBSIDY_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "Subsidy HTTP execution disabled\n");
    exit(78);
}
[$root, $port, $pid, $dialect, $excelSha, $marker, $approvedSourceSha,
    $subsidyLogicSha] = $targets[$lane];
$base = 'http://127.0.0.1:' . $port;
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/SubsidyReport.php')
        !== $subsidyLogicSha
    || hash_file('sha256', $root . '/application/model/Excel.php') !== $excelSha) {
    throw new RuntimeException('Approved report source/runtime mismatch');
}
$runtimeReady = static function () use ($root, $base, $pid, $port): 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);
    $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;
    if ($inode !== '') foreach (@scandir('/proc/' . $pid . '/fd') ?: [] as $fd) {
        if ($fd === '.' || $fd === '..') continue;
        if (@readlink('/proc/' . $pid . '/fd/' . $fd) === 'socket:[' . $inode . ']') {
            $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('Isolated HTTP boundary absent');
$getConfig = static function (string $path): array {
    $v = require $path;
    if (!is_array($v)) throw new RuntimeException('Invalid DB config');
    return $v;
};
$runtime = $getConfig($root . '/application/database.php');
if ($dialect === 'mysql') {
    $local = $getConfig('/workspace/zhct/zhctproject/store/application/config/local/database.php');
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('HTTP store runtime differs from code local');
        }
    }
    $aiConfig = array_merge($runtime, (array)($runtime['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('HTTP AI runtime differs from code local');
        }
    }
    $dsn = static function (array $c): string {
        return 'mysql:host=' . $c['hostname'] . ';port=' . (int)($c['hostport'] ?? 3306)
            . ';dbname=' . $c['database'] . ';charset=' . ($c['charset'] ?? 'utf8');
    };
    $storeDsn = $dsn($runtime);
    $aiDsn = $dsn($aiConfig);
} else {
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $key => $env) {
        if ((string)($runtime[$key] ?? '') !== (string)getenv($env)) {
            throw new RuntimeException('HTTP store runtime differs from isolated worker');
        }
    }
    $aiConfig = array_merge($runtime, (array)($runtime['ai_zhct'] ?? []));
    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)($aiConfig[$key] ?? '') !== (string)getenv($env)) {
            throw new RuntimeException('HTTP AI runtime differs from isolated worker');
        }
    }
    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('Unexpected DM target pair');
    }
    $machineId = trim((string)@file_get_contents('/etc/machine-id'));
    if ($machineId === '' || gethostname() === false
        || hash('sha256', gethostname() . '|' . $machineId)
            !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('Unexpected DM test host');
    }
    $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 $config): PDO {
    try {
        return new PDO($dsn, (string)$config['username'], (string)$config['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, $runtime);
$ai = $connect($aiDsn, $aiConfig);
$mysqlIdentitySql = "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())";
$storeIdentity = $dialect === 'mysql'
    ? (string)$store->query($mysqlIdentitySql)->fetchColumn()
    : 'ZHCT_APP|5237|' . (string)$store->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
$aiIdentity = $dialect === 'mysql'
    ? (string)$ai->query($mysqlIdentitySql)->fetchColumn()
    : 'AIZHCT_APP|5238|' . (string)$ai->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
$approved = $dialect === 'mysql'
    ? ['003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401',
        '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b']
    : ['bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194',
        '628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e'];
if (!hash_equals($approved[0], hash('sha256', $storeIdentity))
    || !hash_equals($approved[1], hash('sha256', $aiIdentity))) {
    throw new RuntimeException('Database identity differs from approved test pair');
}
$s = $dialect === 'dm' ? 'YDY_' : 'ydy_';
$a = $dialect === 'dm' ? 'YOSHOP_' : 'yoshop_';
$userTable = $s . ($dialect === 'dm' ? 'USER' : 'user');
$staffTable = $s . ($dialect === 'dm' ? 'STAFF' : 'staff');
$subsidyTable = $a . ($dialect === 'dm' ? 'USER_SUBSIDY' : 'user_subsidy');
$actorSql = 'SELECT ID,UUID,ACCOUNT,NAME,TYPE FROM ' . $userTable
    . " WHERE ACCOUNT='admin' AND TYPE=0 AND IS_DELETE=0";
$actorRows = $store->query($actorSql)->fetchAll(PDO::FETCH_ASSOC);
if (count($actorRows) !== 1 || (int)$actorRows[0]['id'] <= 0
    || (string)$actorRows[0]['uuid'] === '') {
    throw new RuntimeException('Existing read-only test actor unavailable');
}
$staffSql = 'SELECT u.ACCOUNT,s.NAME,s.UUID,s.MOBILE,s.AI_USER_ID,s.IS_SHOW FROM '
    . $userTable . ' u JOIN ' . $staffTable . ' s ON s.UUID=u.STAFF_UUID'
    . ' WHERE u.ACCOUNT=? AND s.NAME=? AND u.IS_DELETE=0';
$staffStmt = $store->prepare($staffSql);
$staffStmt->execute([$marker, $marker]);
$staffRows = $staffStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($staffRows) !== 1 || (int)$staffRows[0]['is_show'] !== 1
    || (int)$staffRows[0]['ai_user_id'] <= 0) {
    throw new RuntimeException('Synthetic Store staff changed');
}
$sourceSql = 'SELECT ID,USER_ID,MONEY,USED_MONEY,PUSH_DATE,IS_DELETE FROM '
    . $subsidyTable . ' WHERE USER_ID=? AND IS_DELETE=0 ORDER BY ID DESC'
    . ($dialect === 'dm' ? ' FETCH FIRST 2 ROWS ONLY' : ' LIMIT 2');
$sourceStmt = $ai->prepare($sourceSql);
$readSource = static function () use ($sourceStmt, $staffRows, $approvedSourceSha): array {
    $sourceStmt->execute([(int)$staffRows[0]['ai_user_id']]);
    $rows = $sourceStmt->fetchAll(PDO::FETCH_ASSOC);
    $stringify = static function ($v): string {
        return is_resource($v) ? (string)stream_get_contents($v) : (string)$v;
    };
    if (count($rows) !== 1 || hash('sha256', implode('|', array_map($stringify, $rows[0])))
        !== $approvedSourceSha) {
        throw new RuntimeException('Synthetic AI subsidy source changed');
    }
    return $rows[0];
};
$source = $readSource();
$date = substr((string)$source['push_date'], 0, 10);
if ($date !== '2026-09-30') throw new RuntimeException('Synthetic date drift');
$money = static function ($v): string {
    return number_format((float)$v, 2, '.', '');
};
$expectedAmount = $money((float)$source['money'] + (float)$source['used_money']);
if ($expectedAmount !== '20.00') throw new RuntimeException('Unexpected synthetic amount');
if ($mode === '--preflight') {
    echo json_encode(['lane' => $lane, 'mode' => 'select-only-preflight',
        'http_boundary' => true, 'store_db_approved' => true, 'ai_db_approved' => true,
        'existing_admin_present' => true, 'synthetic_source_unchanged' => true,
        'source_day' => $date, 'source_amount' => $expectedAmount],
        JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

require_once $root . '/application/base/Security.php';
$actor = $actorRows[0];
$runMarker = 'CDXSUBGREEN' . strtoupper(bin2hex(random_bytes(6)));
$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 token unavailable');
$call = static function (string $method, string $path, array $form, int $maxBytes)
    use ($base, $token, $runtimeReady, $runMarker): array {
    if (!$runtimeReady()) throw new RuntimeException('HTTP boundary drift');
    $url = $base . $path;
    if ($method === 'GET') $url .= '?' . http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    $options = ['method' => $method, 'timeout' => 20, 'ignore_errors' => true,
        'header' => 'token: ' . $token . "\r\nX-Test-Run: " . $runMarker . "\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('HTTP transport failed');
    $headers = stream_get_meta_data($stream)['wrapper_data'] ?? [];
    $body = stream_get_contents($stream, $maxBytes + 1);
    fclose($stream);
    if (!is_string($body) || strlen($body) > $maxBytes) {
        throw new RuntimeException('HTTP response exceeded safe bound');
    }
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
    return [(int)($match[1] ?? 0), $headers, $body];
};
$form = ['start_date' => $date, 'end_date' => $date,
    'nickname' => $marker, 'page' => '1'];
[$listHttp, $listHeaders, $listBody] = $call('POST',
    '/p/report/subsidyStatisticList', $form, 1024 * 1024);
$list = json_decode($listBody, true);
if ($listHttp !== 200 || !is_array($list) || ($list['code'] ?? null) !== 0
    || !is_array($list['data'] ?? null)) {
    throw new RuntimeException('Subsidy list HTTP/envelope mismatch');
}
$data = $list['data'];
$rows = $data['data'] ?? null;
if (!is_array($rows) || count($rows) !== 1 || (int)($data['total'] ?? 0) !== 1
    || (int)($data['per_page'] ?? 0) !== 15
    || (int)($rows[0]['user_id'] ?? 0) !== (int)$staffRows[0]['ai_user_id']
    || (string)($rows[0]['user']['name'] ?? '') !== $marker
    || (string)($rows[0]['mobile'] ?? '') !== (string)$staffRows[0]['mobile']
    || (string)($rows[0]['recharge_date'] ?? '') !== $date . '至' . $date
    || $money($rows[0]['sum_money'] ?? 0) !== $money($source['money'])
    || $money($rows[0]['sum_used_money'] ?? 0) !== $money($source['used_money'])
    || $money($rows[0]['subsidy_amount'] ?? 0) !== $expectedAmount
    || $money($data['total_price'] ?? 0) !== $expectedAmount) {
    throw new RuntimeException('Subsidy list source/response mismatch');
}
foreach (['sum_money', 'sum_used_money'] as $field) {
    if (!isset($rows[0][$field]) || !is_string($rows[0][$field])
        || !preg_match('/^-?\d+\.\d{2}$/D', $rows[0][$field])) {
        throw new RuntimeException('Subsidy fixed-scale JSON string mismatch');
    }
}
$types = static function (array $row): array {
    $out = array_map('gettype', $row);
    ksort($out);
    return $out;
};
$listForHash = $list;
unset($listForHash['trace_id']);
$listResult = ['http' => $listHttp,
    'response_sha' => hash('sha256', json_encode($listForHash, JSON_UNESCAPED_UNICODE)),
    'top_types' => $types($data), 'row_types' => $types($rows[0]),
    'source_amount_match' => true];

$exportForm = ['start_date' => $date, 'end_date' => $date, 'nickname' => $marker];
[$exportHttp, $exportHeaders, $xls] = $call('GET',
    '/p/report/subsidyStatisticExport', $exportForm, 2 * 1024 * 1024);
$contentType = '';
foreach ($exportHeaders as $header) {
    if (stripos($header, 'Content-Type:') === 0) $contentType = trim(substr($header, 13));
}
if ($exportHttp !== 200 || stripos($contentType, 'application/vnd.ms-excel') === false
    || substr($xls, 0, 8) !== hex2bin('d0cf11e0a1b11ae1')) {
    throw new RuntimeException('Subsidy export HTTP/XLS contract mismatch');
}
$temp = tempnam(sys_get_temp_dir(), 'cdxsubxls-');
if (!is_string($temp)) throw new RuntimeException('Private XLS temp unavailable');
$excelCells = [];
try {
    chmod($temp, 0600);
    if (file_put_contents($temp, $xls) !== strlen($xls)) {
        throw new RuntimeException('Private XLS write failed');
    }
    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() !== 'F') {
        throw new RuntimeException('Subsidy XLS dimensions mismatch: rows='
            . $sheet->getHighestRow() . ', column=' . $sheet->getHighestColumn());
    }
    for ($r = 1; $r <= 3; $r++) {
        $line = [];
        for ($c = 0; $c < 6; $c++) {
            $line[] = $sheet->getCellByColumnAndRow($c, $r)->getValue();
        }
        $excelCells[] = $line;
    }
    $book->disconnectWorksheets();
} finally {
    if (is_file($temp)) unlink($temp);
}
if ($excelCells[0] !== ['序号', '补贴日期', '部门名称', '姓名', '手机号', '补贴金额']
    || (int)$excelCells[1][0] !== 1
    || (string)$excelCells[1][1] !== $date . '至' . $date
    || (string)$excelCells[1][2] !== (string)$rows[0]['department_name']
    || (string)$excelCells[1][3] !== $marker
    || (string)$excelCells[1][4] !== (string)$staffRows[0]['mobile']
    || $money($excelCells[1][5]) !== $expectedAmount
    || (string)$excelCells[2][0] !== '合计'
    || array_filter(array_slice($excelCells[2], 1, 4), static function ($v): bool {
        return $v !== '' && $v !== null;
    })
    || $money($excelCells[2][5]) !== $expectedAmount) {
    throw new RuntimeException('Subsidy XLS cell/source mismatch');
}
$sourceAfter = $readSource();
if ($sourceAfter != $source) throw new RuntimeException('AI source changed after HTTP');
$staffStmt->execute([$marker, $marker]);
if ($staffStmt->fetchAll(PDO::FETCH_ASSOC) != $staffRows) {
    throw new RuntimeException('Store staff changed after HTTP');
}
echo json_encode(['lane' => $lane, 'run_marker' => $runMarker,
    'mode' => 'readonly-http',
    'list' => $listResult, 'export' => ['http' => $exportHttp,
        'content_type' => strtok($contentType, ';'), 'format' => 'Excel5',
        'rows' => 3, 'columns' => 6, 'data_amount_match' => true,
        'summary_amount_match' => true, 'xls_sha' => hash('sha256', $xls)],
    'store_staff_unchanged' => true, 'ai_subsidy_unchanged' => true,
    'no_business_dml' => true], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
