<?php

use App\Livewire\Admin\ConfirmReleaseReservation;
use App\Livewire\Admin\HostDetail;
use App\Models\Customer;
use App\Models\Host;
use App\Services\Provisioning\HostCapacity;
use Livewire\Livewire;

/**
 * Ein Host, der jemandem gehört.
 *
 * Enterprise verkauft eine eigene Maschine. Ohne Markierung landet der nächste
 * Startkunde darauf, und die Kapazitätszahlen versprechen den Platz obendrein
 * ein zweites Mal.
 */
it('platziert keinen fremden Kunden auf einem reservierten Host', function () {
    $mieter = Customer::factory()->create();
    $fremder = Customer::factory()->create();

    Host::factory()->active()->create([
        'datacenter' => 'fsn', 'total_gb' => 4000,
        'reserved_for_customer_id' => $mieter->id,
    ]);

    expect(Host::placeableIn('fsn', 100, $fremder->id))->toBeNull()
        ->and(Host::placeableIn('fsn', 100, $mieter->id))->not->toBeNull();
});

it('zählt einen reservierten Host nicht zum allgemeinen Bestand', function () {
    $mieter = Customer::factory()->create();

    Host::factory()->active()->create([
        'datacenter' => 'fsn', 'total_gb' => 4000,
        'reserved_for_customer_id' => $mieter->id,
    ]);

    expect(app(HostCapacity::class)->largestPlaceableGb('fsn'))->toBe(0)
        ->and(app(HostCapacity::class)->canPlace('fsn', 40))->toBeFalse();
});

it('lässt einen freien Host frei', function () {
    Host::factory()->active()->create(['datacenter' => 'fsn', 'total_gb' => 500]);

    expect(app(HostCapacity::class)->canPlace('fsn', 40))->toBeTrue();
});

/**
 * preferredDatacenter() ruft placeableIn() nicht auf — sie fragt selbst nach
 * dem Rechenzentrum mit dem meisten freien Platz, für einen Checkout, der
 * noch keinen Kunden kennt. Ohne ->unreserved() sieht ein Rechenzentrum mit
 * einem großen, aber komplett reservierten Host geräumiger aus als eines mit
 * echtem allgemeinem Bestand — der Checkout legt die neue Bestellung dorthin,
 * placeableIn() findet dort dann niemanden, und die Bestellung parkt, obwohl
 * anderswo echter Platz war.
 */
it('bevorzugt kein Rechenzentrum, dessen größter Host nur reservierter Platz ist', function () {
    App\Models\Datacenter::factory()->create(['code' => 'fsn4']);
    App\Models\Datacenter::factory()->create(['code' => 'hel4']);

    $mieter = Customer::factory()->create();
    // fsn4: der größere Host, aber vollständig einem Kunden versprochen.
    Host::factory()->active()->create(['datacenter' => 'fsn4', 'total_gb' => 4000, 'reserved_for_customer_id' => $mieter->id]);
    // hel4: kleiner, aber echter allgemeiner Bestand.
    Host::factory()->active()->create(['datacenter' => 'hel4', 'total_gb' => 500]);

    expect(app(HostCapacity::class)->preferredDatacenter())->toBe('hel4');
});

/**
 * Die Reservierung überlebt den Kunden — sie löst sich nicht von selbst.
 *
 * Vorgabe: "Die Beziehung löscht nicht mit dem Kunden … die Reservierung
 * wird beim Auszug von Hand gelöst — sonst stünde die Maschine still im
 * Bestand, ohne dass jemand sie angeboten hätte." Ein `nullOnDelete()` täte
 * genau das Verbotene: die Datenbank hebt die Reservierung von sich aus auf,
 * sobald der Kunde verschwindet (z. B. eine DSGVO-Löschung). `restrictOnDelete()`
 * macht daraus stattdessen eine bewusste Handlung — das Löschen selbst
 * scheitert, solange die Maschine noch jemandem gehört.
 */
it('verhindert das Löschen eines Kunden, dem noch ein Host gehört', function () {
    $mieter = Customer::factory()->create();
    Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);

    expect(fn () => $mieter->delete())->toThrow(\Illuminate\Database\QueryException::class);

    // Nichts wurde stillschweigend aufgelöst — der Kunde steht noch da.
    expect(Customer::query()->whereKey($mieter->id)->exists())->toBeTrue();
});

it('lässt einen Kunden ohne Host normal löschen', function () {
    $customer = Customer::factory()->create();

    $customer->delete();

    expect(Customer::query()->whereKey($customer->id)->exists())->toBeFalse();
});

