<?php

declare(strict_types=1);

// Existing-row-only RPE compatibility: own disabled actor, hidden staff and one complete RPE/INFO pair.
$lane = (string)($argv[1] ?? '');
$mode = (string)($argv[2] ?? '');
$selfSha = $lane === 'B' ? (string)hash_file('sha256', __FILE__) : '';
if (count($argv) !== 3
    || !in_array($lane, ['B', 'C'], true)
    || !in_array($mode, ['--preflight', '--execute', '--audit'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_RPE_APPROVAL') !== 'ROOT_APPROVED_RPE_BC_V1'
            || ($lane === 'B'
                ? !hash_equals($selfSha, (string)getenv('CDX_RPE_SHA'))
                : (string)getenv('CDX_RPE_SOURCE_CHECKED') !== '1')))) {
    echo json_encode(['status' => 'default_deny', 'connected' => false,
        'http_sent' => false, 'dml' => false]), PHP_EOL;
    exit(78);
}

$stage = 'boundary';
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(),
        'message_sha' => hash('sha256', $error->getMessage())]), PHP_EOL;
    exit(2);
});

if ($lane === 'B') {
    $statePath = '/tmp/cdx-rpe-b-worker.state';
    if (!is_file($statePath) || is_link($statePath)
        || (fileperms($statePath) & 0777) !== 0600) {
        throw new RuntimeException('B worker state absent');
    }
    $state = parse_ini_file($statePath, false, INI_SCANNER_RAW);
    $root = (string)($state['root'] ?? '');
    $pid = (int)($state['pid'] ?? 0);
    $port = (int)($state['port'] ?? 0);
    if (!preg_match('~^/tmp/cdx-rpe-b-runtime\.[A-Za-z0-9]+$~D', $root)
        || is_link($root) || $pid <= 0 || $port !== 18082) {
        throw new RuntimeException('B worker state invalid');
    }
} else {
    $root = '/tmp/zhct-dm-poc-20260928.fcpiiN/store';
    $pid = 112175;
    $port = 18080;
}
$base = 'http://127.0.0.1:' . $port;

$runtimeReady = static function () use ($lane, $pid, $port, $root, $base): bool {
    if ($lane === 'B') {
        $command = (string)shell_exec('/bin/ps -p ' . $pid . ' -o command=');
        $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:' . $port
            . ' -sTCP:LISTEN 2>/dev/null'));
        $processReady = $owner === (string)$pid
            && strpos($command, '-S 127.0.0.1:' . $port) !== false
            && strpos($command, $root . '/public') !== false;
    } else {
        $command = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
        $children = trim((string)@file_get_contents('/proc/' . $pid . '/task/' . $pid . '/children'));
        $processReady = strpos($command, $root . '/public') !== false
            && strpos($command, '127.0.0.1:' . $port) !== false && $children === '';
    }
    $boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary'), true);
    return $processReady && is_array($boundary)
        && ($boundary['external_stubs'] ?? null) === true
        && ($boundary['redis_isolated'] ?? null) === true
        && ($boundary['real_queue'] ?? true) === false;
};
if (!$runtimeReady()) throw new RuntimeException('HTTP worker boundary drift');

$sourceHashes = [
    'application/p/controller/Rpe.php' => '51d4bedaf9e71ac3d32229cc4a475a852f52d8b4145260da7470c41840c60e30',
    'application/p/logic/Rpe.php' => '52fb2bc13dbfee44ef7eeb663e304398bd859a26b236ea6092f182bb9c5936f9',
    'application/model/Rpe.php' => 'b5a4d9c587f7efcf889858076f4d52db2957dc8be338bb0bc9ccb365ae3890b0',
    'application/validate/Rpe.php' => '60fff76d5167c1f1b5ed9a40b056c65a71c4ee7b64af8bb7a14a1110c955ac16',
    'application/model/Staff.php' => 'e709b91af8e78758b0adbb29697dba4d16d62402d8c365712afe9c47e72277d3',
    'application/model/DateMenu.php' => '61dd192dc186c334346bac06fb50243dc9dceb6315e75b836b2132b7c6173154',
    'application/model/BaseModel.php' => '439740d6cf118e4541dfa719e120afd95979c497e7639f43d8efe01e6a5d3596',
    'application/base/AdminController.php' => '7565b36fa4dda2c21cc4fa3b37753dbffb7c3a464bb8c11ea0f17ca00bececa5',
    'application/base/Security.php' => 'b82e5ed982e6084a2b2942c033d4767428fb6f1012ba628f609477a7563eb4c3',
];
foreach ($sourceHashes as $relative => $hash) {
    $path = $root . '/' . $relative;
    if (!is_file($path) || is_link($path) || !hash_equals($hash, (string)hash_file('sha256', $path))) {
        throw new RuntimeException('Business source drift');
    }
}

