<?php

use App\Models\Operator;
use App\Services\Secrets\SecretVault;
use App\Services\Terminal\ServerIdentity;
use App\Services\Terminal\ServerTerminalSetup;
use App\Services\Terminal\ServerTicket;
use App\Services\Terminal\TicketStore;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Redis;

/**
 * Die Einrichtung, wie `install-server-terminal` sie auf dem Wirt hinterlässt.
 *
 * Echte Datei statt einer vorgetäuschten: der Weg vom Skript zur Seite führt
 * durch JSON und durch zwei Prüfungen, und beide sind genau die Stellen, an
 * denen sich später etwas verschiebt.
 */
function writeServerSetup(array $overrides = []): void
{
    File::ensureDirectoryExists(dirname(ServerTerminalSetup::path()));
    File::put(ServerTerminalSetup::path(), json_encode(array_merge([
        'address' => '172.18.0.1',
        'fingerprint' => 'SHA256:4J0zwXJCmVzzJOtphWrdG1qADFwOf0dqcQNhh0vkbn0=',
        'installed_at' => '2026-08-04T10:00:00Z',
    ], $overrides)));
}

beforeEach(function () {
    // Beide Schlüssel liegen hier, weil genau ihre Verwechslung das ist, was
    // ein Test unten ausschliessen soll.
    app(SecretVault::class)->put(ServerIdentity::VAULT_KEY, "-----BEGIN OPENSSH PRIVATE KEY-----\nSERVER\n-----END OPENSSH PRIVATE KEY-----", Operator::factory()->create());
    app(SecretVault::class)->put('ssh.private_key', "-----BEGIN OPENSSH PRIVATE KEY-----\nFLOTTE\n-----END OPENSSH PRIVATE KEY-----", Operator::factory()->create());
});

afterEach(function () {
    File::delete(ServerTerminalSetup::path());
});

it('lässt niemanden ohne server.terminal an eine Root-Konsole', function () {
    writeServerSetup();

    // Admin ist die zweitmächtigste Rolle und darf Hosts verwalten — also auch
    // ein HOST-Terminal öffnen. Dieses hier ist eine andere Tür, und dass sie
    // eine andere ist, ist der ganze Punkt dieser Fähigkeit.
    $this->actingAs(Operator::factory()->role('Admin')->create(), 'operator')
        ->get(route('admin.server.terminal'))
        ->assertForbidden();
});

it('stellt kein Ticket aus, wenn die Anfrage von ausserhalb der Liste kommt', function () {
    writeServerSetup();

    // Weder Management-Netz noch freigegebene Adresse. Der Schalter von
    // RestrictConsoleNetwork bleibt dabei AUS — für diese Tür gilt die Liste
    // trotzdem, und genau das hält dieser Test fest.
    config(['admin_access.trusted_ranges' => ['10.66.0.0/24']]);

    $before = Redis::connection('cache')->keys('*terminal:ticket:*');

    $this->actingAs(admin(), 'operator')
        ->get(route('admin.server.terminal'))
        ->assertOk()
        ->assertSee(__('server.terminal.problem.off_network.title'));

    // Kein Fenster ist das eine; kein Ticket ist das, worauf es ankommt.
    expect(Redis::connection('cache')->keys('*terminal:ticket:*'))->toBe($before);
});

it('trägt den Serverschlüssel ins Ticket, nicht den Flottenschlüssel', function () {
    writeServerSetup();

    $operator = admin();

    $payload = TicketStore::redeem(ServerTicket::issue($operator));

    expect($payload['private_key'])->toContain('SERVER')
        ->and($payload['private_key'])->not->toContain('FLOTTE')
        // Adresse und Fingerabdruck kommen aus der Einrichtungsdatei, nicht aus
        // dem Code — auf der nächsten Installation sind beide anders.
        ->and($payload['ip'])->toBe('172.18.0.1')
        ->and($payload['fingerprint'])->toBe('SHA256:4J0zwXJCmVzzJOtphWrdG1qADFwOf0dqcQNhh0vkbn0=')
        ->and($payload['user'])->toBe('root')
        // Damit die Log-Zeile der Brücke lesbar bleibt, ohne sie zu ändern.
        ->and($payload['host_uuid'])->toBe('server')
        ->and($payload['operator_id'])->toBe($operator->id);
});

