<?php

namespace App\Services\Terminal;

use App\Models\Host;
use App\Models\Operator;
use App\Services\Secrets\SecretVault;
use RuntimeException;

/**
 * Die Eintrittskarte für eine Terminalsitzung AUF EINEM HOST — und die einzige
 * Stelle, die weiß, was für einen Host darin steht.
 *
 * Wo sie liegt, wie lange und in welcher Form, steht seit dem zweiten
 * Ausstellungsweg in {@see TicketStore}. Hier bleibt allein die Frage, die nur
 * ein Host stellt: welche Tunneladresse, welcher Fingerabdruck, und wann es
 * beides noch gar nicht gibt.
 */
final class TerminalTicket
{
    /**
     * Steht weiter hier, weil Aufrufer und Tests danach fragen — der Wert
     * selbst gehört dem Speicher, nicht diesem Weg.
     */
    public const TTL_SECONDS = TicketStore::TTL_SECONDS;

    /**
     * Was diesem Host gerade im Weg steht — oder null, wenn nichts im Weg steht.
     *
     * Es gibt diese Methode, weil dieselbe Frage an zwei Stellen gestellt wird
     * und nur eine davon eine Ausnahme gebrauchen kann. `issue()` unten muss
     * scheitern: es ist die letzte Instanz vor dem Ticket. Die Seite dagegen
     * will die Antwort VORHER wissen, um sie in ihrer eigenen Sprache
     * hinzuschreiben, statt sie als 500er an den Betreiber weiterzureichen.
     *
     * Zurück kommt ein Merkwort, kein Satz: die Formulierung gehört in die
     * Sprachdateien, nicht in eine Ausnahme. `null` heißt „nichts im Weg".
     */
    public static function blocker(Host $host): ?string
    {
        // Ohne Schlüssel kann die Brücke sich nicht anmelden, und der Betreiber
        // sähe nur einen Vorspann, der nie weicht.
        if (blank((string) app(SecretVault::class)->get('ssh.private_key'))) {
            return 'no_key';
        }

        // Derselbe Gedanke, für die beiden anderen Felder, ohne die keine
        // sichere Sitzung entstehen kann. Eine fehlende Tunneladresse ergibt
        // bloß ein Fenster, das nie verbindet — ärgerlich, aber harmlos. Ein
        // fehlender Fingerabdruck ist das Gegenteil: er übergäbe die Prüfung an
        // Code, der noch gar nicht existiert, und dessen naheliegendste Fassung
        // ("kein Fingerabdruck im Ticket → nicht prüfen") eine ungepinnte
        // Root-SSH-Sitzung im Tunnel wäre — genau das, was die Spec mit
        // „Fingerabdruck geprüft" ausdrücklich ausschließt. Die Prüfung gehört
        // hierher, wo der Host ohnehin schon feststeht, nicht in einen
        // Container, der dem Ticket nur noch glauben kann.
        if (blank($host->wg_ip) || blank($host->ssh_host_key)) {
            return 'not_ready';
        }

        return null;
    }

    public static function issue(Host $host, Operator $for): string
    {
        // Die Prüfung steht in `blocker()`; hier bleibt nur, sie ernst zu
        // nehmen. Der Wortlaut ist der von früher — er steht in Logs und in
        // den Tests aus Aufgabe 1.
        match (self::blocker($host)) {
            'no_key' => throw new RuntimeException('Kein SSH-Schlüssel hinterlegt — ohne ihn kann keine Terminalsitzung entstehen.'),
            'not_ready' => throw new RuntimeException("Host {$host->name} hat keine Tunneladresse oder keinen geprüften Fingerabdruck hinterlegt — ohne beides kein Ticket."),
            default => null,
        };

        $key = (string) app(SecretVault::class)->get('ssh.private_key');

        return TicketStore::issue([
            'operator_id' => $for->id,
            'host_uuid' => $host->uuid,
            // Die Tunneladresse. Die öffentliche IP wäre der Weg, den
            // SecureHostFirewall ausdrücklich zumacht.
            'ip' => $host->wg_ip,
            'user' => 'root',
            'private_key' => $key,
            'fingerprint' => $host->ssh_host_key,
        ]);
    }

    /**
     * `ip` und `fingerprint` sind hier nicht optional: issue() weist beide
     * zurück, bevor überhaupt etwas in Redis landet.
     *
     * @return array{operator_id: int, host_uuid: string, ip: string, user: string, private_key: string, fingerprint: string}|null
     */
    public static function redeem(string $ticket): ?array
    {
        /** @var array{operator_id: int, host_uuid: string, ip: string, user: string, private_key: string, fingerprint: string}|null */
        return TicketStore::redeem($ticket);
    }
}
