<?php
declare(strict_types=1);

// B/C only. --preflight is SELECT-only; --execute creates one disabled
// temporary operator and deletes exactly that ID after a known HTTP result.
$lane = (string)getenv('CDX_EQUIP_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'], true)
    || ($action === '--execute' && ((string)getenv('CDX_EQUIP_EXECUTE') !== 'ROOT_WINDOW_APPROVED'
        || (string)getenv('CDX_EQUIP_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')
        !== '15fa176b5b3344cbe61e759f7d8760fedb46b76f08a2d8a7a60b4a16e263199c') {
    throw new RuntimeException('Bound source changed');
}
$ready = static function () use ($root, $base, $pid): 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'), true);
    $port = (int)parse_url($base, PHP_URL_PORT);
    return strpos($cmd, '127.0.0.1:' . $port) !== false
        && strpos($cmd, $root . '/public') !== false && $env !== ''
        && trim($children) === ''
        && !preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=([2-9]|\d{2,})(?:\x00|$)/', $env)
        && is_array($boundary) && !empty($boundary['external_stubs'])
        && !empty($boundary['redis_isolated']) && empty($boundary['real_queue']);
};
if (!$ready()) throw new RuntimeException('Isolated worker boundary absent');
$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 {
    $check = [
        'type' => (string)($config['type'] ?? '') === 'app\\cores\\database\\Dm',
        'host' => (string)($config['hostname'] ?? '') === '127.0.0.1',
        'port' => (int)($config['hostport'] ?? 0) === 5237,
        'schema' => strtoupper((string)($config['database'] ?? '')) === 'ZHCT_APP',
        'user' => strtoupper((string)($config['username'] ?? '')) === 'ZHCT_APP',
    ];
    if (in_array(false, $check, true)) {
        throw new RuntimeException('Not isolated nonproduction DM: ' . json_encode($check));
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $hostMark = hash('sha256', gethostname() . '|'
        . trim((string)@file_get_contents('/etc/machine-id')));
    if ($hostMark !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('DM host mismatch');
    }
}
$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_';
$window = [$day . ' 00:00:00', $day . ' 23:59:59'];
$sourceSql = '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 ?';
$readSource = static function (PDO $pdo) use ($sourceSql, $window): array {
    $s = $pdo->prepare($sourceSql);
    $s->execute($window);
    return $s->fetchAll(PDO::FETCH_ASSOC);
};
$all = $readSource($db);
$byCode = [];
$bySelector = [];
foreach ($all as $row) {
    $byCode[(string)$row['equipment_code']][] = $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) {
    if ($action === '--preflight') {
        $coverage = $db->query('SELECT COUNT(*) N, MIN(m.BIND_TIME) LO, MAX(m.BIND_TIME) HI'
            . ' 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 e.DEL_FLAG=0 AND e.TYPE IN (1,9,20,30,40,60)')->fetch(PDO::FETCH_ASSOC);
        $restaurants = $db->query('SELECT m.RESTAURANT_ID rid,COUNT(*) n'
            . ' 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 e.DEL_FLAG=0 AND e.TYPE IN (1,9,20,30,40,60)'
            . ' GROUP BY m.RESTAURANT_ID')->fetchAll(PDO::FETCH_ASSOC);
        $r1 = $db->query('SELECT MIN(m.BIND_TIME) lo,MAX(m.BIND_TIME) hi'
            . ' 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)')->fetch(PDO::FETCH_ASSOC);
        echo json_encode(['lane' => $lane, 'mode' => 'SELECT-only',
            'joined_source_rows' => count($all),
            'equipment_code_groups' => count($byCode),
            'one_order_equipment_candidates' => 0,
            'all_time_joined_orders' => (int)$coverage['n'],
            'restaurants' => $restaurants,
            'restaurant_1_last_day' => substr((string)$r1['hi'], 0, 10),
            'first_day' => substr((string)$coverage['lo'], 0, 10),
            'last_day' => substr((string)$coverage['hi'], 0, 10)]) . PHP_EOL;
        exit(2);
    }
    throw new RuntimeException('No uniquely bound equipment order');
}
$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');
$uniqueMobile = $db->prepare('SELECT COUNT(*) FROM ' . $prefix
    . 'user WHERE MOBILE=? AND IS_DELETE=0 AND STAFF_UUID<>\'\'');
$uniqueMobile->execute([$source['mobile']]);
if ((int)$uniqueMobile->fetchColumn() !== 1) throw new RuntimeException('Mobile not unique');
$sourceHash = hash('sha256', serialize($all));
$markerCount = (int)$db->query('SELECT COUNT(*) FROM ' . $prefix . 'user'
    . " WHERE ACCOUNT LIKE 'CDXEQR4%'")->fetchColumn();
if ($action === '--preflight') {
    echo json_encode(['lane' => $lane, 'mode' => 'SELECT-only',
        'candidate_orders' => count($all), 'one_order_equipment_candidates' => count($candidates),
        'source_id_sha12' => substr(hash('sha256', (string)$source['id']), 0, 12),
        'equipment_code_sha12' => substr(hash('sha256', (string)$source['equipment_code']), 0, 12),
        'prior_actor_count' => $markerCount]) . PHP_EOL;
    exit(0);
}
if ($markerCount !== 0) throw new RuntimeException('Preexisting actor marker');
$marker = 'CDXEQR4' . strtoupper(bin2hex(random_bytes(8)));
$journalDir = sys_get_temp_dir() . '/zhct-equipment-report-' . $marker;
if (!mkdir($journalDir, 0700)) throw new RuntimeException('Recovery journal unavailable');
$journalPath = $journalDir . '/recovery.json';
$operatorId = 0;
$httpSent = false;
$httpKnown = false;
$failure = '';
$result = [];
$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/equipmentList', 'rb', false, $ctx);
    $body = is_resource($stream) ? stream_get_contents($stream, 256 * 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) > 256 * 1024) {
        throw new RuntimeException('HTTP outcome incomplete');
    }
    $httpKnown = true;
    $headers = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
    $response = json_decode($body, true);
    $payload = is_array($response) ? ($response['data'] ?? null) : null;
    $rows = is_array($payload) ? ($payload['data'] ?? null) : null;
    $row = is_array($rows) && count($rows) === 1 ? $rows[0] : null;
    $summary = is_array($payload) ? ($payload['summary'] ?? null) : null;
    $expected = ['consume_count' => 1, 'refund_count' => 0];
    foreach (['cash' => 'cash_amount', 'subsidy' => 'subsidy_amount',
        'gift' => 'gift_amount', 'refund_amount' => 'refund_amount'] as $raw => $field) {
        $expected[$field] = number_format((float)$source[$raw], 2, '.', '');
    }
    $expected['pay_amount'] = number_format((float)$source['pay_price']
        - (float)$source['refund_amount'], 2, '.', '');
    $sourceMatch = is_array($row) && is_array($summary)
        && (int)($row['id'] ?? 0) === (int)$source['equipment_id']
        && (string)($row['code'] ?? '') === (string)$source['equipment_code'];
    foreach ($expected as $field => $value) {
        foreach ([$row, $summary] as $item) {
            $actual = is_array($item) ? ($item[$field] ?? null) : null;
            $equal = in_array($field, ['consume_count', 'refund_count'], true)
                ? (is_numeric($actual) && (int)$actual === $value)
                : (is_numeric($actual) && number_format((float)$actual, 2, '.', '') === $value);
            $sourceMatch = $sourceMatch && $equal;
        }
    }
    $types = static function ($item): array {
        if (!is_array($item)) return [];
        $out = array_map('gettype', $item);
        ksort($out);
        return $out;
    };
    $result = ['lane' => $lane, 'http' => (int)($match[1] ?? 0),
        'code' => is_array($response) ? ($response['code'] ?? null) : null,
        'rows' => is_array($rows) ? count($rows) : null,
        'total' => is_array($payload) ? ($payload['total'] ?? null) : null,
        'own_source_match' => $sourceMatch, 'body_sha256' => hash('sha256', $body),
        'row_types' => $types($row), 'summary_types' => $types($summary),
        'data_types' => $types($payload)];
    if ($result['http'] !== 200 || $result['code'] !== 0
        || !$sourceMatch || (int)$result['total'] !== 1) {
        throw new RuntimeException('Positive equipment report mismatch');
    }
    if (hash('sha256', serialize($readSource($connect()))) !== $sourceHash) {
        throw new RuntimeException('Business source changed');
    }
    $result['source_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');
            $byMarker = $fresh->prepare('SELECT COUNT(*) FROM ' . $prefix . 'user'
                . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
            $byMarker->execute([$marker, $marker, 'CODEX']);
            if ((int)$byMarker->fetchColumn() !== 0) {
                throw new RuntimeException('Actor marker still present');
            }
            $save(['phase' => 'cleaned']);
            $result['actor_cleaned'] = true;
            unlink($journalPath);
            rmdir($journalDir);
        } 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);