it('sagt statt einer Fehlerseite, dass der Server noch nicht eingerichtet ist', function () {
    // Keine Datei — der Normalfall vor dem ersten install-agent.sh.
    $this->actingAs(admin(), 'operator')
        ->get(route('admin.server.terminal'))
        ->assertOk()
        ->assertSee(__('server.terminal.problem.not_installed.title'));
});

it('nimmt eine Einrichtung ohne Fingerabdruck nicht als Einrichtung', function () {
    // Die gefährlichste Halbheit: mit Adresse, ohne Fingerabdruck. Die
    // naheliegendste Lesart davon wäre „dann eben nicht prüfen" — also eine
    // ungepinnte Root-Sitzung auf der Steuerungsebene.
    writeServerSetup(['fingerprint' => '']);

    expect(ServerTerminalSetup::read())->toBeNull()
        ->and(ServerTicket::blocker())->toBe('not_installed');
});

it('nimmt auch eine Einrichtung ohne brauchbare Adresse nicht an', function () {
    writeServerSetup(['address' => 'kein-hostname']);

    expect(ServerTerminalSetup::read())->toBeNull();
});

it('sagt auch dann warum, wenn für den Server gar keine Kennung erzeugt ist', function () {
    writeServerSetup();
    app(SecretVault::class)->forget(ServerIdentity::VAULT_KEY);

    $this->actingAs(admin(), 'operator')
        ->get(route('admin.server.terminal'))
        ->assertOk()
        ->assertSee(__('server.terminal.problem.no_key.title'));
});

it('zeigt die Karte nur dem, der sie drücken darf', function () {
    writeServerSetup();

    $this->actingAs(Operator::factory()->role('Admin')->create(), 'operator')
        ->get(route('admin.settings'))
        ->assertOk()
        ->assertDontSee(__('admin_settings.server_terminal_title'));

    $this->actingAs(admin(), 'operator')
        ->get(route('admin.settings'))
        ->assertOk()
        ->assertSee(__('admin_settings.server_terminal_title'))
        // Und der Zeitstempel geht durch ->local(), wie jede Zeit, die ein
        // Mensch liest (R19). Der Wert oben ist 10:00 UTC.
        ->assertSee(__('admin_settings.server_terminal_installed', [
            'when' => Carbon::parse('2026-08-04T10:00:00Z')->local()->isoFormat('LLL'),
        ]));
});

it('sagt auf der Karte, welche Zeile auf dem Server noch fehlt', function () {
    // Kennung erzeugt, Server noch nicht eingerichtet — der Zustand zwischen
    // den beiden Schritten, und der einzige, in dem die Anleitung zählt.
    $this->actingAs(admin(), 'operator')
        ->get(route('admin.settings'))
        ->assertOk()
        ->assertSee(__('admin_settings.server_terminal_not_installed'))
        ->assertSee('sudo bash deploy/install-agent.sh');
});

it('zeigt dem Owner den Vorspann und das Ticket, aber niemals den Schlüssel', function () {
    writeServerSetup();

    $html = $this->actingAs(admin(), 'operator')
        ->get(route('admin.server.terminal'))
        ->assertOk()
        ->getContent();

    expect($html)->toContain('data-terminal-stage')
        ->and($html)->toMatch('/data-ticket="[0-9a-f]{64}"/')
        // Und nichts, was die Brücke geheim halten muss. Die Adresse steht
        // ebenfalls nicht drin: sie ist zwar keine Zugangsdatei, aber sie
        // gehört zur Auskunft über den Weg und hat im Browser nichts verloren.
        ->and($html)->not->toContain('BEGIN OPENSSH PRIVATE KEY')
        ->and($html)->not->toContain('172.18.0.1');
});
