<?php

namespace App\Provisioning\Steps\Host;

use App\Models\ProvisioningRun;
use App\Provisioning\StepResult;
use App\Services\Ssh\RemoteShell;
use App\Support\ProvisioningSettings;
use Illuminate\Support\Facades\Crypt;

/**
 * First login with the one-time root password, deploy CluPilot's SSH key, pin
 * the host key, verify key login, then scrub the password from the run context.
 */
class EstablishSshTrust extends HostStep
{
    public function __construct(private RemoteShell $shell) {}

    public function key(): string
    {
        return 'establish_ssh_trust';
    }

    public function execute(ProvisioningRun $run): StepResult
    {
        $host = $this->host($run);

        // Idempotent: already trusted (host key pinned) and password already scrubbed.
        if (filled($host->ssh_host_key) && blank($run->context('root_password'))) {
            return StepResult::advance();
        }

        $publicKey = trim(ProvisioningSettings::sshPublicKey());
        $password = Crypt::decryptString((string) $run->context('root_password'));

        $this->shell->connectWithPassword($host->public_ip, 'root', $password);
        $this->shell->run('mkdir -p /root/.ssh && chmod 700 /root/.ssh');
        $this->shell->run(
            'grep -qxF '.escapeshellarg($publicKey).' /root/.ssh/authorized_keys 2>/dev/null '.
            '|| echo '.escapeshellarg($publicKey).' >> /root/.ssh/authorized_keys'
        );

        $host->update(['ssh_host_key' => $this->shell->hostKeyFingerprint()]);

        // Verify the key works, then remove the password from state.
        $this->keyLogin($this->shell, $host);
        $run->forgetContext('root_password');

        $this->refusePasswordLogins();

        return StepResult::advance();
    }

    /**
     * Ab hier kommt man auf diese Maschine nur noch mit dem Schlüssel.
     *
     * Bis hierher blieb die Anmeldung als root MIT PASSWORT erlaubt — und Port
     * 22 steht während der ganzen Übernahme offen im Internet, denn die
     * Host-Firewall macht ihn erst als vorletzter Schritt zu. Zwischen diesem
     * Schritt und jenem lag also ein Fenster von Stunden, in dem jede Maschine
     * der Welt Root-Passwörter durchprobieren durfte. Und wer später einmal das
     * Notfallskript benutzt, reisst dieses Fenster wieder auf.
     *
     * Der richtige Zeitpunkt ist GENAU HIER: eine Zeile weiter oben hat sich
     * `keyLogin()` erfolgreich mit dem Schlüssel angemeldet. Wir wissen also,
     * dass der Weg hinein steht, bevor wir den anderen zumachen.
     *
     * `reload`, nicht `restart`: bestehende Sitzungen bleiben, auch die, über
     * die wir hier gerade reden. Und `sshd -t` davor — eine Konfiguration, die
     * sshd nicht laden kann, dürfte er nie zu sehen bekommen.
     *
     * Fehlschläge sind ausdrücklich KEIN Grund, die Übernahme abzubrechen: der
     * Schlüssel liegt dann trotzdem, die Maschine ist benutzbar, und die
     * Firewall macht Port 22 weiter unten ohnehin zu. Eine Härtung, die einen
     * ganzen Aufbau scheitern lässt, wird beim nächsten Mal weggelassen.
     */
    private function refusePasswordLogins(): void
    {
        // Eigene Datei unter sshd_config.d, nicht die Hauptdatei bearbeiten:
        // Debians sshd_config zieht dieses Verzeichnis in der ERSTEN Zeile
        // herein, und bei sshd gewinnt der erste Treffer. Was hier steht,
        // schlägt damit alles, was weiter unten in der Hauptdatei steht — ohne
        // dass wir eine fremde Datei umschreiben, die das nächste Systemupdate
        // wieder ersetzt.
        $this->shell->run('install -d -m 755 /etc/ssh/sshd_config.d');
        $this->shell->putFile('/etc/ssh/sshd_config.d/99-clupilot.conf', <<<'SSHD'
# Geschrieben von CluPilot (EstablishSshTrust). Nicht von Hand ändern —
# der nächste Lauf dieses Schrittes überschreibt die Datei.
#
# Auf diese Maschine kommt man mit dem Flottenschlüssel. Ein Passwort ist ab
# hier keine Eintrittskarte mehr, auch nicht für root. Der Weg zurück, wenn der
# Schlüssel je verlorengeht, ist die Konsole des Anbieters — nicht ein
# Passwort, das jemand raten kann.
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password

SSHD);

        $this->shell->run('sshd -t && { systemctl reload ssh || systemctl reload sshd; }');
    }
}
