<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/linked_update_lib.php';

function update_test_assert(bool $condition, string $message): void
{
    if (!$condition) throw new RuntimeException($message);
}

function update_test_rejected(callable $operation, string $message): void
{
    try {
        $operation();
    } catch (Throwable $e) {
        return;
    }
    throw new RuntimeException($message);
}

update_test_assert(ua_update_safe_relative_path('admin/assets/app.js') === 'admin/assets/app.js', 'A normal application path must be accepted.');
foreach (['../install_config.php', '/windows/system.ini', 'C:/secret.txt', 'install_config.php', 'admin/config.php', 'data/private.bin', 'runtime/private.bin'] as $unsafe) {
    update_test_assert(ua_update_safe_relative_path($unsafe) === '', 'Unsafe update path was accepted: ' . $unsafe);
}

$payload = "<?php echo 'updated';\n";
$zipPath = tempnam(sys_get_temp_dir(), 'ua-update-test-');
if (!is_string($zipPath) || $zipPath === '') throw new RuntimeException('Unable to allocate the update test archive.');

$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::OVERWRITE) !== true) throw new RuntimeException('Unable to create the update test archive.');
$zip->addFromString('payload/admin/sample.php', $payload);
$zip->close();

try {
    $zip = new ZipArchive();
    if ($zip->open($zipPath) !== true) throw new RuntimeException('Unable to reopen the update test archive.');
    $manifest = [
        'release_id' => 'audit-1.3.1',
        'version' => '1.3.1',
        'min_version' => '1.3.0',
        'roles' => ['branch', 'agent'],
        'restart_required' => true,
        'files' => [[
            'path' => 'admin/sample.php',
            'source' => 'payload/admin/sample.php',
            'size' => strlen($payload),
            'sha256' => hash('sha256', $payload),
        ]],
        'delete_files' => ['admin/obsolete.php'],
    ];
    $validated = ua_update_validate_manifest($manifest, $zip);
    update_test_assert($validated['files'][0]['path'] === 'admin/sample.php', 'A valid changed application file must survive validation.');
    update_test_assert($validated['delete_files'] === ['admin/obsolete.php'], 'A safe delete target must survive validation.');

    $badHash = $manifest;
    $badHash['files'][0]['sha256'] = str_repeat('0', 64);
    update_test_rejected(static fn() => ua_update_validate_manifest($badHash, $zip), 'A payload hash mismatch must be rejected.');

    $badTarget = $manifest;
    $badTarget['files'][0]['path'] = '../install_config.php';
    update_test_rejected(static fn() => ua_update_validate_manifest($badTarget, $zip), 'A traversal target must be rejected.');

    $protectedDelete = $manifest;
    $protectedDelete['delete_files'] = ['install_config.php'];
    update_test_rejected(static fn() => ua_update_validate_manifest($protectedDelete, $zip), 'Deleting device configuration must be rejected.');
    $zip->close();
} finally {
    @unlink($zipPath);
}

echo "linked_update_manifest_test: OK\n";
