<?php

use App\Actions\BookAddon;
use App\Models\Host;
use App\Models\Instance;
use App\Models\Order;
use App\Models\Subscription;
use App\Services\Billing\AddonCatalogue;

/**
 * Ein Block ist Platz auf einer bestimmten Maschine.
 *
 * Eine laufende Instanz zieht nicht um — es gibt keinen Umzug zwischen Hosts.
 * Die Frage ist deshalb nicht "hat irgendein Host Platz", sondern "hat DIESER
 * Host Platz", und sie muss gestellt werden, bevor Geld fließt.
 */
function packedInstanceOn(Host $host): Subscription
{
    $order = Order::factory()->create(['plan' => 'start', 'datacenter' => $host->datacenter, 'status' => 'paid']);
    $subscription = app(App\Actions\OpenSubscription::class)($order);

    $instance = Instance::factory()->create([
        'customer_id' => $subscription->customer_id,
        'host_id' => $host->id,
        'status' => 'active',
        'quota_gb' => 30,
        'disk_gb' => 40,
    ]);

    $subscription->update(['instance_id' => $instance->id]);

    return $subscription->refresh();
}

it('lehnt einen Block ab, für den der Host keinen Platz hat', function () {
    config(['provisioning.storage_addon.gb' => 20, 'provisioning.storage_addon.disk_gb' => 22]);

    // 60 GB gesamt, davon 40 durch die Instanz gebunden: für 22 weitere ist
    // kein Platz. `active()` setzt total_gb/RAM/Kerne — ohne diesen Zustand
    // hat ein Host gar keine Größe.
    $host = Host::factory()->active()->create(['datacenter' => 'fsn', 'total_gb' => 60]);
    $subscription = packedInstanceOn($host);

    expect(fn () => app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1))
        ->toThrow(RuntimeException::class);

    expect($subscription->addons()->count())->toBe(0);
});

it('lässt einen Block durch, für den der Host Platz hat', function () {
    config(['provisioning.storage_addon.gb' => 20, 'provisioning.storage_addon.disk_gb' => 22]);

    $host = Host::factory()->active()->create(['datacenter' => 'fsn', 'total_gb' => 500]);
    $subscription = packedInstanceOn($host);

    app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1);

    expect((int) $subscription->addons()->active()->sum('quantity'))->toBe(1);
});

it('fragt nicht, solange es keine Maschine gibt', function () {
    config(['provisioning.storage_addon.gb' => 20, 'provisioning.storage_addon.disk_gb' => 22]);

    $order = Order::factory()->create(['plan' => 'start', 'datacenter' => 'fsn', 'status' => 'paid']);
    $subscription = app(App\Actions\OpenSubscription::class)($order);

    app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1);

    expect((int) $subscription->addons()->active()->sum('quantity'))->toBe(1);
});

/**
 * Nachtrag aus der Durchsicht: die Kapazitätsprüfung darf einen wiederholt
 * zugestellten Webhook nicht treffen.
 *
 * Stripe liefert bei einem 5xx oder Timeout denselben Webhook ein zweites Mal
 * aus — BookAddon::__invoke() wird mit demselben `$order` erneut aufgerufen,
 * und book()'s eigener Kurzschluss ("Idempotent against a retried webhook")
 * soll dafür nur die bestehende Buchung zurückgeben, ohne irgendetwas neu zu
 * entscheiden. Läge die Kapazitätsprüfung VOR diesem Kurzschluss, würde die
 * Wiederholung erneut fragen "passt NOCH ein Block drauf" — obwohl gar keiner
 * hinzukommt — und einen längst bezahlten, längst gebuchten Vorgang mit einem
 * Fehler quittieren, sobald der Host in der Zwischenzeit enger geworden ist.
 */
it('gibt bei einem wiederholten Webhook die vorhandene Buchung zurück, auch wenn der Host inzwischen eng geworden ist', function () {
    config(['provisioning.storage_addon.gb' => 20, 'provisioning.storage_addon.disk_gb' => 22]);

    // 100 GB gesamt → 85 GB frei (reserve_pct 15). Die gepackte Instanz bindet
    // 40, für den einen Block (22) bleiben 45 - 22 = 23 übrig: reichlich für
    // den ERSTEN Aufruf.
    $host = Host::factory()->active()->create(['datacenter' => 'fsn', 'total_gb' => 100]);
    $subscription = packedInstanceOn($host);

    $order = Order::factory()->create([
        'customer_id' => $subscription->customer_id, 'type' => 'addon', 'addon_key' => 'storage', 'status' => 'paid',
    ]);

    $first = app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1, $order);

    // Der Host wird zwischen den beiden Zustellversuchen enger — hier durch
    // eine zweite Instanz, die den Rest praktisch aufbraucht (40 + 22 + 30 =
    // 92 committed, nur noch 85 - 92 < 0 → 0 frei). Die Ursache ist beliebig
    // (ein zweiter Kunde, ein weiterer Block); die Frage ist nur, ob die
    // WIEDERHOLUNG sie stellen darf.
    Instance::factory()->create([
        'host_id' => $host->id,
        'status' => 'active',
        'disk_gb' => 30,
    ]);

    $second = app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1, $order);

    expect($second->id)->toBe($first->id)
        ->and((int) $subscription->addons()->active()->sum('quantity'))->toBe(1);
});
