<?php

use App\Livewire\Admin\Settings;
use App\Support\Navigation;
use Livewire\Livewire;

/**
 * The settings page is tabbed, and the tab survives a reload.
 *
 * It had grown to three headed sections in one column with the operator's own
 * password somewhere in the middle. Which part somebody wants is a property of
 * what they came to do, not of how far they scroll — and the installation tab
 * refreshes itself while an update runs, so a tab kept only in the component
 * would drop them back to the first one mid-deployment.
 */
it('opens the installation tab by default', function () {
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Settings::class)
        ->assertSet('tab', 'installation')
        ->assertSee(__('admin_settings.update_title'));
});

it('remembers the tab across a reload, because it is in the URL', function () {
    // The whole point of #[Url]. Without it a refresh — or the browser's back
    // button — lands on the first tab every time.
    Livewire::withQueryParams(['tab' => 'account'])
        ->actingAs(operator('Owner'), 'operator')
        ->test(Settings::class)
        ->assertSet('tab', 'account')
        ->assertSee(__('admin_settings.account_title'))
        ->assertDontSee(__('admin_settings.update_title'));
});

it('falls back to the first tab when the URL names one that does not exist', function () {
    // A query string is a string a stranger typed. Anything unknown must not
    // render a settings page with no section on it at all.
    Livewire::withQueryParams(['tab' => 'wat'])
        ->actingAs(operator('Owner'), 'operator')
        ->test(Settings::class)
        ->assertSet('tab', 'installation')
        ->assertSee(__('admin_settings.update_title'));
});

it('carries two-factor enrolment as a tab rather than a page of its own', function () {
    Livewire::withQueryParams(['tab' => 'two-factor'])
        ->actingAs(operator('Owner'), 'operator')
        ->test(Settings::class)
        ->assertSet('tab', 'two-factor')
        ->assertSee(__('two_factor_setup.title'));
});

it('does not offer the team tab to somebody who cannot manage staff', function () {
    // A tab that raises a 403 is worse than one that is not there — and an
    // operator who reaches ?tab=team anyway lands on the first tab rather than
    // on a page with nothing on it.
    $support = operator('Support');

    expect($support->can('staff.manage'))->toBeFalse();

    Livewire::withQueryParams(['tab' => 'team'])
        ->actingAs($support, 'operator')
        ->test(Settings::class)
        ->assertSet('tab', 'installation')
        ->assertViewHas('tabs', fn (array $tabs) => ! in_array('team', $tabs, true));
});

it('lists every tab it can render, and renders every tab it lists', function () {
    // The const IS the schema: it validates the query string, builds the bar
    // and decides what renders. A tab in the list with no label is a button
    // that says "admin_settings.tab.x".
    foreach (Settings::TABS as $tab) {
        $key = 'admin_settings.tab.'.str_replace('-', '_', $tab);

        expect(__($key))->not->toBe($key);
    }
});

it('keeps the tunnel with the fleet and drops the two-factor entry from the menu', function () {
    // The tunnel sat under System, on the argument that it is how the console
    // reaches the estate at all and therefore belongs beside the things that
    // have to work before any of the day's work can.
    //
    // That argument holds — and it holds just as well for Integrations and
    // Readiness, which stayed under System. What decided it instead: this page
    // is a LIST OF PEERS an operator reads while working, the same way they
    // read the host list. Reading a list of machines is not configuring an
    // installation. Same reasoning that already pulled Hostnamen out of System.
    //
    // Two-factor left the menu entirely: it is a tab now.
    $groups = collect(Navigation::console());

    $routesIn = fn (string $label) => $groups
        ->firstWhere('label', __($label))['items'] ?? [];

    $fleet = collect($routesIn('admin.nav_group.fleet'))->pluck(0);
    $system = collect($routesIn('admin.nav_group.system'))->pluck(0);
    $operations = collect($routesIn('admin.nav_group.operations'))->pluck(0);

    expect($fleet)->toContain('admin.vpn')
        ->and($system)->not->toContain('admin.vpn')
        ->and($operations)->not->toContain('admin.vpn')
        ->and($groups->pluck('items')->flatten(1)->pluck(0))
        ->not->toContain('admin.two-factor-setup');
});

it('keeps the standalone enrolment route, because the gate sends people there', function () {
    // RequireOperatorTwoFactor redirects an operator who has not enrolled yet,
    // and that operator may not open anything else — the settings page
    // included. Folding the page away entirely would have locked them out.
    expect(route('admin.two-factor-setup'))->toBeString();

    $this->actingAs(operator('Owner'), 'operator')
        ->get(route('admin.two-factor-setup'))
        ->assertOk()
        ->assertSee(__('two_factor_setup.title'));
});

it('tabs the integrations page the same way, and keeps the raw file to the Owner', function () {
    // Same treatment, same reason: one narrow column of six cards had an
    // operator scrolling past Stripe to reach DNS, with the raw .env editor at
    // the bottom of everything.
    $owner = operator('Owner');

    Livewire::actingAs($owner, 'operator')
        ->test(App\Livewire\Admin\Integrations::class)
        ->assertSet('tab', 'services')
        ->assertViewHas('tabs', fn (array $tabs) => in_array('env', $tabs, true));

    foreach (App\Livewire\Admin\Integrations::TABS as $tab) {
        expect(__('integrations.tab.'.$tab))->not->toBe('integrations.tab.'.$tab);
    }

    // An Admin can reach the page for the DNS zone but not the file that holds
    // every credential on the machine.
    $admin = operator('Admin');

    expect($admin->can('hosts.manage'))->toBeTrue()
        ->and($admin->can('secrets.manage'))->toBeFalse();

    Livewire::withQueryParams(['tab' => 'env'])
        ->actingAs($admin, 'operator')
        ->test(App\Livewire\Admin\Integrations::class)
        ->assertSet('tab', 'services')
        ->assertViewHas('tabs', fn (array $tabs) => ! in_array('env', $tabs, true));
});

it('puts the open tab in the address bar from the first render, on every tabbed page', function () {
    // Reported as "die Tabs fehlen in der Query beim Kunden". The binding was
    // there, but `except` hid the parameter while the default tab was open — so
    // a link to the page could not say which tab it meant unless the tab
    // happened not to be the first one, and the address bar looked like the
    // feature was missing.
    //
    // Asserted against the rendered page rather than the component: the
    // parameter is written by Livewire's JS from the `url` memo in the snapshot,
    // so the memo IS the behaviour.
    $customer = App\Models\Customer::factory()->create();

    $pages = [
        route('admin.settings'),
        route('admin.integrations'),
        route('admin.customer', $customer->uuid),
    ];

    foreach ($pages as $url) {
        $html = html_entity_decode(
            (string) $this->actingAs(operator('Owner'), 'operator')->get($url)->getContent()
        );

        expect($html)->toContain('"url":{"tab":')
            // No `except`: shown even on the default tab.
            ->and($html)->toMatch('/"tab":\{[^}]*"except":null/')
            // push, not replace: each tab is a step the back button can take.
            ->and($html)->toMatch('/"tab":\{[^}]*"use":"push"/');
    }
});
