<?php

declare(strict_types=1);

// One-time recovery for the B team-monitor runner's confirmed timed-out read.
$journalPath = '/tmp/zhct-team5-CDXTM5A35EF77503E1/recovery.json';
$root = '/tmp/zhct-http-store-mysql.dQTTBP';
$base = 'http://127.0.0.1:18081';
$pid = 10001;
$mode = (string)($argv[1] ?? '');
$approval = $mode === '--preflight'
    ? 'ROOT_APPROVED_B_ACTOR_PREFLIGHT_V1'
    : 'ROOT_APPROVED_B_ACTOR_RECOVERY_V1';
if (!in_array($mode, ['--preflight', '--recover-exact'], true)
    || (string)getenv('CDX_TEAM5_RECOVERY_APPROVED')
        !== $approval
    || !hash_equals((string)hash_file('sha256', __FILE__),
        (string)getenv('CDX_TEAM5_RECOVERY_SHA'))) {
    echo "TEAM5_RECOVERY_DENIED\n";
    exit(78);
}
$stage = 'private_journal';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status' => 'recovery_stopped', 'stage' => $stage,
        'class' => get_class($error), 'code' => (string)$error->getCode(),
        'reason_sha' => hash('sha256', $error->getMessage()),
        'journal_retained' => true], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
$dir = dirname($journalPath);
if (is_link($dir) || is_link($journalPath)
    || !is_dir($dir) || !is_file($journalPath)
    || (fileperms($dir) & 0777) !== 0700
    || (fileperms($journalPath) & 0777) !== 0600
    || array_values(array_diff(scandir($dir) ?: [], ['.', '..'])) !== ['recovery.json']) {
    throw new RuntimeException('Private exact journal boundary changed');
}
$journal = json_decode((string)file_get_contents($journalPath), true);
if (!is_array($journal) || ($journal['lane'] ?? null) !== 'B'
    || ($journal['state'] ?? null) !== 'requires_manual_recovery'
    || !preg_match('/^CDXTM5[A-F0-9]{12}$/D', (string)($journal['marker'] ?? ''))
    || $dir !== '/tmp/zhct-team5-' . $journal['marker']
    || (int)($journal['actor_id'] ?? 0) <= 0) {
    throw new RuntimeException('Recovery journal actor identity changed');
}
$marker = (string)$journal['marker'];
$actorId = (int)$journal['actor_id'];

$stage = 'single_worker_completion_barrier';
$cmd = (string)@file_get_contents('/proc/' . $pid . '/cmdline');
$env = (string)@file_get_contents('/proc/' . $pid . '/environ');
$children = trim((string)@file_get_contents('/proc/' . $pid
    . '/task/' . $pid . '/children'));
$hasWorkers = preg_match('/(?:^|\x00)PHP_CLI_SERVER_WORKERS=(\d+)(?:\x00|$)/',
    $env, $workers) === 1;
if (strpos($cmd, '127.0.0.1:18081') === false
    || strpos($cmd, $root . '/public') === false || $env === ''
    || $children !== ''
    || ($hasWorkers && (int)$workers[1] > 1)) {
    throw new RuntimeException('Fixed B single worker changed');
}
$portHex = strtoupper(str_pad(dechex(18081), 4, '0', STR_PAD_LEFT));
$inodes = [];
foreach (array_slice(@file('/proc/net/tcp', FILE_IGNORE_NEW_LINES) ?: [], 1) as $line) {
    $parts = preg_split('/\s+/', trim($line));
    if (is_array($parts) && ($parts[1] ?? '') === '0100007F:' . $portHex
        && ($parts[3] ?? '') === '0A') {
        $inodes[] = (string)($parts[9] ?? '');
    }
}
if (count($inodes) !== 1 || $inodes[0] === '') {
    throw new RuntimeException('B loopback listener inode ambiguous');
}
$owns = false;
foreach (glob('/proc/' . $pid . '/fd/*') ?: [] as $fd) {
    if (@readlink($fd) === 'socket:[' . $inodes[0] . ']') $owns = true;
}
if (!$owns) throw new RuntimeException('Fixed B PID does not own loopback listener');
$socket = @stream_socket_client('tcp://127.0.0.1:18081', $errno, $errstr, 5);
if (!is_resource($socket)) throw new RuntimeException('B loopback listener absent');
fclose($socket);
$boundary = json_decode((string)@file_get_contents($base . '/__poc_boundary',
    false, stream_context_create(['http' => ['timeout' => 5]])), true);
