<?php

use App\Jobs\RunCatalogueSync;
use App\Livewire\Admin\ConfirmSyncCatalogue;
use App\Livewire\Admin\Integrations;
use App\Models\PlanFamily;
use App\Services\Billing\CatalogueSyncStatus;
use App\Support\OperatingMode;
use App\Support\StripeCatalogueMode;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

/**
 * `stripe:sync-catalogue` konnte nur aus einer Shell laufen, obwohl die
 * Bereitschaftsseite den fehlenden Abgleich als blockierend meldet. Jetzt läuft
 * er aus der Konsole — aber NICHT synchron im Livewire-Aufruf: er spricht mit
 * Stripe, legt Produkte und Preise an und braucht Sekunden bis Minuten.
 */
beforeEach(function () {
    config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)));
});

it('queues the sync instead of running it inside the request', function () {
    Queue::fake();

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Integrations::class)
        ->call('syncCatalogue', true)
        ->assertHasNoErrors();

    Queue::assertPushed(RunCatalogueSync::class, fn ($job) => $job->dryRun === true);
});

it('confirms the live run in a modal, and only the live run', function () {
    // Der Trockenlauf ändert nichts und braucht keine Rückfrage — genau
    // deshalb ist er der erste Knopf. Der echte Lauf legt Objekte in einem
    // Zahlungskonto an (R23).
    Queue::fake();

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(ConfirmSyncCatalogue::class)
        ->call('confirm')
        ->assertDispatched('catalogue-sync-confirmed');
});

it('records what the command printed, word for word', function () {
    // Der Trockenlauf zählt, was FEHLEN würde. Sein Text ist das, was der
    // Betreiber sonst im Terminal gelesen hätte — gekürzt oder umformuliert
    // wäre er eine zweite Quelle für dieselbe Aussage.
    (new RunCatalogueSync(dryRun: true, by: 'chef@example.test'))->handle();

    $last = app(CatalogueSyncStatus::class)->last();

    expect($last)->not->toBeNull()
        ->and($last['dry_run'])->toBeTrue()
        ->and($last['by'])->toBe('chef@example.test')
        // Die Zeilen, die der Betreiber sonst im Terminal gelesen hätte: was
        // angelegt WÜRDE, Zeile für Zeile, plus die Summe darunter.
        ->and($last['output'])->toContain('would be created')
        ->and($last['output'])->toContain('price    start v1 monthly');
});

it('shows the stale-catalogue refusal verbatim rather than a summary of it', function () {
    // Die teure Meldung: sie warnt davor, einen Katalog zu leeren, an dem
    // laufende Verträge hängen. Wer sie zu „Abgleich fehlgeschlagen"
    // zusammenfasst, nimmt dem Betreiber genau die Anweisung weg, die er
    // braucht — und die Alternative ist, sie doch wieder im Terminal zu suchen.
    // Die Migration legt den Grundkatalog selbst an — eine dieser Familien
    // trägt jetzt eine ID aus dem anderen Konto.
    PlanFamily::query()->firstOrFail()->update(['stripe_product_id' => 'prod_from_the_other_account']);
    StripeCatalogueMode::record(OperatingMode::current()->isTest() ? OperatingMode::Live : OperatingMode::Test);

    (new RunCatalogueSync(dryRun: true, by: 'chef@example.test'))->handle();

    $last = app(CatalogueSyncStatus::class)->last();

    expect($last['ok'])->toBeFalse()
        ->and($last['output'])->toContain('plan_families.stripe_product_id')
        ->and($last['output'])->toContain('Nothing was created.');
});

it('puts the last result on the page', function () {
    app(CatalogueSyncStatus::class)->record(ok: true, dryRun: true, output: 'MARKER-42 object(s) would be created', by: 'chef@example.test');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Integrations::class)
        ->assertSee('MARKER-42 object(s) would be created');
});

it('does not let an operator without the catalogue capability start a run', function () {
    // Am Modal geprüft, nicht an der Seite: ein Modal ist OHNE die
    // Route-Middleware der Seite erreichbar (siehe EditPlanFamily), also ist
    // das hier die Grenze, die wirklich hält. Die Seite selbst weist Support
    // schon in mount() ab — das ist der Test darunter.
    Queue::fake();

    Livewire::actingAs(operator('Support'), 'operator')
        ->test(ConfirmSyncCatalogue::class)
        ->assertForbidden();

    Queue::assertNothingPushed();
});

it('does not even show the page to an operator who cannot reach it', function () {
    Livewire::actingAs(operator('Support'), 'operator')
        ->test(Integrations::class)
        ->assertForbidden();
});
