<?php

use App\Actions\StartHostOnboarding;
use App\Livewire\Admin\HostCreate;
use App\Livewire\Admin\HostDetail;
use App\Models\Datacenter;
use App\Models\Host;
use App\Support\HostName;
use App\Support\HostTakeoverCommand;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

beforeEach(function () {
    Queue::fake();
    fakeServices();
    // Die Zone wird hier festgenagelt statt aus der .env dieser Maschine
    // gelesen: `provisioning.dns.platform_zone` leitet sich sonst aus APP_URL
    // ab, und dieselbe Prüfung fiele auf einer richtig konfigurierten
    // Installation um. Geprüft wird die FORM des Namens, nicht die Domain
    // dieser einen Kiste.
    config()->set('provisioning.dns.platform_zone', 'clupilot.com');
    // Die Migration von `create_datacenters_table` legt fsn und hel schon an;
    // firstOrCreate steht hier, damit der Test nicht daran hängt.
    Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
});

/** Ein Host, der so angelegt wird, wie die Konsole es tut. */
function onboard(string $dc = 'fsn'): Host
{
    static $n = 0;
    $n++;

    return app(StartHostOnboarding::class)->run([
        'datacenter' => $dc,
        'public_ip' => '203.0.113.'.$n,
        'root_password' => 'supersecret',
    ]);
}

// --- Abnahme 1 + 2 ---

it('vergibt den Namen selbst, fortlaufend je Rechenzentrum', function () {
    expect(onboard()->name)->toBe('fsn-01')
        ->and(onboard()->name)->toBe('fsn-02');
});

it('führt je Rechenzentrum einen eigenen Zähler', function () {
    Datacenter::query()->firstOrCreate(['code' => 'hel'], ['name' => 'Helsinki']);

    expect(onboard('fsn')->name)->toBe('fsn-01')
        ->and(onboard('hel')->name)->toBe('hel-01');
});

// --- Abnahme 3: der Punkt, der die Sorgfalt trägt ---

it('gibt die Nummer eines entfernten Hosts nie wieder aus', function () {
    onboard();                       // fsn-01
    $zweiter = onboard();            // fsn-02
    onboard();                       // fsn-03

    // Die HÖCHSTE zu entfernen ist der gefährliche Fall: MAX(nummer) + 1
    // reichte fsn-03 sofort wieder heraus, und ein zwischengespeicherter Name
    // zeigte danach auf eine andere Maschine.
    $zweiter->delete();

    expect(onboard()->name)->toBe('fsn-04');
});

it('überlebt auch das Entfernen des höchsten Hosts', function () {
    onboard();                       // fsn-01
    $hoechster = onboard();          // fsn-02
    $hoechster->delete();

    expect(onboard()->name)->toBe('fsn-03');
});

// --- Abnahme 5 ---

it('zeigt den Namen vorher an, ohne ihn zu verbrauchen', function () {
    onboard();  // fsn-01 ist weg

    $seite = Livewire::actingAs(admin(), 'operator')->test(HostCreate::class)
        ->set('datacenter', 'fsn');

    $seite->assertSee('fsn-02')->assertSee('fsn-02.node.clupilot.com');

    // Zweimal ansehen verbraucht nichts.
    Livewire::actingAs(admin(), 'operator')->test(HostCreate::class)
        ->set('datacenter', 'fsn')
        ->assertSee('fsn-02');

    expect(HostName::preview('fsn'))->toBe('fsn-02');
});

it('gibt zwei gleichzeitig geöffneten Formularen zwei verschiedene Namen', function () {
    // Beide sehen fsn-01, gespeichert wird fsn-01 und fsn-02.
    expect(HostName::preview('fsn'))->toBe('fsn-01')
        ->and(HostName::preview('fsn'))->toBe('fsn-01');

    expect(onboard()->name)->toBe('fsn-01')
        ->and(onboard()->name)->toBe('fsn-02');
});

// --- Der Riegel ---

it('lässt zwei Hosts nicht denselben Namen tragen', function () {
    onboard();

    expect(fn () => Host::factory()->create(['name' => 'fsn-01']))
        ->toThrow(QueryException::class);
});

it('gibt zwei ähnlich geschriebenen Rechenzentrums-Codes nicht denselben Namen', function () {
    // Codes von vor der Verschärfung der Prüfregel: eu_west und eu-west werden
    // zur selben DNS-Bezeichnung, führen aber getrennte Zähler. Ohne das
    // Weiterzählen bekäme der zweite eu-west-01 und liefe in den Index.
    Datacenter::factory()->create(['code' => 'eu_west', 'name' => 'EU West (alt)']);
    Datacenter::factory()->create(['code' => 'eu-west', 'name' => 'EU West']);

    expect(onboard('eu_west')->name)->toBe('eu-west-01')
        ->and(onboard('eu-west')->name)->toBe('eu-west-02');
});

it('fällt auf eine gültige Bezeichnung zurück, wenn der Code keine hergibt', function () {
    expect(HostName::label('---'))->toBe('node');
});

it('zählt ab hundert ohne Sonderfall weiter', function () {
    Datacenter::query()->where('code', 'fsn')->update(['next_host_number' => 100]);

    expect(onboard()->name)->toBe('fsn-100');
});

// --- Ein Name, überall derselbe ---

it('nennt den Host im DNS so, wie die Konsole ihn nennt', function () {
    $host = onboard();

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

it('zeigt auf der Detailseite die IP und die Domain', function () {
    $host = onboard();
    $host->update(['status' => 'active', 'wg_ip' => '10.66.0.100', 'public_ip' => '203.0.113.200']);

    Livewire::actingAs(admin(), 'operator')
        ->test(HostDetail::class, ['host' => $host])
        ->assertSee('203.0.113.200')
        ->assertSee('10.66.0.100')
        // Der Name allein hilft nicht, wenn niemand die Adresse dazu kennt.
        // 8006 ist die Proxmox-Oberfläche; SecureHostFirewall lässt sie nur aus
        // dem Tunnel zu, und der hostsdir-Eintrag löst nur dort auf.
        ->assertSee('fsn-01.node.clupilot.com')
        ->assertSee('https://fsn-01.node.clupilot.com:8006', false);
});