$stage = 'database-boundary';
if ($lane === 'B') {
    $cfgPath = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/store/application/config/local/database.php';
    $cfg = require $cfgPath;
    if (!is_array($cfg) || strtolower((string)($cfg['type'] ?? 'mysql')) !== 'mysql'
        || (int)($cfg['hostport'] ?? 3306) !== 3306
        || hash_file('sha256', $root . '/application/database.php')
            !== '6a64b60c0c4f9da3856ea2a0a39a828609a6d73053b2946b0459d28e7c2a5216') {
        throw new RuntimeException('B database config drift');
    }
    $process = ' ' . (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=') . ' ';
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER', 'password' => 'POC_PASSWORD']
        as $key => $environmentKey) {
        $expected = (string)($cfg[$key] ?? ($key === 'hostport' ? '3306' : ''));
        if ($expected === '' || strpos($process, ' ' . $environmentKey . '=' . $expected . ' ') === false) {
            throw new RuntimeException('B worker differs from code local config');
        }
    }
    $dsn = 'mysql:host=' . $cfg['hostname'] . ';port=' . (int)($cfg['hostport'] ?? 3306)
        . ';dbname=' . $cfg['database'] . ';charset=' . (string)($cfg['charset'] ?? 'utf8');
} else {
    $worker = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $entry) {
        $pair = explode('=', $entry, 2);
        if (count($pair) === 2) $worker[$pair[0]] = $pair[1];
    }
    foreach (['POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
        'POC_SCHEMA' => 'ZHCT_APP', 'POC_USER' => 'ZHCT_APP',
        'POC_DIALECT' => 'dm', 'POC_EXTERNAL_STUBS' => '1'] as $key => $expected) {
        if ((string)($worker[$key] ?? '') !== $expected) throw new RuntimeException('C worker env drift');
    }
    $cfg = require $root . '/application/database.php';
    if (!is_array($cfg) || (string)($cfg['hostname'] ?? '') !== '127.0.0.1'
        || (int)($cfg['hostport'] ?? 0) !== 5237
        || strtoupper((string)($cfg['database'] ?? '')) !== 'ZHCT_APP') {
        throw new RuntimeException('C database config drift');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
$connect = static function () use ($dsn, $cfg): PDO {
    return new PDO($dsn, (string)$cfg['username'], (string)$cfg['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
};
$db = $connect();
$identity = $lane === 'B'
    ? (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();
$identitySha = $lane === 'B'
    ? '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401'
    : 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
if (!hash_equals($identitySha, hash('sha256', $identity))) {
    throw new RuntimeException('Database identity drift');
}

$prefix = $lane === 'B' ? 'ydy_' : 'YDY_';
$tables = $lane === 'B'
    ? ['user'=>'ydy_user','staff'=>'ydy_staff','rpe'=>'ydy_rpe',
        'info'=>'ydy_rpe_info','menu'=>'ydy_date_menu']
    : ['user'=>'YDY_USER','staff'=>'YDY_STAFF','rpe'=>'YDY_RPE',
        'info'=>'YDY_RPE_INFO','menu'=>'YDY_DATE_MENU'];
$read = static function (PDO $pdo, string $sql, array $args = [], int $cap = 100): array {
    if (!preg_match('/^SELECT\b/i', $sql)) throw new RuntimeException('Read helper received non-SELECT');
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    $output = [];
    while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
        if (count($output) >= $cap) throw new RuntimeException('Read row cap exceeded');
        $clean = [];
        foreach (array_change_key_case($row, CASE_LOWER) as $key => $value) {
            $clean[$key] = is_resource($value) ? stream_get_contents($value) : $value;
        }
        ksort($clean);
        $output[] = $clean;
    }
    return $output;
};
$count = static function (PDO $pdo, string $sql, array $args = []) use ($read): int {
    $rows = $read($pdo, $sql, $args, 1);
    return (int)(reset($rows[0]) ?: 0);
};

$stage = 'schema-gate';
$required = [
    'user' => ['ID','UUID','ACCOUNT','PWD','SALT','NAME','TYPE','STATUS','CREATE_TIME','CREATE_BY','IS_DELETE'],
    'staff' => ['ID','UUID','STATION_UUID','IS_ATHLETE','GRADE','NAME','SEX','BIRTHDAY',
        'HEIGHT','WEIGHT','SPORT_UUID','SPORT_EVENT_UUID','IN_TIME','CREATE_TIME','CREATE_BY',
        'PERSONNEL_UMBER','IS_SHOW','STATUS','PERMANENT_ADDRESS','COACH_NAME','SPORT_YEAR',
        'JOB_STATUS','ZP_IS_SYNCHRO'],
    'rpe' => ['ID','UUID','STAFF_UUID','TRAIN_DATE','TRAIN_CON','TRAIN_TEMPERATURE',
        'TRAIN_ENVIRONMENT','SPECIAL_FUNCTION','MOTION_PERCENTAGE','NUTRITION_RESCRIPTION',
        'CREATE_TIME','CREATE_BY','MENU_MODEL'],
    'info' => ['ID','RPE_UUID','START_TIME','END_TIME','TOTAL_MIN','OTHER_MIN',
        'IS_STRENGTH','IS_CONTENT','SUPPLY_SYSTEM'],
];
$metadata = [];
foreach ($required as $logical => $columns) {
    if ($lane === 'B') {
        $stmt = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA'
            . ' FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?'
            . ' ORDER BY ORDINAL_POSITION');
        $stmt->execute([$tables[$logical]]);
    } else {
        $stmt = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,'
            . ' NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID');
        $stmt->execute([$tables[$logical]]);
    }
    $names = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $names[] = strtoupper((string)$row['column_name']);
        $metadata[] = [$logical, $row];
    }
    foreach ($columns as $column) {
        if (!in_array($column, $names, true)) throw new RuntimeException('Required column absent');
    }
}
foreach (['user','staff','rpe','info'] as $logical) {
    if ($lane === 'B') {
        $stmt = $db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS'
            . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?');
        $stmt->execute([$tables[$logical]]); $triggers = (int)$stmt->fetchColumn();
        $stmt = $db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE'
            . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND REFERENCED_TABLE_NAME IS NOT NULL');
        $stmt->execute([$tables[$logical]]); $fkOut = (int)$stmt->fetchColumn();
        $stmt = $db->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE'
            . ' WHERE REFERENCED_TABLE_SCHEMA=DATABASE() AND REFERENCED_TABLE_NAME=?');
        $stmt->execute([$tables[$logical]]); $fkIn = (int)$stmt->fetchColumn();
    } else {
        $stmt = $db->prepare('SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME=?');
        $stmt->execute([$tables[$logical]]); $triggers = (int)$stmt->fetchColumn();
        $stmt = $db->prepare("SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE TABLE_NAME=? AND CONSTRAINT_TYPE='R'");
        $stmt->execute([$tables[$logical]]); $fkOut = (int)$stmt->fetchColumn();
        $stmt = $db->prepare("SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R'"
            . ' AND R_CONSTRAINT_NAME IN (SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME=?)');
        $stmt->execute([$tables[$logical]]); $fkIn = (int)$stmt->fetchColumn();
    }
    if ($triggers !== 0 || $fkOut !== 0 || $fkIn !== 0) {
        throw new RuntimeException('Unreviewed trigger or FK');
    }
}

