<?php

use App\Livewire\Admin\HostCreate;
use App\Livewire\Admin\ReissueTakeover;
use App\Models\Datacenter;
use App\Models\Host;
use App\Models\Operator;
use App\Support\HostEnrolment;
use App\Support\HostTakeoverCommand;
use Livewire\Features\SupportTesting\Testable;
use Livewire\Livewire;

beforeEach(function () {
    fakeServices();

    config()->set('provisioning.wireguard.hub_public_key', 'HUBKEYHUBKEYHUBKEYHUBKEYHUBKEYHUBKEYHUBKEY0=');
    config()->set('provisioning.wireguard.endpoint', 'hub.clupilot.cloud:51820');
    config()->set('provisioning.wireguard.hub_address', '10.66.0.1');
    config()->set('provisioning.wireguard.subnet', '10.66.0.0/24');
    config()->set('admin_access.app_host', 'app.clupilot.com');
    config()->set('provisioning.dns.platform_zone', 'clupilot.com');

    // firstOrCreate: die Migrationen legen Rechenzentren schon an, und ein
    // zweites `fsn` verletzt den eindeutigen Index.
    Datacenter::query()->firstOrCreate(
        ['code' => 'fsn'],
        ['name' => 'Falkenstein', 'location' => 'DE', 'active' => true],
    );
});

function createHostAs(): Testable
{
    return Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(HostCreate::class)
        ->set('datacenter', 'fsn')
        ->set('public_ip', '198.51.45.9')
        ->set('root_password', 'ein-langes-kennwort')
        ->call('save');
}

/**
 * Die Bootstrap-Zeile, wie sie der Weg über das Rettungssystem braucht.
 *
 * Sie wird hier direkt gebaut statt über eine Seite abgegriffen. Ihre
 * Richtigkeit hat nichts damit zu tun, wer sie anzeigt — und genau diese
 * Kopplung war der Grund, warum fünf Tests umfielen, als die Anlegen-Seite
 * aufhörte, sie zu zeigen.
 */
function takeoverCommandFor(Host $host): string
{
    return HostTakeoverCommand::for($host, HostEnrolment::issueWithKeys($host));
}

/**
 * Die Anlegen-Seite zeigt keine Befehlszeile mehr.
 *
 * Sie gehörte zum Bootstrap-Weg: Rettungssystem starten, Zeile kopieren, dort
 * einfügen. Das Speichern startet aber die Provisionierungs-Pipeline, und die
 * meldet sich mit dem Root-Passwort selbst an einer laufenden Debian-Maschine
 * an. Es gibt nichts zu kopieren — die Seite behauptete es trotzdem.
 */
it('does not hand the operator a command line they do not need', function () {
    createHostAs()
        ->assertDontSee('clupilot-bootstrap.sh')
        ->assertDontSee('--code')
        // Stattdessen der Weg zu dem, was jetzt wirklich passiert.
        ->assertSee(__('hosts.takeover.watch'));
});

/**
 * Der Weg über das Rettungssystem bleibt erreichbar — nur nicht mehr von der
 * Anlegen-Seite aus. Wer leeres Blech ohne vorinstalliertes System hat,
 * bekommt die Zeile über Admin\ReissueTakeover.
 */
it('still issues the command line where the rescue route lives', function () {
    $host = Host::factory()->create(['datacenter' => 'fsn']);

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReissueTakeover::class, ['uuid' => $host->uuid])
        ->call('issue')
        ->assertSee('clupilot-bootstrap.sh');
});

/**
 * Die Zeile muss ALLES tragen, was das Skript vor dem Tunnel braucht (Spec §5).
 * Fehlt ein Wert, läuft sie an und endet in einem Tunnel ohne Handshake — und
 * das merkt man erst auf einer Maschine, die schon bestellt ist.
 */
it('carries every value the script needs before the tunnel', function () {
    $command = takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn']));

    foreach (['--code', '--wg-private', '--wg-ip', '--hub-pubkey', '--hub-endpoint', '--api', '--fqdn'] as $argument) {
        expect($command)->toContain($argument);
    }

    expect($command)
        ->toContain('hub.clupilot.cloud:51820')
        ->toContain('--api http://10.66.0.1')
        ->toContain('/opt/clupilot');

    // Der Name des Hosts liegt auf der PLATTFORM-Zone. `.cloud` gehört den
    // Kundeninstanzen und ist laut OfficialDomains bewusst eine andere
    // registrierbare Domain — geprüft wird deshalb der --fqdn-Wert selbst und
    // nicht die ganze Zeile: der Hub-Endpunkt ist eine eigene Einstellung und
    // darf heißen, wie er will.
    preg_match('/--fqdn (\S+)/', $command, $fqdn);
    expect($fqdn[1] ?? '')->toEndWith('.node.clupilot.com');
});