/**
 * Die Konsole: setzen und lösen.
 *
 * Setzen braucht keine Bestätigung — ein Host, der noch niemandem gehört,
 * bekommt einen Besitzer, nichts geht verloren. Lösen schon (R23): danach
 * ist die Maschine wieder allgemeiner Bestand, und der nächste Startkunde
 * kann darauf landen.
 */
it('reserviert einen Host für einen Kunden von der Konsole aus', function () {
    $host = Host::factory()->active()->create();
    $customer = Customer::factory()->create();

    Livewire::actingAs(admin(), 'operator')
        ->test(HostDetail::class, ['host' => $host])
        ->call('reserveFor', $customer->uuid)
        ->assertDispatched('notify');

    expect($host->fresh()->reserved_for_customer_id)->toBe($customer->id);
});

it('verweigert das Reservieren ohne hosts.manage', function () {
    $host = Host::factory()->active()->create();
    $customer = Customer::factory()->create();

    Livewire::actingAs(operator('Read-only'), 'operator')
        ->test(HostDetail::class, ['host' => $host])
        ->call('reserveFor', $customer->uuid)
        ->assertForbidden();

    expect($host->fresh()->reserved_for_customer_id)->toBeNull();
});

it('bestätigt das Lösen im eigenen Modal, ohne dabei selbst etwas zu ändern', function () {
    $mieter = Customer::factory()->create(['name' => 'Acme GmbH']);
    $host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);

    Livewire::actingAs(admin(), 'operator')
        ->test(ConfirmReleaseReservation::class, ['uuid' => $host->uuid])
        ->assertSee('Acme GmbH')
        ->call('confirmRelease')
        ->assertDispatched('host-reservation-release-confirmed');

    // Das Modal selbst rührt nichts an — die Reservierung steht noch.
    expect($host->fresh()->reserved_for_customer_id)->toBe($mieter->id);
});

it('löst die Reservierung, sobald die Seite das bestätigte Ereignis empfängt', function () {
    $mieter = Customer::factory()->create();
    $host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);

    Livewire::actingAs(admin(), 'operator')
        ->test(HostDetail::class, ['host' => $host])
        ->call('onReleaseConfirmed')
        ->assertDispatched('notify');

    expect($host->fresh()->reserved_for_customer_id)->toBeNull();
});

/**
 * Erklärt statt versteckt (Aufgabenauflösung #4): eine reservierte Maschine
 * verschwindet nirgends kommentarlos aus dem Bestand — Host-Detailseite,
 * Host-Liste und Kapazitätsseite weisen sie jeweils als solche aus.
 */
it('zeigt die Reservierung auf der Host-Detailseite', function () {
    $mieter = Customer::factory()->create(['name' => 'Contoso KG']);
    $host = Host::factory()->active()->create(['reserved_for_customer_id' => $mieter->id]);

    $this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))
        ->assertOk()
        ->assertSee(__('hosts.detail.reserved_for', ['name' => 'Contoso KG']));
});

it('bietet auf der Host-Detailseite eine Kundenauswahl, solange nichts reserviert ist', function () {
    $host = Host::factory()->active()->create();
    $customer = Customer::factory()->create(['name' => 'Globex AG']);

    $this->actingAs(admin(), 'operator')->get(route('admin.hosts.show', $host))
        ->assertOk()
        ->assertSee(__('hosts.detail.reserved_general'))
        ->assertSee('Globex AG');
});

it('weist die Reservierung auf der Host-Liste aus', function () {
    $mieter = Customer::factory()->create(['name' => 'Umbrella Corp']);
    Host::factory()->active()->create(['name' => 'pve-reserved-1', 'reserved_for_customer_id' => $mieter->id]);

    $this->actingAs(admin(), 'operator')->get(route('admin.hosts'))
        ->assertOk()
        ->assertSee(__('hosts.reserved_badge', ['name' => 'Umbrella Corp']));
});

it('weist die Reservierung auf der Kapazitätsseite aus', function () {
    App\Models\Datacenter::factory()->create(['code' => 'fsn2']);
    $mieter = Customer::factory()->create(['name' => 'Initech Ltd']);
    Host::factory()->active()->create(['datacenter' => 'fsn2', 'reserved_for_customer_id' => $mieter->id]);

    $this->actingAs(admin(), 'operator')->get(route('admin.capacity'))
        ->assertOk()
        ->assertSee(__('hosts.reserved_badge', ['name' => 'Initech Ltd']));
});
