<?php
declare(strict_types=1);

// Isolated B/C legacy direct XLS export. --preflight has no writes.
$lane = (string)getenv('CDX_LEGACY_LANE');
$action = (string)($argv[1] ?? '');
$targets = [
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001, 'mysql', '2025-09-04',
        '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175, 'dm', '2025-02-17',
        'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194'],
];
if (!isset($targets[$lane]) || !in_array($action, ['--preflight', '--execute', '--recover'], true)
    || ($action === '--execute' && ((string)getenv('CDX_LEGACY_EXECUTE') !== 'ROOT_WINDOW_APPROVED'
        || (string)getenv('CDX_LEGACY_SHA') !== hash_file('sha256', __FILE__)))
    || ($action === '--recover' && ((string)getenv('CDX_LEGACY_RECOVER') === ''
        || (string)getenv('CDX_LEGACY_SHA') !== hash_file('sha256', __FILE__)))) {
    fwrite(STDERR, "execution disabled\n");
    exit(78);
}
[$root, $base, $pid, $dialect, $day, $expectedIdentity] = $targets[$lane];
if (is_link($root) || !is_dir($root)
    || hash_file('sha256', $root . '/application/p/controller/MealOrder.php')
        !== '0e818de300828a2c3546d6ccb83fe22ef0dd87bf5cc9c885ef646e6342f2ad5b'
    || hash_file('sha256', $root . '/application/p/logic/MealOrder.php')
        !== '1d5766cb99622ed334e1c10c3f57e1bebf080114aa39373c68300e379e7a39e1') {
    throw new RuntimeException('Bound source changed');
}
$workerEnvironment = (string)@file_get_contents('/proc/' . $pid . '/environ');
$ready = static function () use ($root, $base, $pid, $workerEnvironment): bool {
    $cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
    $children = (string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children');
    $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    $port = (int)parse_url($base, PHP_URL_PORT);
    return strpos($cmd, '127.0.0.1:' . $port) !== false
        && strpos($cmd, $root . '/public') !== false
        && $workerEnvironment !== '' && trim($children) === ''
        && !preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=([2-9]|\d{2,})(?:\x00|$)/', $workerEnvironment)
        && is_array($boundary) && !empty($boundary['external_stubs'])
        && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']);
};
if (!$ready()) throw new RuntimeException('Isolated worker boundary absent');
if ($dialect === 'dm') {
    foreach (explode("\0", $workerEnvironment) as $pair) {
        if (preg_match('/^(POC_[A-Z0-9_]+)=(.*)$/sD', $pair, $m)) putenv($m[1] . '=' . $m[2]);
    }
}
$config = require $root . '/application/database.php';
if (!is_array($config) || (string)($config['prefix'] ?? '') !== 'ydy_') {
    throw new RuntimeException('Database config shape mismatch');
}
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    if ((string)($config['type'] ?? '') !== 'mysql') throw new RuntimeException('Not MySQL');
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($local[$key] ?? null) !== ($config[$key] ?? null)) throw new RuntimeException('Not code-local dev MySQL');
    }
    $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . (int)($config['hostport'] ?? 3306)
        . ';dbname=' . $config['database'] . ';charset=' . (string)($config['charset'] ?? 'utf8');
} else {
    if ((string)($config['type'] ?? '') !== 'app\\cores\\database\\Dm'
        || (string)($config['hostname'] ?? '') !== '127.0.0.1'
        || (int)($config['hostport'] ?? 0) !== 5237
        || strtoupper((string)($config['database'] ?? '')) !== 'ZHCT_APP'
        || strtoupper((string)($config['username'] ?? '')) !== 'ZHCT_APP') {
        throw new RuntimeException('Not isolated nonproduction DM');
    }
    $hostMark = hash('sha256', gethostname() . '|'
        . trim((string)@file_get_contents('/etc/machine-id')));
    if ($hostMark !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('DM host mismatch');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
$connect = static function () use ($dsn, $config): PDO {
    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]);
};
$db = $connect();
$identity = $dialect === 'mysql'
    ? (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('Database identity mismatch');
}
$prefix = $dialect === 'dm' ? 'YDY_' : 'ydy_';
if ($action === '--recover') {
    $recoverMarker = (string)getenv('CDX_LEGACY_RECOVER');
    if (!preg_match('/^CDXLGE5[A-F0-9]{16}$/D', $recoverMarker)) {
        throw new RuntimeException('Recovery marker invalid');
    }
    $recoverDir = sys_get_temp_dir() . '/cdx-legacy-equipment-' . $recoverMarker;
    $recoverJournal = $recoverDir . '/recovery.json';
    if (is_link($recoverDir) || is_link($recoverJournal) || !is_dir($recoverDir)
        || !is_file($recoverJournal) || (fileperms($recoverDir) & 0777) !== 0700) {
        throw new RuntimeException('Recovery directory invalid');
    }
    $journalData = json_decode((string)file_get_contents($recoverJournal), true);
    if (!is_array($journalData) || ($journalData['lane'] ?? null) !== $lane
        || ($journalData['marker'] ?? null) !== $recoverMarker
        || ($journalData['database_fingerprint'] ?? null) !== $expectedIdentity
        || (int)($journalData['operator_id'] ?? 0) <= 0) {
        throw new RuntimeException('Recovery journal mismatch');
    }
    $operatorId = (int)$journalData['operator_id'];
    $sqlIdentity = ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?';
    $args = [$operatorId, $recoverMarker, $recoverMarker, 'CODEX'];
    $check = $db->prepare('SELECT COUNT(*) FROM ' . $prefix . 'user' . $sqlIdentity);
    $check->execute($args);
    if ((int)$check->fetchColumn() !== 1) throw new RuntimeException('Actor not uniquely owned');
    $delete = $db->prepare('DELETE FROM ' . $prefix . 'user' . $sqlIdentity);
    $delete->execute($args);
    if ($delete->rowCount() !== 1) throw new RuntimeException('Precise recovery delete failed');
    $fresh = $connect();
    $check = $fresh->prepare('SELECT COUNT(*) FROM ' . $prefix . 'user' . $sqlIdentity);
    $check->execute($args);
    if ((int)$check->fetchColumn() !== 0) throw new RuntimeException('Actor remains after recovery');
    $markerCheck = $fresh->prepare('SELECT COUNT(*) FROM ' . $prefix . 'user'
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $markerCheck->execute([$recoverMarker, $recoverMarker, 'CODEX']);
    if ((int)$markerCheck->fetchColumn() !== 0) throw new RuntimeException('Marker remains after recovery');
    $recoverFile = $recoverDir . '/response.xls';
    if (is_file($recoverFile) && !is_link($recoverFile)) unlink($recoverFile);
    unlink($recoverJournal);
    rmdir($recoverDir);
    echo json_encode(['lane' => $lane, 'actor_precisely_cleaned' => true,
        'second_connection_zero' => true, 'private_files_removed' => true]) . PHP_EOL;
    exit(0);
}
$window = [$day . ' 00:00:00', $day . ' 23:59:59'];
$sql = 'SELECT m.ID,m.EQUIPMENT_CODE,m.USER_ID,m.CASH,m.SUBSIDY,m.GIFT,'
    . 'm.REFUND_AMOUNT,m.PAY_PRICE,u.MOBILE,e.ID AS EQUIPMENT_ID'
    . ' FROM ' . $prefix . 'meal_order m'
    . ' JOIN ' . $prefix . 'user u ON u.ID=m.USER_ID'
    . ' JOIN ' . $prefix . 'equipment e ON e.CODE=m.EQUIPMENT_CODE'
    . ' WHERE u.IS_DELETE=0 AND u.STAFF_UUID<>\'\' AND m.IS_DELETE=0'
    . ' AND m.ORDER_STATUS=30 AND m.RESTAURANT_ID=1'
    . ' AND e.DEL_FLAG=0 AND e.TYPE IN (1,9,20,30,40,60)'
    . ' AND m.BIND_TIME BETWEEN ? AND ?';
$read = static function (PDO $pdo) use ($sql, $window): array {
    $s = $pdo->prepare($sql);
    $s->execute($window);
    return $s->fetchAll(PDO::FETCH_ASSOC);
};
$all = $read($db);
$bySelector = [];
foreach ($all as $row) $bySelector[(string)$row['equipment_code'] . '|' . (string)$row['mobile']][] = $row;
$candidates = [];
foreach ($bySelector as $rows) if (count($rows) === 1 && (string)$rows[0]['mobile'] !== '') $candidates[] = $rows[0];
usort($candidates, static function (array $a, array $b): int { return (int)$a['id'] <=> (int)$b['id']; });
if (!$candidates) throw new RuntimeException('No unique natural source');
$source = $candidates[0];
$refund = $db->prepare('SELECT COUNT(*) FROM ' . $prefix
    . 'meal_order_refund WHERE MEAL_ORDER_ID=? AND IS_DELETE=0');
$refund->execute([$source['id']]);
if ((int)$refund->fetchColumn() !== 0) throw new RuntimeException('Refund branch outside scope');
$mobile = $db->prepare('SELECT COUNT(*) FROM ' . $prefix
    . 'user WHERE MOBILE=? AND IS_DELETE=0 AND STAFF_UUID<>\'\'');
$mobile->execute([$source['mobile']]);
if ((int)$mobile->fetchColumn() !== 1) throw new RuntimeException('Mobile not unique');
$queueState = static function (PDO $pdo) use ($prefix): array {
    return $pdo->query('SELECT COUNT(*) n, COALESCE(MAX(ID),0) hi FROM '
        . $prefix . 'export_log')->fetch(PDO::FETCH_ASSOC);
};
$sourceHash = hash('sha256', serialize($all));
$queueBefore = $queueState($db);
$markerCount = (int)$db->query('SELECT COUNT(*) FROM ' . $prefix . 'user'
    . " WHERE ACCOUNT LIKE 'CDXLGE5%'")->fetchColumn();
if ($action === '--preflight') {
    $zeroFlags = [];
    $sourceTypes = [];
    foreach (['cash', 'subsidy', 'gift', 'refund_amount', 'pay_price'] as $field) {
        $zeroFlags[$field] = is_numeric($source[$field])
            && number_format((float)$source[$field], 2, '.', '') === '0.00';
        $sourceTypes[$field] = gettype($source[$field]);
    }
    echo json_encode(['lane' => $lane, 'mode' => 'SELECT-only',
        'joined_source_rows' => count($all), 'unique_source_candidates' => count($candidates),
        'selected_id_sha12' => substr(hash('sha256', (string)$source['id']), 0, 12),
        'selected_zero_flags' => $zeroFlags, 'selected_php_types' => $sourceTypes,
        'selected_refund_count_zero' => true,
        'prior_actor_count' => $markerCount,
        'queue_snapshot' => ['rows' => (int)$queueBefore['n'], 'max_id' => (int)$queueBefore['hi']]]) . PHP_EOL;
    exit(0);
}
if ($markerCount !== 0) throw new RuntimeException('Preexisting actor marker');
$marker = 'CDXLGE5' . strtoupper(bin2hex(random_bytes(8)));
$dir = sys_get_temp_dir() . '/cdx-legacy-equipment-' . $marker;
umask(0077);
if (!mkdir($dir, 0700)) throw new RuntimeException('Private directory unavailable');
$journalPath = $dir . '/recovery.json';
$file = $dir . '/response.xls';
$operatorId = 0;
$httpSent = false;
$httpKnown = false;
$result = [];
$failure = '';
$journal = ['lane' => $lane, 'marker' => $marker, 'operator_id' => 0,
    'database_fingerprint' => $expectedIdentity, 'phase' => 'before_insert'];
$save = static function (array $delta) use (&$journal, $journalPath): void {
    $journal = array_merge($journal, $delta);
    if (file_put_contents($journalPath, json_encode($journal)) === false) {
        throw new RuntimeException('Recovery journal write failed');
    }
    chmod($journalPath, 0600);
};
$save([]);
try {
    $insert = $db->prepare('INSERT INTO ' . $prefix . 'user'
        . ' (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]);
    $idQuery = $db->prepare('SELECT ID FROM ' . $prefix . 'user'
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $idQuery->execute([$marker, $marker, 'CODEX']);
    $operatorId = (int)$idQuery->fetchColumn();
    if ($operatorId <= 0) throw new RuntimeException('Actor insert unreadable');
    $save(['operator_id' => $operatorId, 'phase' => 'actor_inserted']);
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$operatorId, $marker, $marker, '0', '', '', $marker]));
    if (!is_string($token) || $token === '' || !$ready()) {
        throw new RuntimeException('Token or worker unavailable');
    }
    $form = ['start_date' => $day, 'end_date' => $day,
        'mobile' => (string)$source['mobile'],
        'equipment_codes' => (string)$source['equipment_code'],
        'restaurant_shop_id' => '1'];
    $ctx = stream_context_create(['http' => ['method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\n"
            . 'token: ' . $token . "\r\n",
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 25, 'ignore_errors' => true]]);
    $httpSent = true;
    $stream = @fopen($base . '/p/mealOrder/equipmentListExport', 'rb', false, $ctx);
    $body = is_resource($stream) ? stream_get_contents($stream, 64 * 1024 + 1) : false;
    $meta = is_resource($stream) ? stream_get_meta_data($stream) : [];
    if (is_resource($stream)) fclose($stream);
    if (!is_string($body) || !empty($meta['timed_out']) || strlen($body) > 64 * 1024) {
        throw new RuntimeException('HTTP outcome incomplete');
    }
    $httpKnown = true;
    $headers = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
    $contentDisposition = implode(' ', preg_grep('/^Content-Disposition:/i', $headers));
    if ((int)($match[1] ?? 0) !== 200 || strpos($contentDisposition, 'attachment') === false
        || substr($body, 0, 8) !== hex2bin('D0CF11E0A1B11AE1')) {
        throw new RuntimeException('HTTP XLS boundary mismatch');
    }
    if (file_put_contents($file, $body) !== strlen($body)) {
        throw new RuntimeException('Private XLS write incomplete');
    }
    chmod($file, 0600);
    require_once $root . '/vendor/autoload.php';
    $book = \PHPExcel_IOFactory::load($file);
    $sheet = $book->getActiveSheet();
    $result['observed_dimensions'] = [$sheet->getHighestRow(), $sheet->getHighestColumn()];
    if ((int)$sheet->getHighestRow() !== 3 || $sheet->getHighestColumn() !== 'J') {
        throw new RuntimeException('XLS dimensions mismatch');
    }
    $cells = [];
    foreach (['D2', 'E2', 'F2', 'G2', 'H2', 'I2', 'J2',
              'D3', 'E3', 'F3', 'G3', 'H3', 'I3', 'J3'] as $addr) {
        $cell = $sheet->getCell($addr);
        $cells[$addr] = ['value' => $cell->getCalculatedValue(), 'type' => $cell->getDataType()];
    }
    $expected = ['D' => 1, 'E' => $source['cash'], 'F' => $source['subsidy'],
        'G' => $source['gift'], 'H' => 0, 'I' => $source['refund_amount'],
        'J' => (float)$source['pay_price'] - (float)$source['refund_amount']];
    $mismatches = [];
    if ((string)$sheet->getCell('C2')->getValue() !== (string)$source['equipment_code']) {
        $mismatches[] = 'C2:equipment_code';
    }
    if ((string)$sheet->getCell('A3')->getValue() !== '合计:') {
        $mismatches[] = 'A3:summary_label';
    }
    $legacyBlankZeroCells = [];
    foreach (['2', '3'] as $row) foreach ($expected as $col => $value) {
        $actual = $cells[$col . $row]['value'];
        if ($col === 'H' && (int)$value === 0 && ($actual === null || $actual === '')) {
            $legacyBlankZeroCells[] = $col . $row;
            continue;
        }
        if (!is_numeric($actual)
            || number_format((float)$actual, 2, '.', '') !== number_format((float)$value, 2, '.', '')) {
            $mismatches[] = $col . $row;
        }
    }
    $result['legacy_blank_zero_cells'] = $legacyBlankZeroCells;
    if ($mismatches) {
        $result['mismatch_cells'] = $mismatches;
        throw new RuntimeException('XLS original-row mismatch');
    }
    $types = [];
    foreach ($cells as $addr => $cell) $types[$addr] = $cell['type'];
    $result = ['lane' => $lane, 'http' => (int)$match[1], 'mime' => 'Excel5',
        'bytes' => strlen($body), 'rows' => 3, 'columns' => 10,
        'source_values_match_except_legacy_zero_blank' => true,
        'legacy_blank_zero_cells' => $legacyBlankZeroCells, 'cell_types' => $types,
        'xls_sha256' => hash('sha256', $body)];
    $fresh = $connect();
    if (hash('sha256', serialize($read($fresh))) !== $sourceHash
        || $queueState($fresh) != $queueBefore) {
        throw new RuntimeException('Business source or queue changed');
    }
    $result['source_unchanged'] = true;
    $result['queue_unchanged'] = true;
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    if (!$httpSent || $httpKnown) {
        try {
            $fresh = $connect();
            $verify = $fresh->prepare('SELECT COUNT(*) FROM ' . $prefix . 'user'
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
            $verify->execute([$operatorId, $marker, $marker, 'CODEX']);
            if ((int)$verify->fetchColumn() === 1) {
                $delete = $fresh->prepare('DELETE FROM ' . $prefix . 'user'
                    . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
                $delete->execute([$operatorId, $marker, $marker, 'CODEX']);
                if ($delete->rowCount() !== 1) throw new RuntimeException('Precise cleanup failed');
            }
            $verify->execute([$operatorId, $marker, $marker, 'CODEX']);
            if ((int)$verify->fetchColumn() !== 0) throw new RuntimeException('Actor still present');
            if (is_file($file)) unlink($file);
            $save(['phase' => 'cleaned']);
            $result['actor_cleaned'] = true;
            unlink($journalPath);
            rmdir($dir);
        } catch (Throwable $e) {
            $failure .= '|cleanup:' . get_class($e) . ':' . hash('sha256', $e->getMessage());
        }
    } else {
        $save(['phase' => 'HTTP_outcome_unknown']);
        $failure .= '|HTTP_outcome_unknown_actor_retained';
    }
}
echo json_encode(['result' => $result, 'failure' => $failure], JSON_UNESCAPED_SLASHES) . PHP_EOL;
if ($failure !== '') exit(1);