$referenceTables = static function (PDO $pdo, string $column) use ($lane): array {
    if ($lane === 'B') {
        $stmt = $pdo->prepare('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS'
            . ' WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME=?');
        $stmt->execute([strtolower($column)]);
    } else {
        $stmt = $pdo->prepare('SELECT TABLE_NAME FROM USER_TAB_COLUMNS WHERE COLUMN_NAME=?');
        $stmt->execute([strtoupper($column)]);
    }
    return $stmt->fetchAll(PDO::FETCH_COLUMN);
};
$staffRefTables = $referenceTables($db, 'STAFF_UUID');
$rpeRefTables = $referenceTables($db, 'RPE_UUID');
if (!$staffRefTables || !$rpeRefTables || count($staffRefTables) > 200 || count($rpeRefTables) > 50) {
    throw new RuntimeException('Reference inventory invalid');
}
foreach (array_merge($staffRefTables, $rpeRefTables) as $table) {
    if (!preg_match('/^' . preg_quote($prefix, '/') . '[A-Z0-9_]+$/i', (string)$table)) {
        throw new RuntimeException('Reference table name invalid');
    }
}
$references = static function (PDO $pdo, array $referenceTables, string $column, string $value)
    use ($count): array {
    $result = [];
    foreach ($referenceTables as $table) {
        $rows = $count($pdo, 'SELECT COUNT(*) FROM ' . $table . ' WHERE ' . $column . '=?', [$value]);
        if ($rows !== 0) $result[(string)$table] = $rows;
    }
    return $result;
};
$markerCount = static function (PDO $pdo) use ($count, $tables): int {
    return $count($pdo, 'SELECT COUNT(*) FROM ' . $tables['user']
            . " WHERE UUID LIKE 'CDXRPE%' OR ACCOUNT LIKE 'CDXRPE%' OR CREATE_BY LIKE 'CDXRPE%'")
        + $count($pdo, 'SELECT COUNT(*) FROM ' . $tables['staff']
            . " WHERE UUID LIKE 'CDXRPE%' OR PERSONNEL_UMBER LIKE 'CDXRPE%' OR CREATE_BY LIKE 'CDXRPE%'")
        + $count($pdo, 'SELECT COUNT(*) FROM ' . $tables['rpe']
            . " WHERE UUID LIKE 'CDXRPE%' OR STAFF_UUID LIKE 'CDXRPE%' OR CREATE_BY LIKE 'CDXRPE%'")
        + $count($pdo, 'SELECT COUNT(*) FROM ' . $tables['info'] . " WHERE RPE_UUID LIKE 'CDXRPE%'");
};
$prior = [
    'user' => $count($db, 'SELECT COUNT(*) FROM ' . $tables['user']),
    'staff' => $count($db, 'SELECT COUNT(*) FROM ' . $tables['staff']),
    'rpe' => $count($db, 'SELECT COUNT(*) FROM ' . $tables['rpe']),
    'info' => $count($db, 'SELECT COUNT(*) FROM ' . $tables['info']),
];

