<?php

use App\Models\PlanFamily;
use App\Models\Subscription;
use App\Services\Billing\PlanCatalogue;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Die Leiter, wie sie verkauft wird.
 *
 * Zugeschnitten auf die Maschine, die dasteht: 388 GB vergebbar, ~58 GB RAM für
 * Gäste. Neun Startkunden à 40 GB Platte lasten Speicher und RAM gleichzeitig
 * aus — die alte Leiter band bei drei Kunden und ließ sechs RAM-Plätze
 * verfallen.
 */
it('verkauft drei Pakete', function () {
    expect(array_keys(app(PlanCatalogue::class)->sellable()))->toBe(['start', 'team', 'business']);
});

it('verkauft sie zu den beschlossenen Zahlen', function () {
    $sellable = app(PlanCatalogue::class)->sellable();

    expect($sellable['start']['quota_gb'])->toBe(30)
        ->and($sellable['start']['disk_gb'])->toBe(40)
        ->and($sellable['start']['ram_mb'])->toBe(6144)
        ->and($sellable['start']['seats'])->toBe(10)
        ->and($sellable['start']['price_cents'])->toBe(3900)
        ->and($sellable['start']['yearly_price_cents'])->toBe(39000)
        ->and($sellable['team']['quota_gb'])->toBe(85)
        ->and($sellable['team']['disk_gb'])->toBe(100)
        ->and($sellable['team']['price_cents'])->toBe(7900)
        ->and($sellable['business']['quota_gb'])->toBe(175)
        ->and($sellable['business']['disk_gb'])->toBe(200)
        ->and($sellable['business']['price_cents'])->toBe(13900);
});

it('lässt neun Startkunden auf 388 GB', function () {
    $start = app(PlanCatalogue::class)->sellable()['start'];

    expect(intdiv(388, $start['disk_gb']))->toBe(9);
});

it('nimmt Intern und Enterprise aus dem Laden, lässt aber beide verschenkbar', function () {
    // Beide verlassen sellable() über dasselbe Feld — `internal` —, nicht über
    // den Verkaufsschalter: der bleibt an, sonst verböte currentVersion() auch
    // das Verschenken (Aufgabe 6/InternalPlanTest).
    expect(PlanFamily::query()->where('key', 'test')->value('internal'))->toBeTruthy()
        ->and(PlanFamily::query()->where('key', 'test')->value('name'))->toBe('Intern')
        ->and(PlanFamily::query()->where('key', 'test')->value('sales_enabled'))->toBeTruthy()
        ->and(PlanFamily::query()->where('key', 'enterprise')->value('internal'))->toBeTruthy()
        ->and(PlanFamily::query()->where('key', 'enterprise')->value('sales_enabled'))->toBeTruthy();
});

it('hält Intern nicht für erledigt, nur weil Kontingent und Platte schon stimmten', function () {
    // Der Defekt: die Prüfung verglich nur quota_gb und disk_gb. Genau der
    // Bestand, den der Betreiber früher von Hand über die Konsole angelegt
    // hatte — Kontingent und Platte schon richtig (5 GB / 20 GB), RAM, Kerne
    // und Plätze noch auf den alten Werten (1024 MB, 1 Kern, 5 Plätze).
    $family = PlanFamily::query()->where('key', 'test')->sole();

    DB::table('plan_versions')->where('plan_family_id', $family->id)
        ->update(['available_until' => now(), 'updated_at' => now()]);

    DB::table('plan_versions')->insert([
        'uuid' => (string) Str::uuid(),
        'plan_family_id' => $family->id,
        'version' => 99,
        'quota_gb' => 5, 'disk_gb' => 20, // richtig, wie die Vorgabe
        'ram_mb' => 1024, 'cores' => 1, 'seats' => 5, // die alten, falschen Werte
        'traffic_gb' => 1000, 'performance' => 'standard', 'free_months' => 2,
        'template_vmid' => 9000, 'features' => '[]',
        'available_from' => now()->subYear(), 'available_until' => null,
        'published_at' => now()->subYear(),
        'created_at' => now(), 'updated_at' => now(),
    ]);

    // Der Preis muss ebenfalls stimmen, sonst würde die Prüfung ihn für sich
    // allein schon als Unterschied werten, ohne dass RAM/Kerne/Plätze geprüft
    // wurden — hier soll ausdrücklich der Rest der Vorgabe geprüft werden.
    DB::table('plan_prices')->insert([
        'uuid' => (string) Str::uuid(),
        'plan_version_id' => DB::table('plan_versions')->where('plan_family_id', $family->id)->where('version', 99)->value('id'),
        'term' => 'monthly', 'amount_cents' => 100, 'currency' => 'EUR',
        'created_at' => now(), 'updated_at' => now(),
    ]);

    (require database_path('migrations/2026_08_01_000004_switch_to_new_plan_ladder.php'))->up();

    $current = app(PlanCatalogue::class)->currentVersion('test');

    expect($current->ram_mb)->toBe(4096)
        ->and($current->cores)->toBe(2)
        ->and($current->seats)->toBe(3);
});