/**
 * Dieselbe Trennung an der Stelle, an der sie schon einmal gebrochen war.
 *
 * `RegisterHostDns` nennt im eigenen Kopfkommentar `fsn-01.node.clupilot.com`,
 * die Prüfregel in `Datacenters` nennt denselben Namen, und `ServicesTest`
 * schreibt ihn wörtlich — der Schritt baute den Namen aber aus
 * `provisioning.dns.zone`, und das ist die KUNDENZONE. Auf dieser Installation
 * hieß ein Host damit `fsn-01.node.clupilot.cloud`.
 */
it('keeps host names out of the customer zone', function () {
    config()->set('provisioning.dns.zone', 'clupilot.cloud');
    config()->set('provisioning.dns.platform_zone', 'clupilot.com');

    $host = Host::factory()->create(['name' => 'fsn-01']);

    expect(HostTakeoverCommand::fqdnFor($host))
        ->toBe('fsn-01.node.clupilot.com');
});

/**
 * Über den ÖFFENTLICHEN Hostnamen. Die Konsole läuft unter admin.…, aber die
 * Zeile wird auf einer Maschine ausgeführt, die den Adminbereich nicht erreichen
 * darf — sie ist genau dafür abgeriegelt. Ein Link auf den Konsolen-Hostnamen
 * liefe in eine 404 auf einem Server, an den man dann nur noch über die
 * Anbieterkonsole kommt.
 */
it('fetches the installer over the public hostname', function () {
    // Vom Datei-Hostnamen, seit es einen gibt — und in keinem Fall vom
    // Konsolen-Hostnamen: die Konsole ist per Client-IP abgeriegelt
    // (RestrictConsoleNetwork), und ein frischer Server im Rettungssystem steht
    // in keinem der freigegebenen Bereiche.
    expect(takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn'])))
        ->toContain('https://files.clupilot.test/bootstrap.tar.gz')
        ->not->toContain('admin.');
});

/**
 * Der Schlüssel ist base64 und enthält +, / und =. Ohne Anführungszeichen
 * zerlegt die Shell die Zeile an einer Stelle, die niemand sieht.
 */
it('quotes the keys so a shell cannot split them', function () {
    $command = takeoverCommandFor(Host::factory()->create(['datacenter' => 'fsn']));

    expect($command)->toMatch("/--wg-private '[^']+'/")
        ->and($command)->toMatch("/--hub-pubkey '[^']+'/");
});

/**
 * Der Code aus der Zeile muss der sein, mit dem der Host sich meldet. Sonst ist
 * die Anleitung richtig und der Ablauf trotzdem kaputt.
 */
it('shows a code that actually resolves to the new host', function () {
    $host = Host::factory()->create(['datacenter' => 'fsn', 'name' => 'fsn-07']);
    $command = takeoverCommandFor($host);

    preg_match('/--code ([A-Za-z0-9]+)/', $command, $matches);

    // Auf DIESEN Host, nicht bloß auf irgendeinen: der Code ist der Ausweis,
    // mit dem sich der Server meldet.
    expect(HostEnrolment::claim($matches[1])?->is($host))->toBeTrue();
});

/**
 * Ein leerer Hub-Schlüssel ergibt eine Zeile, die läuft und nie handshaked.
 * Das gehört gesagt, BEVOR jemand einen Server bestellt.
 */
it('warns before creating a host when the tunnel settings are missing', function () {
    config()->set('provisioning.wireguard.hub_public_key', '');
    config()->set('provisioning.wireguard.endpoint', '');

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(HostCreate::class)
        ->assertSee('CLUPILOT_WG_HUB_PUBKEY')
        ->assertSee('CLUPILOT_WG_ENDPOINT');
});

/**
 * Nach dem Anlegen bleibt derselbe Ablauf stehen, nur einen Schritt weiter.
 * Die Schiene bleibt vollstaendig sichtbar: wer wissen will, was noch kommt,
 * soll nicht auf einer Seite landen, die nur noch aus einem Knopf besteht.
 */
it('keeps the whole procedure visible after the host exists', function () {
    createHostAs()
        ->assertSee(__('hosts.takeover.s1_label'))
        ->assertSee(__('hosts.takeover.s2_label'))
        ->assertSee(__('hosts.takeover.s3_label'));
});

it('refuses to create a host without the permission', function () {
    Livewire::actingAs(Operator::factory()->create(), 'operator')
        ->test(HostCreate::class)
        ->set('datacenter', 'fsn')
        ->set('public_ip', '198.51.45.10')
        ->set('root_password', 'ein-langes-kennwort')
        ->call('save')
        ->assertForbidden();

    expect(Host::query()->where('public_ip', '198.51.45.10')->exists())->toBeFalse();
});
