<?php // tests/Feature/Provisioning/ConfigureInstanceMailTest.php

use App\Models\Host;
use App\Models\Instance;
use App\Models\Mailbox;
use App\Models\ProvisioningRun;
use App\Provisioning\Steps\Customer\ConfigureInstanceMail;
use App\Services\Proxmox\FakeProxmoxClient;
use App\Services\Proxmox\ProxmoxClient;
use App\Support\Settings;
use Illuminate\Support\Facades\Log;

function laufMitInstanz(Instance $instance): ProvisioningRun
{
    return ProvisioningRun::factory()->create([
        'pipeline' => 'instance-mail',
        'context' => ['instance_id' => $instance->id, 'node' => 'pve', 'vmid' => 201],
    ]);
}

/**
 * Eine Instanz, die tatsächlich auf einem Host steht.
 *
 * Der Schritt ruft ProxmoxClient::forHost($instance->host) auf — genau wie
 * ApplyStorageQuota, dessen Testaufbau (ApplyStorageQuotasTest::unquotedInstance())
 * aus demselben Grund einen Host anlegt. Eine Instanz ohne host_id gibt es in
 * diesem Bestand für eine Maschine, die tatsächlich Gastbefehle bekommt,
 * nicht — FakeProxmoxClient::forHost() verlangt ein echtes Host-Objekt, weil
 * der reale Client genauso wenig ohne einen Host wüsste, wohin er soll.
 */
function instanzAufHost(): Instance
{
    return Instance::factory()->create(['host_id' => Host::factory()]);
}

function versandbereitFuerSchritt(): void
{
    Settings::set('mail.host', 'mail.clupilot.cloud');
    Settings::set('mail.port', 587);
    Settings::set('mail.encryption', 'tls');
    // Die Zeile legt eine eigene Wanderung an — inaktiv und ohne
    // Zugangsdaten. Ausgefüllt wird sie hier, so wie der Betreiber es in der
    // Konsole täte; ein factory()->create() mit demselben Schlüssel liefe in
    // die Eindeutigkeit der Spalte.
    Mailbox::findByKey('instance-relay')->update([
        'address' => 'noreply@clupilot.cloud',
        'username' => 'noreply@clupilot.cloud', 'password' => 'geheim',
        'active' => true, 'authenticates' => true,
    ]);
}

it('trägt jeden Wert einzeln in den Gast', function () {
    versandbereitFuerSchritt();
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = instanzAufHost();

    app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));

    $befehle = implode("\n", $pve->guestCommands);

    // Alle acht Werte aus GuestMailConfig::values(), nicht nur zwei — ein
    // vergessener Schlüssel soll hier auffallen, nicht erst beim Kunden.
    expect($befehle)->toContain('config:system:set mail_smtpmode --value='.escapeshellarg('smtp'))
        ->and($befehle)->toContain('config:system:set mail_smtphost --value='.escapeshellarg('mail.clupilot.cloud'))
        ->and($befehle)->toContain('config:system:set mail_smtpport --value='.escapeshellarg('587'))
        ->and($befehle)->toContain('config:system:set mail_smtpsecure --value='.escapeshellarg('tls'))
        ->and($befehle)->toContain('config:system:set mail_smtpauth --value='.escapeshellarg('true'))
        ->and($befehle)->toContain('config:system:set mail_smtpname --value='.escapeshellarg('noreply@clupilot.cloud'))
        ->and($befehle)->toContain('config:system:set mail_from_address --value='.escapeshellarg('noreply'))
        ->and($befehle)->toContain('config:system:set mail_domain --value='.escapeshellarg('clupilot.cloud'));
});

it('maskiert das Passwort, sodass es keinen zweiten Befehl starten kann', function () {
    // Verstecken lässt sich der Wert auf dieser Maschine nicht (siehe
    // Kopfkommentar des Schrittes). Was sehr wohl gilt und geprüft gehört:
    // er darf aus seiner Klammerung nicht ausbrechen. Der Befehl läuft als
    // root auf einer Kundenmaschine — ein Semikolon im Passwort wäre dort
    // ein zweiter Befehl.
    Settings::set('mail.host', 'mail.clupilot.cloud');
    Settings::set('mail.port', 587);
    Mailbox::findByKey('instance-relay')->update([
        'address' => 'noreply@clupilot.cloud',
        'username' => 'noreply@clupilot.cloud',
        'password' => "boes'; touch /tmp/PWNED; echo '",
        'active' => true, 'authenticates' => true,
    ]);

    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);

    app(ConfigureInstanceMail::class)->execute(laufMitInstanz(instanzAufHost()));

    $passwortbefehl = collect($pve->guestCommands)
        ->first(fn ($b) => str_contains($b, 'mail_smtppassword'));

    // Der ganze Wert steht in EINEM maskierten Argument: das Semikolon darf
    // nicht ausserhalb der Anführungszeichen stehen.
    expect($passwortbefehl)->toContain(escapeshellarg("boes'; touch /tmp/PWNED; echo '"));
});

it('schreibt GAR NICHTS, wenn der Mailserver fehlt', function () {
    // Nicht eingerichtet ist etwas anderes als kaputt: dieser Schritt sitzt in
    // der PFLICHT-Pipeline `customer`, und ein bezahlter Kunde bekommt seine
    // Cloud auch dann, wenn der Mailversand noch nicht steht — derselbe
    // Grundsatz wie bei RegisterMonitoring. Der Lauf geht deshalb WEITER
    // (advance), nicht in einen Fehlschlag; nur in den Gast wird nichts
    // geschrieben, und der Grund landet im Protokoll für den Betreiber.
    Log::spy();
    Settings::set('mail.host', '');
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = Instance::factory()->create();

    $ergebnis = app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));

    expect($pve->guestCommands)->toBe([])
        ->and($ergebnis->type)->toBe(\App\Provisioning\StepResult::ADVANCE);
    Log::shouldHaveReceived('warning')->once()->withArgs(
        fn ($message, $context) => $context['instance'] === $instance->uuid && $context['reason'] === 'no_server'
    );
});

it('hängt ein Lauf-Ereignis an, das der Betreiber in der Konsole sieht', function () {
    // Log::warning allein reicht hier nicht: bis der Mailserver steht, trifft
    // dieser Zweig auf JEDE Bestellung zu, und die einzige Rückmeldung darf
    // nicht in einer Logdatei verschwinden, in die niemand schaut —
    // RegisterMonitoring macht sein Überspringen genauso am Lauf sichtbar.
    Settings::set('mail.host', '');
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $run = laufMitInstanz(Instance::factory()->create());

    app(ConfigureInstanceMail::class)->execute($run);

    $ereignis = $run->events()->first();

    expect($ereignis)->not->toBeNull()
        ->and($ereignis->step)->toBe('configure_instance_mail')
        ->and($ereignis->outcome)->toBe('info')
        ->and($ereignis->message)->toContain('no_server');
});

it('schreibt beim zweiten Lauf erneut, statt sich mit einem Merker zu sperren', function () {
    versandbereitFuerSchritt();
    $pve = new FakeProxmoxClient;
    app()->instance(ProxmoxClient::class, $pve);
    $instance = instanzAufHost();

    app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));
    $ersteRunde = count($pve->guestCommands);
    app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));

    // config:system:set ist von sich aus wiederholbar — derselbe Wert zweimal
    // geschrieben ist derselbe Wert. Der Schritt darf deshalb ohne Merker
    // erneut laufen; das ist bei einer Nachrüstung über den Bestand der
    // Normalfall, nicht die Ausnahme.
    expect(count($pve->guestCommands))->toBe($ersteRunde * 2);
});
