<?php
declare(strict_types=1);

// B/C natural-data, read-only business HTTP batch. Only one disabled actor is owned.
$lane = (string)getenv('CDX_PAL3_LANE');
$mode = (string)($argv[1] ?? '');
$self = (string)hash_file('sha256', __FILE__);
if (!in_array($lane, ['B', 'C'], true)
    || count($argv) !== 2 || !in_array($mode, ['--preflight', '--execute', '--audit'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_PAL3_APPROVE') !== 'ROOT_NATURAL_READ3_V1'
            || !hash_equals($self, (string)getenv('CDX_PAL3_SHA'))))) {
    echo json_encode(['status' => 'default_deny', 'connected' => false,
        'http_sent' => false, 'dml' => false]), PHP_EOL;
    exit(78);
}
$stage = 'runtime';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status' => 'redacted_stop', 'stage' => $stage,
        'class' => get_class($error), 'code' => (string)$error->getCode(),
        'recovery' => 'inspect_private_journal']), PHP_EOL;
    exit(2);
});
[$root, $pid, $port, $dialect] = [
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 10001, 18081, 'mysql'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 112175, 18080, 'dm'],
][$lane];
$hashes = [
    '/application/p/controller/MetabolismPal.php' => '79612aeb2fa93b1fd9adb0da4738135b112f39ce350178c36fa9b737fb70154f',
    '/application/p/logic/MetabolismPal.php' => '3ed68654e95faaf4a1c69cf32b4cc875beb8f6cad1d326074b40ca3d7d2c38d0',
    '/application/model/PalRecord.php' => 'f3e192a08baa17f38c25506aa2e11c6ba4ae4735a69e2bec93def0912378fdaa',
    '/application/model/MetabolismPal.php' => 'aa057d2d36127ab4fc1840642b92c1c010467a8a13d8b7f1e01dc82b5c0a2d42',
    '/application/model/Staff.php' => 'e709b91af8e78758b0adbb29697dba4d16d62402d8c365712afe9c47e72277d3',
    '/application/model/Sport.php' => '0926a7240a54afbe8c0435c26810c2f3745fe3645d2e11c1dcb607f76a4eee01',
];
if (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400 || is_link($root)) {
    throw new RuntimeException('Runtime/root drift');
}
foreach ($hashes as $path => $sha) if (hash_file('sha256', $root . $path) !== $sha) {
    throw new RuntimeException('Business source drift');
}
$command = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
$children = trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
$base = 'http://127.0.0.1:' . $port;
$boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
if (strpos($command, $root . '/public') === false
    || strpos($command, '127.0.0.1:' . $port) === false || $children !== ''
    || !is_array($boundary) || ($boundary['external_stubs'] ?? null) !== true
    || ($boundary['redis_isolated'] ?? null) !== true
    || ($boundary['real_queue'] ?? true) !== false) {
    throw new RuntimeException('Isolated HTTP boundary drift');
}
$runtime = require $root . '/application/database.php';
if ($dialect === 'mysql') {
    $config = $runtime;
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    unset($config);
    foreach (['hostname', 'hostport', 'database', 'username', 'password', 'type'] as $key) {
        if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
            throw new RuntimeException('Not code-local development MySQL');
        }
    }
    $dsn = 'mysql:host=' . $runtime['hostname'] . ';port=' . (int)$runtime['hostport']
        . ';dbname=' . $runtime['database'] . ';charset=' . ($runtime['charset'] ?? 'utf8');
} 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('C runtime/worker DB drift');
        }
    }
    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 nonproduction DM');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
