<?php

use App\Livewire\Admin\ConfirmGenerateSshKey;
use App\Livewire\Admin\Integrations;
use App\Services\Secrets\SecretVault;
use App\Services\Ssh\Keypair;
use App\Support\ProvisioningSettings;
use Livewire\Livewire;
use phpseclib3\Crypt\EC;
use phpseclib3\Crypt\PublicKeyLoader;

/**
 * Der Tresor hielt `ssh.private_key` write-only und hatte keinen Erzeuger: das
 * Paar musste in einer Shell entstehen und von Hand hineinkopiert werden. Eine
 * Bereitschaftsseite, die etwas bemängelt, das nur eine Shell beheben kann,
 * hat die Arbeit verschoben statt erledigt.
 */
beforeEach(function () {
    config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)));
});

it('mints a key the SSH client itself can load', function () {
    // Die eigentliche Zusicherung, und der Grund für phpseclib statt eines
    // selbstgeschriebenen OpenSSH-Rahmens: PhpseclibRemoteShell::connectWithKey()
    // reicht den gespeicherten Text an genau dieses PublicKeyLoader::load()
    // weiter. Was diese Prüfung lädt, ist damit dasselbe, was sich später an
    // einem Host anmeldet — ein Format, das nur „nach OpenSSH aussieht", fiele
    // erst bei der ersten Host-Übernahme auf.
    $pair = Keypair::generate();

    $loaded = PublicKeyLoader::load($pair->privateKey);

    expect($loaded)->toBeInstanceOf(EC\PrivateKey::class)
        ->and($loaded->getCurve())->toBe('Ed25519');
});

it('publishes a public half that is one authorized_keys line and belongs to the private half', function () {
    // EstablishSshTrust hängt genau diesen String mit `echo … >> authorized_keys`
    // an. Ein Zeilenumbruch darin schriebe zwei kaputte Zeilen; ein öffentlicher
    // Teil, der nicht zum privaten gehört, ergäbe eine Übernahme, die beim
    // Schlüssel-Login scheitert, nachdem das Passwort schon verworfen wurde.
    $pair = Keypair::generate();

    expect($pair->publicKey)->toStartWith('ssh-ed25519 ')
        ->and($pair->publicKey)->not->toContain("\n")
        ->and(PublicKeyLoader::load($pair->privateKey)->getPublicKey()->toString('OpenSSH', ['comment' => 'clupilot']))
        ->toBe($pair->publicKey);
});

it('never mints the same key twice', function () {
    expect(Keypair::generate()->privateKey)->not->toBe(Keypair::generate()->privateKey);
});

it('stores the private half in the vault and publishes only the public half', function () {
    $owner = operator('Owner');

    $page = Livewire::actingAs($owner, 'operator')
        ->test(Integrations::class)
        ->set('confirmablePassword', 'password')
        ->call('confirmPassword')
        ->call('generateSshKey')
        ->assertHasNoErrors();

    $private = (string) app(SecretVault::class)->get('ssh.private_key');
    $public = ProvisioningSettings::sshPublicKey();

    expect($private)->toContain('BEGIN OPENSSH PRIVATE KEY')
        ->and($public)->toStartWith('ssh-ed25519 ')
        // Das Feld auf der Seite zeigt den öffentlichen Teil, damit der
        // Betreiber ihn ohne Umweg auf einen Host legen kann.
        ->and($page->get('sshPublicKey'))->toBe($public)
        ->and(PublicKeyLoader::load($private)->getPublicKey()->toString('OpenSSH', ['comment' => 'clupilot']))
        ->toBe($public);
});

it('never lets the generated private half reach the page', function () {
    // Derselbe Maßstab wie für jeden anderen Tresorwert: gespeichert wird er,
    // angezeigt nie. Ein erzeugter Schlüssel ist dabei der heikelste Fall — er
    // entsteht in derselben Anfrage, die die Antwort schreibt.
    $owner = operator('Owner');

    $page = Livewire::actingAs($owner, 'operator')
        ->test(Integrations::class)
        ->set('confirmablePassword', 'password')
        ->call('confirmPassword')
        ->call('generateSshKey');

    $private = (string) app(SecretVault::class)->get('ssh.private_key');
    $body = json_encode($page->snapshot).$page->html();

    expect($body)->not->toContain('BEGIN OPENSSH PRIVATE KEY');

    foreach (array_slice(explode("\n", trim($private)), 1, -1) as $zeile) {
        expect($body)->not->toContain(trim($zeile));
    }
});

it('refuses to generate without a confirmed password', function () {
    // Dieselbe Sperre wie Speichern und Vergessen: das Erzeugen ÜBERSCHREIBT
    // den Schlüssel, mit dem diese Anwendung jeden Host erreicht.
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Integrations::class)
        ->call('generateSshKey')
        ->assertForbidden();
});

it('refuses to generate without the capability', function () {
    Livewire::actingAs(operator('Admin'), 'operator')
        ->test(Integrations::class)
        ->call('generateSshKey')
        ->assertForbidden();
});

it('confirms in a modal before replacing the identity every host trusts', function () {
    // R23, und hier mit echten Folgen: der bisherige öffentliche Teil bleibt in
    // den authorized_keys jedes übernommenen Hosts stehen, der neue steht dort
    // noch nicht. Zwischen Erzeugen und Ausrollen erreicht die Konsole keine
    // einzige Maschine mehr.
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(ConfirmGenerateSshKey::class)
        ->call('confirm')
        ->assertDispatched('ssh-key-generate-confirmed');
});