if ($mode === '--preflight') {
    $old = $markerCount($db);
    echo json_encode(['status' => $old === 0 ? 'select_only_ready' : 'marker_stop',
        'lane' => $lane, 'metadata_sha' => hash('sha256', serialize($metadata)),
        'foreign_keys' => 0, 'triggers' => 0, 'old_marker_rows' => $old,
        'staff_ref_tables' => count($staffRefTables),
        'rpe_ref_tables' => count($rpeRefTables), 'prior_counts' => $prior,
        'http_sent' => false, 'dml' => false]), PHP_EOL;
    exit($old === 0 ? 0 : 2);
}

if ($mode === '--audit') {
    $stage = 'independent-audit';
    $journalPath = (string)getenv('CDX_RPE_JOURNAL');
    if (!preg_match('~^/tmp/cdx-rpe-[bc]-[a-f0-9]{12}/recovery\.json$~D', $journalPath)
        || !is_file($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['state'] ?? null) !== 'cleaned'
        || !preg_match('/^CDXRPE[A-F0-9]{12}$/D', (string)($saved['marker'] ?? ''))) {
        throw new RuntimeException('Audit journal ownership invalid');
    }
    $marker = (string)$saved['marker'];
    $actorUuid = $marker . 'A'; $staffUuid = $marker . 'S'; $rpeUuid = $marker . 'R';
    $fresh = $connect();
    $staffRefs = $references($fresh, $staffRefTables, 'STAFF_UUID', $staffUuid);
    $rpeRefs = $references($fresh, $rpeRefTables, 'RPE_UUID', $rpeUuid);
    if ($markerCount($fresh) !== 0 || $staffRefs || $rpeRefs) {
        throw new RuntimeException('Independent audit not zero');
    }
    unlink($journalPath); rmdir(dirname($journalPath));
    echo json_encode(['status' => 'independent_zero_pass', 'lane' => $lane,
        'marker_rows' => 0, 'staff_references' => 0, 'rpe_references' => 0,
        'private_journal_removed' => true]), PHP_EOL;
    exit(0);
}

if ($markerCount($db) !== 0) throw new RuntimeException('Old RPE marker remains');
$marker = 'CDXRPE' . strtoupper(bin2hex(random_bytes(6)));
$actorUuid = $marker . 'A'; $staffUuid = $marker . 'S'; $rpeUuid = $marker . 'R';
$day = '2001-01-02';
if ($references($db, $staffRefTables, 'STAFF_UUID', $staffUuid)
    || $references($db, $rpeRefTables, 'RPE_UUID', $rpeUuid)
    || $count($db, 'SELECT COUNT(*) FROM ' . $tables['menu']
        . ' WHERE STAFF_UUID=? AND MENU_DATE=?', [$staffUuid, $day]) !== 0) {
    throw new RuntimeException('Fixture identifier collision');
}
$privateDir = '/tmp/cdx-rpe-' . strtolower($lane) . '-' . strtolower(bin2hex(random_bytes(6)));
if (file_exists($privateDir) || !mkdir($privateDir, 0700) || !chmod($privateDir, 0700)) {
    throw new RuntimeException('Recovery directory unavailable');
}
$journalPath = $privateDir . '/recovery.json';
$journal = ['lane' => $lane, 'marker' => $marker, 'state' => 'prepared',
    'ids' => ['actor' => 0, 'staff' => 0, 'rpe' => 0, 'info' => 0]];
$saveJournal = static function () use (&$journal, $journalPath): void {
    $encoded = json_encode($journal, JSON_UNESCAPED_SLASHES);
    if (!is_string($encoded) || file_put_contents($journalPath, $encoded, LOCK_EX) !== strlen($encoded)
        || !chmod($journalPath, 0600)) throw new RuntimeException('Recovery journal write failed');
};
$saveJournal();