if (!is_array($boundary) || ($boundary['external_stubs'] ?? null) !== true
    || ($boundary['redis_isolated'] ?? null) !== true
    || ($boundary['real_queue'] ?? true) !== false) {
    throw new RuntimeException('B completion barrier failed');
}

$stage = 'code_local_development_mysql';
$runtime = (static function (string $path): array {
    $config = require $path;
    if (!is_array($config)) throw new RuntimeException('Runtime DB config invalid');
    return $config;
})($root . '/application/database.php');
$local = (static function (string $path) use ($runtime): array {
    $config = $runtime;
    $result = require $path;
    if (!is_array($result)) throw new RuntimeException('Local DB config invalid');
    return $result;
})('/workspace/zhct/zhctproject/store/application/config/local/database.php');
foreach (['type', 'hostname', 'hostport', 'database', 'username', 'password'] as $key) {
    if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
        throw new RuntimeException('B DB config differs from code local');
    }
}
$dsn = 'mysql:host=' . $runtime['hostname'] . ';port='
    . (int)$runtime['hostport'] . ';dbname=' . $runtime['database']
    . ';charset=' . (string)($runtime['charset'] ?? 'utf8');
$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]);
};
$identity = static function (PDO $db): bool {
    $value = (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
    return hash_equals(
        '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401',
        hash('sha256', $value));
};
$db = $connect();
if (!$identity($db)) throw new RuntimeException('B physical MySQL identity changed');
$stage = 'exact_owned_actor_and_no_relations';
$stmt = $db->prepare('SELECT ID,UUID,ACCOUNT,NAME,TYPE,CREATE_BY'
    . ' FROM ydy_user WHERE ID=? OR UUID=? OR ACCOUNT=?');
$stmt->execute([$actorId, $marker, $marker]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($rows) !== 1) throw new RuntimeException('Exact B actor count changed');
$actor = $rows[0];
if ((int)$actor['id'] !== $actorId
    || (string)$actor['uuid'] !== $marker
    || (string)$actor['account'] !== $marker
    || (string)$actor['name'] !== $marker
    || (int)$actor['type'] !== 0
    || (string)$actor['create_by'] !== 'CODEX') {
    throw new RuntimeException('Exact B actor row changed');
}
foreach (['ydy_user_role', 'ydy_user_data'] as $table) {
    $stmt = $db->prepare('SELECT COUNT(*) FROM ' . $table . ' WHERE USER_UUID=?');
    $stmt->execute([$marker]);
    if ((int)$stmt->fetchColumn() !== 0) {
        throw new RuntimeException('B actor unexpectedly linked');
    }
}
if ($mode === '--preflight') {
    echo json_encode(['status' => 'exact_b_actor_recovery_preflight_pass',
        'single_worker_barrier' => true, 'code_local_mysql' => true,
        'actor_rows' => 1, 'actor_relations' => 0,
        'business_http_sent' => false, 'db_dml' => false],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$stage = 'delete_one_owned_actor';
$db->beginTransaction();
try {
    $stmt = $db->prepare('DELETE FROM ydy_user WHERE ID=? AND UUID=?'
        . ' AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?');
    $stmt->execute([$actorId, $marker, $marker, $marker, 'CODEX']);
    if ($stmt->rowCount() !== 1) throw new RuntimeException('Owned actor delete not one');
    if (!$db->commit()) throw new RuntimeException('Actor cleanup commit uncertain');
} catch (Throwable $error) {
    if ($db->inTransaction()) $db->rollBack();
    throw $error;
}
$stage = 'independent_zero';
$verify = $connect();
if (!$identity($verify)) throw new RuntimeException('Independent B MySQL identity changed');
$stmt = $verify->prepare('SELECT COUNT(*) FROM ydy_user'
    . ' WHERE ID=? OR UUID=? OR ACCOUNT=?');
$stmt->execute([$actorId, $marker, $marker]);
if ((int)$stmt->fetchColumn() !== 0) {
    throw new RuntimeException('B actor remains after exact cleanup');
}
if (!unlink($journalPath) || !rmdir($dir)) {
    throw new RuntimeException('Private recovery journal removal failed');
}
echo json_encode(['status' => 'exact_b_actor_recovered',
    'actor_rows' => 0, 'user_role_rows' => 0, 'user_data_rows' => 0,
    'old_service_preserved' => true, 'journal_removed' => true,
    'business_http_sent' => false, 'deleted_only_owned_actor' => true],
    JSON_UNESCAPED_SLASHES), PHP_EOL;