it('hebt Intern nachträglich auf seine neuen Größen, für einen Bestand, auf dem die erste Migration schon lief', function () {
    // Nachgebaut statt echt reproduziert: die erste Migration ist inzwischen
    // korrigiert und erzeugt diesen kaputten Bestand selbst nicht mehr. Genau
    // das war aber der gemessene Zustand auf der Entwicklungsdatenbank, bevor
    // dieser Nachtrag existierte — Kontingent und Platte schon richtig, RAM,
    // Kerne und Plätze noch auf den alten Werten, nur eine einzige Version.
    $family = PlanFamily::query()->where('key', 'test')->sole();
    $liveVersionId = DB::table('plan_versions')
        ->where('plan_family_id', $family->id)->whereNull('available_until')->value('id');

    // Dieselbe Zeile, auf ihre alten Werte zurückgesetzt — die frische
    // Installation legt Version 1 schon richtig an, und dieser Test spielt
    // genau die Zeile durch, die vor diesem Nachtrag falsch stehen blieb.
    DB::table('plan_versions')->where('id', $liveVersionId)
        ->update(['ram_mb' => 1024, 'cores' => 1, 'seats' => 5, 'updated_at' => now()]);

    // Vorher: der gemessene Bestand aus dem Aufgabenblatt.
    $before = app(PlanCatalogue::class)->currentVersion('test');
    expect($before->ram_mb)->toBe(1024)
        ->and($before->cores)->toBe(1)
        ->and($before->seats)->toBe(5);

    (require database_path('migrations/2026_08_01_000005_finish_the_plan_ladder_switch.php'))->up();

    // Nachher: 4096 MB, 2 Kerne, 3 Plätze — Kontingent und Platte unverändert.
    $after = app(PlanCatalogue::class)->currentVersion('test');
    expect($after->ram_mb)->toBe(4096)
        ->and($after->cores)->toBe(2)
        ->and($after->seats)->toBe(3)
        ->and($after->quota_gb)->toBe(5)
        ->and($after->disk_gb)->toBe(20);
});

it('tut nichts an Intern, wenn es seine neuen Größen bereits trägt', function () {
    // Der Fall einer Neuinstallation: die korrigierte erste Migration hat
    // Intern schon richtig angelegt, und dieser Nachtrag darf keine dritte
    // Version nachschieben.
    $before = App\Models\PlanVersion::query()->where(
        'plan_family_id', PlanFamily::query()->where('key', 'test')->value('id')
    )->count();

    (require database_path('migrations/2026_08_01_000005_finish_the_plan_ladder_switch.php'))->up();

    expect(App\Models\PlanVersion::query()->where(
        'plan_family_id', PlanFamily::query()->where('key', 'test')->value('id')
    )->count())->toBe($before)
        ->and(app(PlanCatalogue::class)->currentVersion('test')->ram_mb)->toBe(4096);
});

it('macht Enterprise verschenkbar statt käuflich', function () {
    (require database_path('migrations/2026_08_01_000005_finish_the_plan_ladder_switch.php'))->up();

    expect(array_keys(app(PlanCatalogue::class)->grantable()))->toContain('enterprise')
        ->and(array_keys(app(PlanCatalogue::class)->sellable()))->not->toContain('enterprise');
});

it('schaltet kein zweites Mal um, wenn sie noch einmal gefahren wird', function () {
    // Ein zweiter Deploy, ein wiederholtes `migrate` auf einem Bestand, der die
    // Umschaltung schon hinter sich hat: die Migration darf dann keine dritte
    // Fassung nachschieben und die laufende nicht beenden. Erkannt wird das an
    // dem, was die laufende Fassung TRÄGT, nicht an ihrer Nummer.
    $before = App\Models\PlanVersion::query()->count();

    (require database_path('migrations/2026_08_01_000004_switch_to_new_plan_ladder.php'))->up();

    expect(App\Models\PlanVersion::query()->count())->toBe($before)
        ->and(app(PlanCatalogue::class)->sellable()['team']['quota_gb'])->toBe(85)
        ->and(app(PlanCatalogue::class)->currentVersion('team')->available_until)->toBeNull();
});

it('lässt einen laufenden Vertrag auf seinen eigenen Zahlen', function () {
    // Ein Vertrag, wie ihn OpenSubscription vor der Umschaltung geschrieben
    // hat, mit der Maschine dazu.
    $subscription = Subscription::factory()->create([
        'plan' => 'team', 'quota_gb' => 500, 'disk_gb' => 540, 'price_cents' => 17900,
    ]);
    $instance = App\Models\Instance::factory()->create([
        'customer_id' => $subscription->customer_id, 'status' => 'active',
        'quota_gb' => 500, 'disk_gb' => 540,
    ]);

    // Der Katalog sagt heute etwas anderes — und keine Leseseite schreibt es
    // dem Vertrag um. Die mittlere Zusicherung ist die eigentliche: sie geht
    // durch denselben Weg, über den eine Bereitstellung das Kontingent holt.
    expect(app(PlanCatalogue::class)->sellable()['team']['quota_gb'])->toBe(85)
        ->and(App\Services\Billing\StorageAllowance::for($instance)->planGb)->toBe(500)
        ->and($subscription->fresh()->price_cents)->toBe(17900);
});