$shape = null;
$shape = static function ($value) use (&$shape) {
    if (!is_array($value)) return gettype($value);
    if ($value === []) return [];
    $keys = array_keys($value);
    if ($keys === range(0, count($value) - 1)) return ['[]' => $shape($value[0])];
    $result = [];
    foreach ($value as $key => $item) $result[(string)$key] = $shape($item);
    ksort($result);
    return $result;
};
$routeEvidence = static function (array $json) use ($shape): array {
    return ['key_sha' => hash('sha256', serialize(array_keys($json))),
        'type_sha' => hash('sha256', serialize($shape($json))), 'shape' => $shape($json)];
};
$httpUncertain = false;
$post = static function (string $action, array $form, ?string $token)
    use ($base, $runtimeReady, &$httpUncertain): array {
    if (!in_array($action, ['addAltitude', 'addNutrition', 'info'], true) || !$runtimeReady()) {
        throw new RuntimeException('HTTP action or boundary rejected');
    }
    $content = http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    $headers = 'Content-Type: application/x-www-form-urlencoded' . "\r\n"
        . 'Content-Length: ' . strlen($content) . "\r\n"
        . ($token === null ? '' : 'token: ' . $token . "\r\n");
    $httpUncertain = true;
    $stream = @fopen($base . '/p/rpe/' . $action, 'rb', false,
        stream_context_create(['http' => ['method' => 'POST', 'header' => $headers,
            'content' => $content, 'timeout' => 20, 'ignore_errors' => true,
            'follow_location' => 0]]));
    $body = is_resource($stream) ? stream_get_contents($stream, 1048577) : false;
    $meta = is_resource($stream) ? stream_get_meta_data($stream) : [];
    $headersOut = $http_response_header ?? [];
    if (is_resource($stream)) fclose($stream);
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headersOut[0] ?? ''), $match);
    $withinLimit = is_string($body) && strlen($body) <= 1048576;
    $json = $withinLimit ? json_decode($body, true) : null;
    if (!isset($match[1]) || (int)$match[1] !== 200 || !empty($meta['timed_out'])
        || !$withinLimit || !is_array($json) || json_last_error() !== JSON_ERROR_NONE
        || !array_key_exists('code', $json)) {
        throw new RuntimeException('HTTP transport/JSON outcome unknown');
    }
    $httpUncertain = false;
    return $json;
};

$ids = ['actor' => 0, 'staff' => 0, 'rpe' => 0, 'info' => 0];
$fixtureCommitted = false; $failure = null;
$result = ['status' => 'FAIL', 'lane' => $lane, 'routes' => [], 'rejections' => [],
    'fixture_hidden' => false, 'existing_row_only' => false,
    'excluded_actions' => ['addTemperature', 'addEenginery'], 'journal' => $journalPath];