$connect = static function () use ($dsn, $runtime): PDO {
    return new PDO($dsn, (string)$runtime['username'], (string)$runtime['password'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5,
    ]);
};
$db = $connect();
if ($dialect === 'mysql') {
    $identity = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
    if (hash('sha256', $identity)
        !== '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401') {
        throw new RuntimeException('B DB identity drift');
    }
} elseif (strtoupper((string)$db->query(
    "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()) !== 'ZHCT_APP') {
    throw new RuntimeException('C schema drift');
}
$t = static function (string $name) use ($dialect): string {
    if (!in_array($name, ['user', 'pal_record', 'metabolism_pal', 'staff', 'sport'], true)) {
        throw new RuntimeException('Unapproved table');
    }
    return $dialect === 'dm' ? strtoupper('ydy_' . $name) : 'ydy_' . $name;
};
$journalPath = (string)getenv('CDX_PAL3_JOURNAL');
if ($mode === '--audit') {
    if (!preg_match('~^/tmp/cdx-pal3-[a-f0-9]{12}/recovery\.json$~D', $journalPath)
        || is_link($journalPath) || is_link(dirname($journalPath))
        || (fileperms(dirname($journalPath)) & 0777) !== 0700
        || (fileperms($journalPath) & 0777) !== 0600) {
        throw new RuntimeException('Audit journal invalid');
    }
    $saved = json_decode((string)file_get_contents($journalPath), true);
    if (!is_array($saved) || ($saved['lane'] ?? null) !== $lane
        || ($saved['phase'] ?? null) !== 'actor_deleted'
        || !preg_match('/^CDXPAL3[A-F0-9]{12}$/D', (string)($saved['marker'] ?? ''))
        || (int)($saved['actor_id'] ?? 0) <= 0) {
        throw new RuntimeException('Audit journal ownership invalid');
    }
    $q = $db->prepare('SELECT COUNT(*) FROM ' . $t('user')
        . ' WHERE ID=? OR UUID=? OR ACCOUNT=?');
    $q->execute([(int)$saved['actor_id'], $saved['marker'], $saved['marker']]);
    if ((int)$q->fetchColumn() !== 0) throw new RuntimeException('Actor remains');
    unlink($journalPath); rmdir(dirname($journalPath));
    echo json_encode(['status' => 'independent_zero_pass', 'lane' => $lane,
        'actor_count' => 0, 'private_journal_removed' => true]), PHP_EOL;
    exit(0);
}
$stage = 'natural_sources';
$recordSql = 'SELECT * FROM ' . $t('pal_record')
    . " WHERE UUID IS NOT NULL AND UUID<>'' ORDER BY ID DESC";
$staffSql = 'SELECT UUID,SEX,BIRTHDAY,SPORT_UUID,SPORT_EVENT_UUID'
    . ' FROM ' . $t('staff') . " WHERE IS_SHOW=1 AND UUID<>''"
    . " AND BIRTHDAY IS NOT NULL AND SPORT_UUID IS NOT NULL AND SPORT_UUID<>''"
    . ' ORDER BY ID';
$record = $db->query($dialect === 'dm'
    ? 'SELECT * FROM (' . $recordSql . ') WHERE ROWNUM<=1'
    : $recordSql . ' LIMIT 1')->fetch(PDO::FETCH_ASSOC);
$staff = $db->query($dialect === 'dm'
    ? 'SELECT * FROM (' . $staffSql . ') WHERE ROWNUM<=1'
    : $staffSql . ' LIMIT 1')->fetch(PDO::FETCH_ASSOC);
if (!is_array($record) || !is_array($staff)) throw new RuntimeException('Natural PAL source absent');
$palRows = $db->query('SELECT STRENGTH,PAL FROM ' . $t('metabolism_pal')
    . ' WHERE STRENGTH IN (11,12,13,14,15,17,18,19,20,21,22,23,24,25,26)'
    . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC);
if (count($palRows) < 15) throw new RuntimeException('PAL parameter source too small');
$allowed = [];
foreach ($palRows as $row) {
    $allowed[(int)$row['strength']][number_format((float)$row['pal'], 6, '.', '')] = true;
}
$sourceDigest = hash('sha256', serialize([$record, $staff, $palRows]));
$oldActors = (int)$db->query('SELECT COUNT(*) FROM ' . $t('user')
    . " WHERE ACCOUNT LIKE 'CDXPAL3%'")->fetchColumn();
if ($mode === '--preflight') {
    echo json_encode(['status' => 'select_only_ready', 'lane' => $lane,
        'pal_record_count' => (int)$db->query('SELECT COUNT(*) FROM ' . $t('pal_record'))->fetchColumn(),
        'pal_parameter_count' => (int)$db->query('SELECT COUNT(*) FROM ' . $t('metabolism_pal'))->fetchColumn(),
        'staff_source_ready' => true, 'old_actor_count' => $oldActors,
        'http_sent' => false, 'dml' => false]), PHP_EOL;
    exit(0);
}
if ($oldActors !== 0) throw new RuntimeException('Old actor marker present');
$marker = 'CDXPAL3' . strtoupper(bin2hex(random_bytes(6)));
$directory = '/tmp/cdx-pal3-' . strtolower(bin2hex(random_bytes(6)));
if (file_exists($directory) || !mkdir($directory, 0700)) {
    throw new RuntimeException('Private directory unavailable');
}
chmod($directory, 0700);
$journalPath = $directory . '/recovery.json';
$saved = ['lane' => $lane, 'marker' => $marker, 'actor_id' => 0, 'phase' => 'before_insert'];
$save = static function () use (&$saved, $journalPath): void {
    if (file_put_contents($journalPath, json_encode($saved), LOCK_EX) === false
        || !chmod($journalPath, 0600)) throw new RuntimeException('Journal write failed');
};
$save();
$result = ['status' => 'FAIL', 'lane' => $lane];
$failure = '';
try {
    $stage = 'actor_insert';
    $db->prepare('INSERT INTO ' . $t('user')
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,0,?,?,0)')->execute([$marker, $marker, 'DISABLED',
            strtoupper(bin2hex(random_bytes(8))), $marker, date('Y-m-d H:i:s'), 'CODEX']);
    $q = $db->prepare('SELECT ID FROM ' . $t('user')
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
    $q->execute([$marker, $marker, 'CODEX']);
    $ids = $q->fetchAll(PDO::FETCH_COLUMN);
    if (count($ids) !== 1 || (int)$ids[0] <= 0) throw new RuntimeException('Actor ambiguous');
    $saved['actor_id'] = (int)$ids[0]; $saved['phase'] = 'actor_inserted'; $save();
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t", [
        (string)$saved['actor_id'], $marker, $marker, '0', '', '', $marker,
    ]));
    $request = static function (string $path, array $form) use ($base, $token): array {
        $stream = @fopen($base . $path, 'rb', false,
            stream_context_create(['http' => ['method' => 'POST', 'timeout' => 20,
                'ignore_errors' => true,
                'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: "
                    . $token . "\r\n",
                'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986)]]));
        $raw = $stream === false ? false : stream_get_contents($stream, 1048577);
        $headers = $http_response_header ?? [];
        if (is_resource($stream)) fclose($stream);
        preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
        return [(int)($match[1] ?? 0), is_string($raw) ? json_decode($raw, true) : null];
    };
    $typeShape = static function ($node) use (&$typeShape) {
        if (!is_array($node)) return gettype($node);
        $out = []; foreach ($node as $k => $v) $out[$k] = $typeShape($v);
        if (array_keys($out) !== range(0, count($out) - 1)) ksort($out);
        return $out;
    };
    $cases = [];
    $stage = 'http_record_lists';
    [$http, $body] = $request('/p/metabolismPal/recordlists', ['page' => 1, 'pagesize' => 5]);
    $rows = $body['data']['rows'] ?? null;
    $found = null;
    if (is_array($rows)) foreach ($rows as $row) if (($row['uuid'] ?? null) === $record['uuid']) $found = $row;
    if ($http !== 200 || ($body['code'] ?? null) !== 0 || !is_array($found)) {
        throw new RuntimeException('recordLists source mismatch');
    }
    $cases['recordLists'] = ['source_match' => true,
        'type_sha' => hash('sha256', serialize($typeShape($body['data'])))];
    $stage = 'http_record_info';
    [$http, $body] = $request('/p/metabolismPal/recordinfo', ['uuid' => $record['uuid']]);
    $info = $body['data'] ?? null;
    if ($http !== 200 || ($body['code'] ?? null) !== 0 || !is_array($info)
        || ($info['uuid'] ?? null) !== $record['uuid']) {
        throw new RuntimeException('recordInfo source mismatch');
    }
    $cases['recordInfo'] = ['source_match' => true,
        'type_sha' => hash('sha256', serialize($typeShape($info)))];
    $stage = 'http_find_query_list';
    [$http, $body] = $request('/p/metabolismPal/findquerylist', [
        'uuid' => $staff['uuid'], 'sex' => (int)$staff['sex'],
        'sport_uuid' => $staff['sport_uuid'],
        'sport_event_uuid' => (string)($staff['sport_event_uuid'] ?? ''),
    ]);
    $data = $body['data'] ?? null;
    $strengths = ['strength_1'=>11,'strength_2'=>12,'strength_3'=>13,
        'strength_4'=>14,'strength_5'=>15,'rpe_1'=>17,'rpe_2'=>18,
        'rpe_3'=>19,'rpe_4'=>20,'rpe_5'=>21,'rpe_6'=>22,'rpe_7'=>23,
        'rpe_8'=>24,'rpe_9'=>25,'rpe_10'=>26];
    if ($http !== 200 || ($body['code'] ?? null) !== 0 || !is_array($data)) {
        throw new RuntimeException('findquerylist failed');
    }
    foreach ($strengths as $key => $strength) {
        $normalized = number_format((float)($data[$key] ?? NAN), 6, '.', '');
        if (!isset($allowed[$strength][$normalized])) {
            throw new RuntimeException('findquerylist PAL value outside original table');
        }
    }
    $cases['findquerylist'] = ['source_match' => true,
        'type_sha' => hash('sha256', serialize($typeShape($data)))];
    $stage = 'source_unchanged';
    $fresh = $connect();
    $afterRecord = $fresh->prepare('SELECT * FROM ' . $t('pal_record') . ' WHERE UUID=?');
    $afterRecord->execute([$record['uuid']]);
    $afterStaff = $fresh->prepare('SELECT UUID,SEX,BIRTHDAY,SPORT_UUID,SPORT_EVENT_UUID'
        . ' FROM ' . $t('staff') . ' WHERE UUID=?');
    $afterStaff->execute([$staff['uuid']]);
    $afterPal = $fresh->query('SELECT STRENGTH,PAL FROM ' . $t('metabolism_pal')
        . ' WHERE STRENGTH IN (11,12,13,14,15,17,18,19,20,21,22,23,24,25,26)'
        . ' ORDER BY ID')->fetchAll(PDO::FETCH_ASSOC);
    if (hash('sha256', serialize([$afterRecord->fetch(PDO::FETCH_ASSOC),
            $afterStaff->fetch(PDO::FETCH_ASSOC), $afterPal])) !== $sourceDigest) {
        throw new RuntimeException('Natural PAL source changed');
    }
    $result = ['status' => 'HTTP_PASS_PENDING_AUDIT', 'lane' => $lane,
        'cases' => $cases, 'source_unchanged' => true];
} catch (Throwable $error) {
    $failure = get_class($error) . ':' . (string)$error->getCode();
    $result['failure_stage'] = $stage;
}
$afterBoundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
if (!is_array($afterBoundary) || ($afterBoundary['external_stubs'] ?? null) !== true
    || ($afterBoundary['real_queue'] ?? true) !== false
    || trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children')) !== '') {
    echo json_encode(['status' => 'uncertain_http_retain_journal',
        'journal' => $journalPath]), PHP_EOL; exit(3);
}
$clean = $connect();
$del = $clean->prepare('DELETE FROM ' . $t('user')
    . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?');
$del->execute([(int)$saved['actor_id'], $marker, $marker, 'CODEX']);
if ($del->rowCount() !== 1) throw new RuntimeException('Actor cleanup mismatch');
$saved['phase'] = 'actor_deleted'; $save();
$result['journal'] = $journalPath;
if ($failure !== '') $result['failure'] = $failure;
echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($failure === '' ? 0 : 2);
