<?php

use App\Livewire\Admin\Roles;
use App\Models\Operator;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

/**
 * Rollen und ihre Rechte waren fest im Code. Zuweisbar war in der Konsole nur
 * die Rolle selbst — was sie DARF, stand in Migrationen.
 *
 * Beim Nachsehen fiel zweierlei auf, und beides ist der Grund, warum die Liste
 * jetzt aus der Datenbank kommt statt aus einer Konstanten:
 *
 * - `Developer` existiert als Rolle, wurde vom Einladen-Formular aber nie
 *   angeboten: die zulässigen Namen standen an DREI Stellen (zwei
 *   Validierungsregeln plus Operator::OPERATOR_ROLES), und eine davon war
 *   nicht mitgezogen worden.
 * - `dpa.manage` lag zusätzlich auf dem `web`-Guard, wo kein Betreiber je
 *   hinsieht — eine Migration hatte den Guard verwechselt.
 */
it('offers every role that actually exists, not a list written down elsewhere', function () {
    Role::findOrCreate('Buchhaltung', 'operator');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->assertSee('Buchhaltung')
        ->assertSee('Developer');
});

it('lets the owner give a role a capability', function () {
    $role = Role::findOrCreate('Buchhaltung', 'operator');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->set("granted.{$role->id}", ['console.view', 'billing.manage'])
        ->call('save', $role->id)
        ->assertHasNoErrors();

    expect($role->fresh()->permissions->pluck('name')->sort()->values()->all())
        ->toBe(['billing.manage', 'console.view']);
});

it('takes a capability away again', function () {
    $role = Role::findOrCreate('Buchhaltung', 'operator');
    $role->givePermissionTo('billing.manage', 'console.view');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->set("granted.{$role->id}", ['console.view'])
        ->call('save', $role->id);

    expect($role->fresh()->permissions->pluck('name')->all())->toBe(['console.view']);
});

it('refuses to let Owner be stripped', function () {
    // Sonst sperrt sich der Betreiber mit einem Klick selbst aus, und es gibt
    // niemanden mehr, der es zurücknehmen könnte. Die einzige Rolle, die
    // dieser Bildschirm nicht anfassen darf.
    $owner = Role::where('name', 'Owner')->where('guard_name', 'operator')->firstOrFail();
    $vorher = $owner->permissions->pluck('name')->sort()->values()->all();

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->set("granted.{$owner->id}", ['console.view'])
        ->call('save', $owner->id)
        ->assertHasErrors();

    expect($owner->fresh()->permissions->pluck('name')->sort()->values()->all())->toBe($vorher);
});

it('creates a role', function () {
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->set('newRole', 'Buchhaltung')
        ->call('create')
        ->assertHasNoErrors();

    expect(Role::where('name', 'Buchhaltung')->where('guard_name', 'operator')->exists())->toBeTrue();
});

it('never creates a role on the wrong guard', function () {
    // Genau der Fehler, der `dpa.manage` auf dem web-Guard hinterlassen hat:
    // ein Eintrag, den kein Betreiber je sieht, weil Operator gegen
    // `operator` auflöst.
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->set('newRole', 'Buchhaltung')
        ->call('create');

    expect(Role::where('name', 'Buchhaltung')->where('guard_name', 'web')->exists())->toBeFalse();
});

it('will not delete a role somebody still holds', function () {
    $role = Role::findOrCreate('Buchhaltung', 'operator');
    Operator::factory()->create()->assignRole($role);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->call('delete', $role->id)
        ->assertHasErrors();

    expect(Role::where('name', 'Buchhaltung')->exists())->toBeTrue();
});

it('deletes a role nobody holds', function () {
    $role = Role::findOrCreate('Buchhaltung', 'operator');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->call('delete', $role->id)
        ->assertHasNoErrors();

    expect(Role::where('name', 'Buchhaltung')->exists())->toBeFalse();
});

it('is not reachable without staff.manage', function () {
    // Wer bestimmen darf, was eine Rolle kann, bestimmt alles andere gleich
    // mit. Dasselbe Recht wie das Einladen von Mitarbeitern.
    Livewire::actingAs(operator('Admin'), 'operator')
        ->test(Roles::class)
        ->assertForbidden();
});

it('keeps counting capabilities correctly past the first role', function () {
    // Ein Fehler, den ich beim Umbau selbst gebaut habe: die Ansicht nannte
    // die Zählvariable `$granted` — genauso wie die Eigenschaft des Bauteils.
    // Ab der zweiten Rolle war sie eine Zahl statt der Liste, und die Seite
    // starb an `count(int)`. Mit einer Rolle wäre es nie aufgefallen.
    Role::findOrCreate('Buchhaltung', 'operator')->givePermissionTo('console.view');
    Role::findOrCreate('Werkstatt', 'operator');

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->assertOk()
        ->assertSee('Buchhaltung')
        ->assertSee('Werkstatt');
});

it('groups the capabilities instead of listing twenty-one in a row', function () {
    $groups = Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Roles::class)
        ->viewData('groups');

    expect($groups)->toHaveKey('billing')
        ->and($groups)->toHaveKey('provisioning')
        ->and(count($groups))->toBeLessThan(21);
});

/**
 * Die Rollenseite zeigte statt der Beschreibungen ihre eigenen Schlüssel:
 * „roles.can.billing.manage" stand da, wo „Rechnungen, Gutschriften,
 * Zahlungsprobleme" hingehört.
 *
 * Die Übersetzungen waren die ganze Zeit vorhanden. Der Aufruf lautete
 * `__('roles.can.'.$permission)`, und ein Berechtigungsname trägt selbst einen
 * Punkt — `__()` liest Punkte als Verschachtelung und suchte deshalb
 * `roles → can → billing → manage`, während in der Sprachdatei ein FLACHER
 * Schlüssel `'billing.manage'` steht. Gefunden wurde nie etwas.
 */
it('zeigt zu jeder Berechtigung ihre Beschreibung, nicht ihren Schluessel', function () {
    foreach (['de', 'en'] as $locale) {
        app()->setLocale($locale);

        $labels = (array) trans('roles.can');

        // Aus der Datenbank, nicht aus einer Liste im Test: so kann eine neu
        // angelegte Berechtigung nicht ohne Beschreibung durchrutschen.
        $permissions = Permission::query()
            ->where('guard_name', 'operator')->pluck('name');

        foreach ($permissions as $permission) {
            expect($labels[$permission] ?? null)
                ->not->toBeNull("„{$permission}“ hat in lang/{$locale}/roles.php keine Beschreibung.")
                // Und die Beschreibung ist keine Wiederholung des Schlüssels.
                ->not->toBe($permission);
        }
    }
});

it('rendert die Beschreibungen wirklich auf der Seite', function () {
    // Gegen das ausgelieferte HTML, nicht gegen die Sprachdatei: der Fehler lag
    // nicht in den Texten, sondern darin, wie die Seite sie holte.
    $html = $this->actingAs(admin(), 'operator')->get(route('admin.roles'))->assertOk()->getContent();

    expect($html)->toContain(e(trans('roles.can')['billing.manage']))
        // Und kein roher Schlüssel mehr.
        ->and($html)->not->toContain('roles.can.billing.manage');
});