try {
    $stage = 'fixture-transaction';
    $now = date('Y-m-d H:i:s');
    $db->beginTransaction();
    $stmt = $db->prepare('INSERT INTO ' . $tables['user']
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,STATUS,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?,?)');
    $stmt->execute([$actorUuid,$actorUuid,'DISABLED',strtoupper(bin2hex(random_bytes(8))),
        $marker,0,0,$now,$marker,0]);
    if ($stmt->rowCount()!==1) throw new RuntimeException('Actor insert mismatch');
    $staff = [
        'UUID'=>$staffUuid,'STATION_UUID'=>$marker.'T','IS_ATHLETE'=>1,'GRADE'=>1,
        'NAME'=>$marker,'SEX'=>1,'BIRTHDAY'=>'2000-01-01','HEIGHT'=>'170.00','WEIGHT'=>'60.00',
        'NATION'=>'测试','NATIVE_PLACE'=>'测试','IDENTITY_TYPE'=>1,
        'IDENTITY_NUMBER'=>$marker,'HEAD_IMG'=>$marker,'SPORT_UUID'=>$marker.'P',
        'SPORT_EVENT_UUID'=>'[]','IN_TIME'=>'2020-01-01','CREATE_TIME'=>$now,
        'CREATE_BY'=>$marker,'PERSONNEL_UMBER'=>$marker,'IS_SHOW'=>0,'STATUS'=>0,
        'PERMANENT_ADDRESS'=>'测试地址','COACH_NAME'=>'测试教练','SPORT_YEAR'=>'1',
        'JOB_STATUS'=>0,'ZP_IS_SYNCHRO'=>1,
    ];
    $columns = array_keys($staff);
    $stmt = $db->prepare('INSERT INTO ' . $tables['staff'] . ' (' . implode(',', $columns)
        . ') VALUES (' . implode(',', array_fill(0, count($columns), '?')) . ')');
    $stmt->execute(array_values($staff));
    if ($stmt->rowCount()!==1) throw new RuntimeException('Staff insert mismatch');
    $stmt = $db->prepare('INSERT INTO ' . $tables['rpe']
        . ' (UUID,STAFF_UUID,TRAIN_DATE,TRAIN_CON,TRAIN_TEMPERATURE,TRAIN_ENVIRONMENT,'
        . ' SPECIAL_FUNCTION,MOTION_PERCENTAGE,NUTRITION_RESCRIPTION,CREATE_TIME,CREATE_BY,MENU_MODEL)'
        . ' VALUES (?,?,?,?,?,?,?,?,?,?,?,?)');
    $stmt->execute([$rpeUuid,$staffUuid,$day,1,34,36,'',0,0,$now,$actorUuid,1]);
    if ($stmt->rowCount()!==1) throw new RuntimeException('RPE seed insert mismatch');
    $stmt = $db->prepare('INSERT INTO ' . $tables['info']
        . ' (RPE_UUID,START_TIME,END_TIME,TOTAL_MIN,OTHER_MIN,IS_STRENGTH,IS_CONTENT,SUPPLY_SYSTEM)'
        . ' VALUES (?,?,?,?,?,?,?,?)');
    $stmt->execute([$rpeUuid,'08:00:00','09:00:00',60,0,28,1,0]);
    if ($stmt->rowCount()!==1) throw new RuntimeException('RPE_INFO seed insert mismatch');
    $actorRows = $read($db, 'SELECT ID FROM ' . $tables['user']
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND STATUS=0 AND CREATE_BY=?',
        [$actorUuid,$actorUuid,$marker],2);
    $staffRows = $read($db, 'SELECT ID,IS_SHOW,STATUS,JOB_STATUS,ZP_IS_SYNCHRO FROM '
        . $tables['staff'] . ' WHERE UUID=? AND PERSONNEL_UMBER=? AND CREATE_BY=?',
        [$staffUuid,$marker,$marker],2);
    $rpeRows = $read($db, 'SELECT * FROM ' . $tables['rpe']
        . ' WHERE UUID=? AND STAFF_UUID=? AND CREATE_BY=?', [$rpeUuid,$staffUuid,$actorUuid],2);
    $infoRows = $read($db, 'SELECT * FROM ' . $tables['info'] . ' WHERE RPE_UUID=?', [$rpeUuid],2);
    if (count($actorRows)!==1 || count($staffRows)!==1 || count($rpeRows)!==1 || count($infoRows)!==1
        || (int)$staffRows[0]['is_show']!==0 || (int)$staffRows[0]['status']!==0
        || (int)$staffRows[0]['job_status']!==0 || (int)$staffRows[0]['zp_is_synchro']!==1
        || (int)$rpeRows[0]['train_environment']!==36
        || (int)$rpeRows[0]['nutrition_rescription']!==0 || (int)$infoRows[0]['total_min']!==60) {
        throw new RuntimeException('Fixture identity or completeness mismatch');
    }
    $ids = ['actor'=>(int)$actorRows[0]['id'],'staff'=>(int)$staffRows[0]['id'],
        'rpe'=>(int)$rpeRows[0]['id'],'info'=>(int)$infoRows[0]['id']];
    if (min($ids)<=0) throw new RuntimeException('Fixture IDs invalid');
    $seedRpe=$rpeRows[0]; $seedInfo=$infoRows[0];
    $db->commit(); $fixtureCommitted=true;
    $journal['state']='fixture_committed'; $journal['ids']=$ids; $saveJournal();
    $result['fixture_hidden']=true;

    require_once $root . '/application/base/Security.php';
    $token=\app\base\Security::encrypt(implode("\t",
        [(string)$ids['actor'],$actorUuid,$marker,'0','','',$actorUuid]));
    if (!is_string($token)||$token==='') throw new RuntimeException('Actor token unavailable');
    $altitudeForm=['staff_uuid'=>$staffUuid,
        'train_date_altitude'=>json_encode([['start_time'=>$day,'end_time'=>$day]])];
    $nutritionForm=['staff_uuid'=>$staffUuid,
        'train_date_nutrition'=>json_encode([['start_time'=>$day,'end_time'=>$day]])];
    $infoForm=['staff_uuid'=>$staffUuid,'train_date'=>$day];
    foreach (['addAltitude'=>$altitudeForm,'addNutrition'=>$nutritionForm,'info'=>$infoForm]
        as $action=>$form) {
        $stage='unauth-'.$action;
        $unauth=$post($action,$form,null);
        if ((int)($unauth['code']??0)===0) throw new RuntimeException('Unauthenticated RPE route accepted');
        $result['rejections'][$action.'.unauth']=true;
    }
    if ($read($connect(),'SELECT * FROM '.$tables['rpe'].' WHERE ID=?',[$ids['rpe']],2)!==[$seedRpe]
        || $read($connect(),'SELECT * FROM '.$tables['info'].' WHERE ID=?',[$ids['info']],2)!==[$seedInfo]) {
        throw new RuntimeException('Unauthenticated requests mutated own fixture');
    }

    $stage='addAltitude-existing-http';
    $altitude=$post('addAltitude',$altitudeForm,$token);
    if ((int)($altitude['code']??-1)!==0) throw new RuntimeException('Altitude existing update rejected');
    $afterAltitude=$read($connect(),'SELECT * FROM '.$tables['rpe']
        .' WHERE STAFF_UUID=? AND TRAIN_DATE=?',[$staffUuid,$day],2);
    $afterAltitudeInfo=$read($connect(),'SELECT * FROM '.$tables['info'].' WHERE RPE_UUID=?',[$rpeUuid],2);
    $afterAltitudeStaff=$read($connect(),'SELECT ID,ZP_IS_SYNCHRO FROM '.$tables['staff']
        .' WHERE UUID=?',[$staffUuid],2);
    if (count($afterAltitude)!==1 || (int)$afterAltitude[0]['id']!==$ids['rpe']
        || rtrim((string)$afterAltitude[0]['uuid'])!==$rpeUuid
        || (int)$afterAltitude[0]['train_environment']!==35
        || (int)$afterAltitude[0]['nutrition_rescription']!==0
        || count($afterAltitudeInfo)!==1 || (int)$afterAltitudeInfo[0]['id']!==$ids['info']
        || count($afterAltitudeStaff)!==1 || (int)$afterAltitudeStaff[0]['id']!==$ids['staff']
        || (int)$afterAltitudeStaff[0]['zp_is_synchro']!==0) {
        throw new RuntimeException('Altitude updated/created row contract mismatch');
    }
    $result['routes']['addAltitude']=array_merge($routeEvidence($altitude),
        ['existing_row_updated'=>true,'created_rpe_rows'=>0,'created_info_rows'=>0,'staff_sync_reset'=>true]);

    $stage='addNutrition-existing-http';
    $nutrition=$post('addNutrition',$nutritionForm,$token);
    if ((int)($nutrition['code']??-1)!==0) throw new RuntimeException('Nutrition existing update rejected');
    $afterNutrition=$read($connect(),'SELECT * FROM '.$tables['rpe']
        .' WHERE STAFF_UUID=? AND TRAIN_DATE=?',[$staffUuid,$day],2);
    $afterNutritionInfo=$read($connect(),'SELECT * FROM '.$tables['info'].' WHERE RPE_UUID=?',[$rpeUuid],2);
    if (count($afterNutrition)!==1 || (int)$afterNutrition[0]['id']!==$ids['rpe']
        || (int)$afterNutrition[0]['train_environment']!==35
        || (int)$afterNutrition[0]['nutrition_rescription']!==1
        || count($afterNutritionInfo)!==1 || (int)$afterNutritionInfo[0]['id']!==$ids['info']) {
        throw new RuntimeException('Nutrition updated/created row contract mismatch');
    }
    $expectedRpe=$seedRpe;
    $expectedRpe['train_environment']=$afterNutrition[0]['train_environment'];
    $expectedRpe['nutrition_rescription']=$afterNutrition[0]['nutrition_rescription'];
    if ($expectedRpe!==$afterNutrition[0] || [$seedInfo]!==$afterNutritionInfo) {
        throw new RuntimeException('Unrelated own RPE/INFO fields changed');
    }
    $result['routes']['addNutrition']=array_merge($routeEvidence($nutrition),
        ['existing_row_updated'=>true,'created_rpe_rows'=>0,'created_info_rows'=>0,
            'other_own_fields_unchanged'=>true]);

    $stage='info-http';
    $detail=$post('info',$infoForm,$token); $data=$detail['data']??null;
    if ((int)($detail['code']??-1)!==0 || !is_array($data)
        || rtrim((string)($data['uuid']??''))!==$rpeUuid
        || rtrim((string)($data['staff_uuid']??''))!==$staffUuid
        || (int)($data['train_environment']??-1)!==35
        || (int)($data['nutrition_rescription']??-1)!==1
        || !is_array($data['load']??null) || count($data['load'])!==1
        || (int)($data['load'][0]['total_min']??-1)!==60) {
        throw new RuntimeException('Info source/response mismatch');
    }
    $result['routes']['info']=array_merge($routeEvidence($detail),
        ['source_match'=>true,'data_shape'=>$shape($data),'load_item_shape'=>$shape($data['load'][0])]);
    if ($count($connect(),'SELECT COUNT(*) FROM '.$tables['rpe'].' WHERE STAFF_UUID=?',[$staffUuid])!==1
        || $count($connect(),'SELECT COUNT(*) FROM '.$tables['info'].' WHERE RPE_UUID=?',[$rpeUuid])!==1
        || $count($connect(),'SELECT COUNT(*) FROM '.$tables['menu']
            .' WHERE STAFF_UUID=? AND MENU_DATE=?',[$staffUuid,$day])!==0) {
        throw new RuntimeException('Existing-row-only or menu invariant failed');
    }
    $result['existing_row_only']=true; $result['status']='HTTP_PASS_PENDING_CLEANUP';
} catch (Throwable $error) {
    if ($db->inTransaction()) $db->rollBack();
    $failure=['stage'=>$stage,'class'=>get_class($error),'code'=>(string)$error->getCode(),
        'message_sha'=>hash('sha256',$error->getMessage())];
}

