<?php
declare(strict_types=1);

// One isolated C Store/tree request. Default-deny; no raw person/department response leaves memory.
$action = (string)($argv[1] ?? '');
if ($action !== '--preflight' && $action !== '--execute') {
    echo json_encode(['status' => 'default_deny', 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
if ($action === '--execute'
    && ((string)getenv('CDX_TREE_DIAG_APPROVAL') !== 'ROOT_APPROVED_C_TREE_ONCE'
        || (string)getenv('CDX_TREE_DIAG_SHA') !== hash_file('sha256', __FILE__))) {
    echo json_encode(['status' => 'default_deny', 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
if ((string)getenv('CDX_NEXT5_LANE') !== 'C') throw new RuntimeException('C only');
$preflight = is_file(__DIR__ . '/preflight_next5.php')
    ? __DIR__ . '/preflight_next5.php'
    : __DIR__ . '/../parallel-store-next5-readonly/preflight_next5.php';
if (!is_file($preflight) || is_link($preflight)
    || hash_file('sha256', $preflight)
        !== 'e8e94b6cd6960ba2e8f1420c8d2520fb36ed8c41d8b928d047824c621a4c6c3e') {
    throw new RuntimeException('Frozen preflight unavailable');
}
$argv[1] = '--preflight';
require $preflight; // Fixed C PID/docroot/config/stub/DM identity/natural leaf source.
$argv[1] = $action;
$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]);
};
$rows = static function (PDO $pdo, string $sql, array $args = [], int $cap = 3000): array {
    $statement = $pdo->prepare($sql);
    $statement->execute($args);
    $output = [];
    while (($raw = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
        if (count($output) >= $cap) throw new RuntimeException('Source cap exceeded');
        $row = [];
        foreach (array_change_key_case($raw, CASE_LOWER) as $key => $value) {
            $row[$key] = is_resource($value) ? stream_get_contents($value) : $value;
        }
        ksort($row, SORT_STRING);
        $output[] = $row;
    }
    return $output;
};
$scalar = static function (PDO $pdo, string $sql, array $args = []) {
    $statement = $pdo->prepare($sql);
    $statement->execute($args);
    return $statement->fetchColumn();
};
$sameIdentity = static function (PDO $pdo): bool {
    $schema = (string)$pdo->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
        ->fetchColumn();
    return hash_equals('bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194',
        hash('sha256', 'ZHCT_APP|5237|' . $schema));
};
$source = static function (PDO $pdo) use ($rows): array {
    $all = $rows($pdo, 'SELECT * FROM YDY_DEPARTMENT');
    $hashRows = [];
    $map = [];
    foreach ($all as $row) {
        $hashRows[] = serialize($row);
        if ((int)$row['status'] === 1 && (int)$row['is_show'] === 1) {
            $map[(string)$row['uuid']] = $row;
        }
    }
    sort($hashRows, SORT_STRING);
    return [$map, count($all), hash('sha256', implode("\n", $hashRows))];
};
$queue = static function (PDO $pdo) use ($rows): array {
    $item = $rows($pdo, 'SELECT COUNT(*) AS TOTAL,'
        . ' SUM(CASE WHEN STATUS=0 THEN 1 ELSE 0 END) AS PENDING FROM YDY_EXPORT_LOG', [], 2)[0];
    return [(int)$item['total'], (int)$item['pending']];
};
$db = $connect();
if (!$sameIdentity($db)) throw new RuntimeException('DB identity mismatch');
[$expected, $sourceCount, $sourceHash] = $source($db);
$queueBefore = $queue($db);
$oldActors = (int)$scalar($db, "SELECT COUNT(*) FROM YDY_USER WHERE ACCOUNT LIKE 'CDXN5%'");
if ($sourceCount !== 26 || count($expected) !== 26 || $queueBefore[1] !== 0
    || $oldActors !== 0) throw new RuntimeException('C source/queue/actor preflight drift');
if ($action === '--preflight') {
    echo json_encode(['status' => 'preflight_pass', 'source_rows' => $sourceCount,
        'active_rows' => count($expected), 'source_sha' => $sourceHash,
        'queue' => $queueBefore, 'old_actor' => 0, 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(0);
}

umask(0077);
$marker = 'CDXN5D' . strtoupper(bin2hex(random_bytes(5)));
if ((int)$scalar($db, 'SELECT COUNT(*) FROM YDY_USER WHERE UUID=? OR ACCOUNT=?',
        [$marker, $marker]) !== 0) throw new RuntimeException('Marker collision');
$dir = sys_get_temp_dir() . '/cdx-next5-tree-' . $marker;
if (file_exists($dir) || !mkdir($dir, 0700)) throw new RuntimeException('Journal unavailable');
$journal = $dir . '/run.json';
$state = ['marker' => $marker, 'state' => 'before_write'];
$save = static function () use (&$state, $journal): void {
    if (file_put_contents($journal, json_encode($state)) === false) {
        throw new RuntimeException('Journal write failed');
    }
    chmod($journal, 0600);
};
$save();
$actorId = 0;
$httpSent = false;
$httpConfirmed = true;
$diagnosis = ['kind' => 'unreached'];
$error = '';
$cleaned = false;
try {
    $statement = $db->prepare('INSERT INTO YDY_USER'
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $statement->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    if ($statement->rowCount() !== 1) throw new RuntimeException('Actor insert failed');
    $actorId = (int)$scalar($db, 'SELECT ID FROM YDY_USER'
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?',
        [$marker, $marker, 'CODEX']);
    if ($actorId <= 0) throw new RuntimeException('Actor ID missing');
    $state['state'] = 'actor_committed';
    $state['actor_id'] = $actorId;
    $save();
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$actorId, $marker, $marker, '0', '', '', $marker]));
    if (!is_string($token) || $token === '') throw new RuntimeException('Token unavailable');
    $httpSent = true;
    $httpConfirmed = false;
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: " . $token . "\r\n",
        'content' => '', 'timeout' => 12, 'ignore_errors' => true,
        'follow_location' => 0, 'max_redirects' => 0,
    ]]);
    $stream = @fopen('http://127.0.0.1:18080/p/staff/tree', 'rb', false, $context);
    $body = is_resource($stream) ? stream_get_contents($stream, 2097153) : false;
    $meta = is_resource($stream) ? stream_get_meta_data($stream) : [];
    if (is_resource($stream)) fclose($stream);
    if (!is_string($body) || strlen($body) > 2097152 || !empty($meta['timed_out'])) {
        throw new RuntimeException('HTTP outcome unknown');
    }
    $httpConfirmed = true;
    $headers = $http_response_header ?? [];
    if (strpos((string)($headers[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('HTTP status mismatch');
    }
    $json = json_decode($body, true);
    if (!is_array($json) || (int)($json['code'] ?? -1) !== 0
        || !isset($json['data']['department']) || !is_array($json['data']['department'])) {
        throw new RuntimeException('Business envelope mismatch');
    }
    $seen = [];
    $walk = static function (array $nodes) use (&$walk, &$seen): void {
        foreach ($nodes as $node) {
            if (!is_array($node)) throw new RuntimeException('Tree node type mismatch');
            $seen[] = $node;
            if (isset($node['children'])) $walk($node['children']);
        }
    };
    $walk($json['data']['department']);
    $diagnosis = ['kind' => 'none', 'field' => '', 'response_type' => '',
        'source_type' => '', 'response_sha12' => '', 'source_sha12' => '',
        'response_rows' => count($seen), 'source_rows' => count($expected)];
    foreach ($seen as $node) {
        $uuid = (string)($node['uuid'] ?? '');
        if (!isset($expected[$uuid])) {
            $diagnosis['kind'] = 'missing_source_key';
            $diagnosis['field'] = 'uuid';
            $diagnosis['response_type'] = gettype($node['uuid'] ?? null);
            break;
        }
        foreach (['name', 'puuid'] as $field) {
            $value = $node[$field] ?? null;
            $sourceValue = $expected[$uuid][$field] ?? null;
            if ((string)$value !== (string)$sourceValue) {
                $diagnosis['kind'] = 'value_mismatch';
                $diagnosis['field'] = $field;
                $diagnosis['response_type'] = gettype($value);
                $diagnosis['source_type'] = gettype($sourceValue);
                $diagnosis['response_sha12'] = substr(hash('sha256', (string)$value), 0, 12);
                $diagnosis['source_sha12'] = substr(hash('sha256', (string)$sourceValue), 0, 12);
                break 2;
            }
        }
    }
    $fresh = $connect();
    if (!$sameIdentity($fresh) || $source($fresh)[2] !== $sourceHash
        || $queue($fresh) !== $queueBefore) {
        throw new RuntimeException('Source/queue changed');
    }
    $state['state'] = 'http_confirmed';
    $save();
} catch (Throwable $exception) {
    $error = get_class($exception) . ':' . hash('sha256', $exception->getMessage());
} finally {
    if (!$httpSent || $httpConfirmed) {
        try {
            $cleanup = $connect();
            if (!$sameIdentity($cleanup)) throw new RuntimeException('Cleanup identity mismatch');
            $owners = $rows($cleanup, 'SELECT ID,UUID,ACCOUNT,NAME,TYPE,CREATE_BY'
                . ' FROM YDY_USER WHERE UUID=? OR ACCOUNT=?', [$marker, $marker], 2);
            if (count($owners) !== 1 || (int)$owners[0]['id'] !== $actorId
                || (string)$owners[0]['uuid'] !== $marker
                || (string)$owners[0]['account'] !== $marker
                || (string)$owners[0]['name'] !== $marker
                || (int)$owners[0]['type'] !== 0
                || (string)$owners[0]['create_by'] !== 'CODEX') {
                throw new RuntimeException('Actor ownership changed');
            }
            foreach (['YDY_USER_ROLE', 'YDY_USER_DATA'] as $relation) {
                if ((int)$scalar($cleanup, 'SELECT COUNT(*) FROM ' . $relation
                    . ' WHERE USER_UUID=?', [$marker]) !== 0) {
                    throw new RuntimeException('Actor relation appeared');
                }
            }
            $cleanup->beginTransaction();
            $delete = $cleanup->prepare('DELETE FROM YDY_USER WHERE ID=? AND UUID=?'
                . ' AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?');
            $delete->execute([$actorId, $marker, $marker, $marker, 'CODEX']);
            if ($delete->rowCount() !== 1) throw new RuntimeException('Actor delete count mismatch');
            $cleanup->commit();
            $zero = $connect();
            if (!$sameIdentity($zero)
                || (int)$scalar($zero, 'SELECT COUNT(*) FROM YDY_USER WHERE UUID=? OR ACCOUNT=?',
                    [$marker, $marker]) !== 0) {
                throw new RuntimeException('Independent actor zero failed');
            }
            $cleaned = true;
            unlink($journal);
            rmdir($dir);
        } catch (Throwable $exception) {
            $error .= ';cleanup:' . hash('sha256', $exception->getMessage());
        }
    } else {
        $error .= ';unknown_http_keep_journal';
    }
}
echo json_encode(['status' => $error === '' ? 'diagnosed' : 'failed',
    'diagnosis' => $diagnosis, 'source_count' => $sourceCount,
    'source_sha' => $sourceHash, 'queue_before' => $queueBefore,
    'actor_independent_zero' => $cleaned, 'journal_removed' => $cleaned,
    'failure' => $error], JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($error !== '' || !$cleaned) exit(1);
