<?php

use App\Livewire\Admin\Finance;
use App\Models\ExportTarget;
use Illuminate\Support\Facades\File;
use Livewire\Livewire;

/**
 * Minting the key a NAS collects the archive with, from the console.
 *
 * Everything it replaces was done by hand across three machines: generate a
 * keypair, install the public half restricted to one directory, carry the
 * private half over. None of it can happen in the panel — the keys, the home
 * directory and rrsync are all on the host, and the panel is www-data inside a
 * container. So it asks, exactly as it does for an update.
 */
beforeEach(function () {
    File::deleteDirectory(storage_path('app/deploy'));

    $this->target = ExportTarget::create([
        'name' => 'Übergabe', 'driver' => ExportTarget::LOCAL,
        'path' => '/opt/clupilot/storage/archive', 'layout' => ExportTarget::BY_DAY, 'active' => true,
    ]);
});

it('asks the host for a key, and names the directory it may read', function () {
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Finance::class)
        ->call('createPullAccess', $this->target->uuid)
        ->assertSet('waitingForKey', true);

    $request = json_decode(File::get(storage_path('app/deploy/update-request.json')), true);

    expect($request['kind'])->toBe('archive-key')
        // The path travels with the request: the panel is the only side that
        // knows which destination the button was pressed on.
        ->and($request['archive_path'])->toBe('/opt/clupilot/storage/archive');
});

it('refuses for a destination that is not on this host', function () {
    // Only a directory here has a home directory and an authorized_keys to put
    // anything into. Anywhere else is somebody else's machine.
    $remote = ExportTarget::create([
        'name' => 'Anbieter', 'driver' => ExportTarget::SFTP, 'path' => '/rechnungen',
        'host' => 'backup.example.com', 'port' => 22, 'username' => 'u1', 'active' => true,
    ]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Finance::class)
        ->call('createPullAccess', $remote->uuid)
        ->assertSet('waitingForKey', false);

    expect(File::exists(storage_path('app/deploy/update-request.json')))->toBeFalse();
});

it('shows the minted key once and takes it off the disk in the same breath', function () {
    // A private key that stays on disk after somebody has seen it is a private
    // key on disk.
    File::ensureDirectoryExists(storage_path('app/deploy'));
    File::put(storage_path('app/deploy/archive-key.json'), json_encode([
        'created_at' => now()->toIso8601String(),
        'label' => 'clupilot-archiv',
        'path' => '/opt/clupilot/storage/archive',
        'private_key' => "-----BEGIN OPENSSH PRIVATE KEY-----\nAAAA\n-----END OPENSSH PRIVATE KEY-----",
    ]));

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Finance::class)
        ->set('waitingForKey', true)
        ->call('collectArchiveKey')
        ->assertSet('waitingForKey', false)
        ->assertSee('BEGIN OPENSSH PRIVATE KEY');

    expect(File::exists(storage_path('app/deploy/archive-key.json')))->toBeFalse();
});

it('never writes the key into the database', function () {
    // It is shown to be copied into a NAS and nowhere else. Storing it "for
    // convenience" would put a working credential into every backup of this
    // database.
    File::ensureDirectoryExists(storage_path('app/deploy'));
    File::put(storage_path('app/deploy/archive-key.json'), json_encode([
        'created_at' => now()->toIso8601String(), 'label' => 'x',
        'path' => '/opt/clupilot/storage/archive', 'private_key' => 'GEHEIM-XYZ',
    ]));

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Finance::class)
        ->set('waitingForKey', true)
        ->call('collectArchiveKey');

    foreach (ExportTarget::all() as $target) {
        expect(json_encode($target->getAttributes()))->not->toContain('GEHEIM-XYZ');
    }

    expect(json_encode(ExportTarget::query()->get()->toArray()))->not->toContain('GEHEIM-XYZ');
});

it('locks the key to one directory, read-only, with no shell', function () {
    // The agent's half, which no PHP test can reach. rrsync rather than pinning
    // the exact rsync option string: that string differs between rsync versions
    // and breaks silently, as a refusal with no reason given.
    $agent = File::get(base_path('deploy/update-agent.sh'));

    expect($agent)->toContain('archive-key')
        ->and($agent)->toContain('/usr/bin/rrsync -ro')
        ->and($agent)->toContain(',restrict ')
        // Refuses rather than issuing an UNrestricted key when rrsync is absent.
        ->and($agent)->toContain('rrsync_missing');

    // And the installer puts rsync on the host, because sshd starts
    // `rsync --server` HERE when the NAS connects.
    expect(File::get(base_path('deploy/install-agent.sh')))->toContain('install -y -qq rsync');
});