if (!$fixtureCommitted) {
    if ($markerCount($connect())===0) { unlink($journalPath); rmdir($privateDir); }
    $result['status']='FIXTURE_FAILED_ROLLBACK'; $result['failure']=$failure;
    echo json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL; exit(2);
}
if ($httpUncertain || !$runtimeReady()) {
    $journal['state']='unknown_http_retain'; $journal['ids']=$ids; $saveJournal();
    echo json_encode(['status'=>'unknown_http_retain_fixture','lane'=>$lane,'journal'=>$journalPath]),PHP_EOL;
    exit(3);
}
$stage='reference-scan';
$staffRefs=$references($connect(),$staffRefTables,'STAFF_UUID',$staffUuid);
$rpeRefs=$references($connect(),$rpeRefTables,'RPE_UUID',$rpeUuid);
$ownRpeReferences=(int)($staffRefs[$tables['rpe']]??0);
$ownInfoReferences=(int)($rpeRefs[$tables['info']]??0);
unset($staffRefs[$tables['rpe']],$rpeRefs[$tables['info']]);
if ($ownRpeReferences!==1 || $ownInfoReferences!==1 || $staffRefs || $rpeRefs
    || $count($connect(),'SELECT COUNT(*) FROM '.$tables['menu']
        .' WHERE STAFF_UUID=? AND MENU_DATE=?',[$staffUuid,$day])!==0) {
    $journal['state']='unexpected_reference_retain'; $journal['ids']=$ids; $saveJournal();
    echo json_encode(['status'=>'unexpected_reference_retain_fixture','lane'=>$lane,
        'journal'=>$journalPath,'reference_sha'=>hash('sha256',serialize([$staffRefs,$rpeRefs]))]),PHP_EOL;
    exit(4);
}

