<?php

namespace App\Services\Terminal;

use App\Models\Operator;
use App\Services\Secrets\SecretVault;
use App\Services\Ssh\Keypair;
use phpseclib3\Crypt\PublicKeyLoader;
use Throwable;

/**
 * Die SSH-Kennung, mit der das Terminal auf den CluPilot-Server selbst kommt.
 *
 * Bewusst eine eigene, nicht der Flottenschlüssel: dessen öffentliche Hälfte
 * liegt auf JEDEM übernommenen Host in `/root/.ssh/authorized_keys`. Ein
 * Schlüssel für beides hieße, dass ein Host, der einmal in falsche Hände gerät,
 * dieselbe Kennung trägt wie die Tür zur Steuerungsebene.
 *
 * Nur die private Hälfte wird aufbewahrt. Die öffentliche wird daraus
 * abgeleitet, wann immer sie gebraucht wird — zwei gespeicherte Hälften können
 * auseinanderlaufen, eine abgeleitete nie.
 */
final class ServerIdentity
{
    /** Der Tresor-Eintrag. Steht hier, nicht verstreut bei den Aufrufern. */
    public const VAULT_KEY = 'server.private_key';

    public static function exists(): bool
    {
        return filled(app(SecretVault::class)->get(self::VAULT_KEY));
    }

    /**
     * Erzeugt eine neue Kennung und ersetzt eine bestehende.
     *
     * Ersetzen sperrt aus: die alte öffentliche Hälfte steht auf dem Wirt in
     * `authorized_keys` und wird von hier aus nicht angefasst. Zwischen dem
     * Erzeugen und dem nächsten `install-agent.sh` erreicht das Terminal den
     * Server nicht. Deshalb hängt an dieser Methode eine Rückfrage (R23), und
     * deshalb sagt die Einstellungsseite danach, was noch zu tun ist.
     */
    public static function generate(Operator $by): void
    {
        app(SecretVault::class)->put(self::VAULT_KEY, Keypair::generate()->privateKey, $by);
    }

    /**
     * Die öffentliche Hälfte als eine Zeile `ssh-ed25519 AAAA… clupilot`, oder
     * null, wenn es keine Kennung gibt.
     *
     * Genau diese Zeile holt sich der root-eigene Schritt auf dem Wirt über
     * `php artisan clupilot:server-terminal-key`. Sie ist öffentlich — das
     * Kommando gibt nichts preis, was nicht ohnehin in einer authorized_keys
     * stünde.
     */
    public static function publicKey(): ?string
    {
        $private = (string) app(SecretVault::class)->get(self::VAULT_KEY);

        if (blank($private)) {
            return null;
        }

        try {
            return PublicKeyLoader::load($private)
                ->getPublicKey()
                ->toString('OpenSSH', ['comment' => Keypair::COMMENT]);
        } catch (Throwable) {
            // Unlesbar gespeichert heißt „keine Kennung". Nicht werfen: die
            // Aufrufer sind ein Kommando, das eine Zeile ausgeben soll, und
            // eine Seite, die nie mit einer Fehlerseite aufgehen darf.
            return null;
        }
    }
}