$stage='exact-cleanup'; $clean=$connect();
$ownedInfo=$read($clean,'SELECT ID FROM '.$tables['info'].' WHERE ID=? AND RPE_UUID=?',[$ids['info'],$rpeUuid],2);
$ownedRpe=$read($clean,'SELECT ID FROM '.$tables['rpe']
    .' WHERE ID=? AND UUID=? AND STAFF_UUID=? AND CREATE_BY=?',[$ids['rpe'],$rpeUuid,$staffUuid,$actorUuid],2);
$ownedStaff=$read($clean,'SELECT ID FROM '.$tables['staff']
    .' WHERE ID=? AND UUID=? AND PERSONNEL_UMBER=? AND CREATE_BY=? AND IS_SHOW=0 AND STATUS=0',
    [$ids['staff'],$staffUuid,$marker,$marker],2);
$ownedActor=$read($clean,'SELECT ID FROM '.$tables['user']
    .' WHERE ID=? AND UUID=? AND ACCOUNT=? AND CREATE_BY=? AND TYPE=0 AND STATUS=0',
    [$ids['actor'],$actorUuid,$actorUuid,$marker],2);
if (count($ownedInfo)!==1 || count($ownedRpe)!==1 || count($ownedStaff)!==1 || count($ownedActor)!==1) {
    $journal['state']='cleanup_identity_ambiguous'; $journal['ids']=$ids; $saveJournal();
    throw new RuntimeException('Cleanup identity ambiguous');
}
$clean->beginTransaction();
try {
    $deletes=[
        ['DELETE FROM '.$tables['info'].' WHERE ID=? AND RPE_UUID=?',[$ids['info'],$rpeUuid]],
        ['DELETE FROM '.$tables['rpe'].' WHERE ID=? AND UUID=? AND STAFF_UUID=? AND CREATE_BY=?',
            [$ids['rpe'],$rpeUuid,$staffUuid,$actorUuid]],
        ['DELETE FROM '.$tables['staff']
            .' WHERE ID=? AND UUID=? AND PERSONNEL_UMBER=? AND CREATE_BY=? AND IS_SHOW=0 AND STATUS=0',
            [$ids['staff'],$staffUuid,$marker,$marker]],
        ['DELETE FROM '.$tables['user']
            .' WHERE ID=? AND UUID=? AND ACCOUNT=? AND CREATE_BY=? AND TYPE=0 AND STATUS=0',
            [$ids['actor'],$actorUuid,$actorUuid,$marker]],
    ];
    foreach ($deletes as $delete) {
        $stmt=$clean->prepare($delete[0]); $stmt->execute($delete[1]);
        if ($stmt->rowCount()!==1) throw new RuntimeException('Cleanup affected-row mismatch');
    }
    $clean->commit();
} catch (Throwable $cleanupError) {
    if ($clean->inTransaction()) $clean->rollBack();
    $journal['state']='cleanup_failed'; $journal['ids']=$ids; $saveJournal(); throw $cleanupError;
}
$fresh=$connect();
if ($markerCount($fresh)!==0
    || $references($fresh,$staffRefTables,'STAFF_UUID',$staffUuid)
    || $references($fresh,$rpeRefTables,'RPE_UUID',$rpeUuid)) {
    throw new RuntimeException('Cleanup not zero');
}
$journal['state']='cleaned'; $journal['ids']=$ids; $saveJournal();
$result['cleanup']=['marker_rows'=>0,'staff_references'=>0,'rpe_references'=>0,'journal'=>$journalPath];
if ($failure===null && count($result['routes'])===3 && count($result['rejections'])===3
    && $result['existing_row_only']) {
    $result['status']='HTTP_PASS_CLEANED_PENDING_AUDIT';
    echo json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL; exit(0);
}
$result['status']='HTTP_FAIL_CLEANED'; $result['failure']=$failure;
echo json_encode($result,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL; exit(2);
