<?php

use App\Livewire\Admin\Readiness as ReadinessPage;
use App\Models\Operator;
use App\Services\Secrets\SecretVault;
use App\Support\OperatingMode;
use App\Support\Readiness;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;

it('is closed to operators without either capability', function () {
    $this->actingAs(Operator::factory()->role('Read-only')->create(), 'operator')
        ->get(route('admin.readiness'))
        ->assertForbidden();
});

it('lists every check, satisfied or not', function () {
    // Livewire::actingAs() needs the 'operator' guard spelled out — omitting it
    // signed the test in on the 'web' guard instead and cost Task 11 a failing
    // test for exactly this reason.
    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->assertSee(__('readiness.billing.company_details'))
        ->assertSee(__('readiness.provisioning.usable_host'));
});

it('says what breaks, not only that something is missing', function () {
    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->assertSee(__('readiness.delivery.mailer_not_log_breaks'));
});

it('runs an active check on demand and shows its answer', function () {
    // Die drei Prüfungen aus Task 9 sind nur dann etwas wert, wenn jemand sie
    // auslösen kann. Sie laufen NICHT bei jedem Seitenaufruf mit: eine davon
    // schreibt einen TXT-Eintrag in die echte Zone.
    //
    // Http::fake() steht hier unbedingt, nicht weil dieser eine Testpfad das
    // Netzwerk erreicht (DnsTokenCheck bricht schon vorher an blank($token)
    // || blank($zone) ab), sondern weil JEDER Test, der runCheck() anfasst,
    // dieselbe Absicherung tragen soll — ein Testpfad, der heute früh
    // ausscheidet, ist keine Garantie, dass er es morgen noch tut.
    Http::fake();

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->call('runCheck', 'provisioning.dns_token')
        ->assertSet('results.provisioning.dns_token.ok', false);
});

/**
 * Der „Prüfen"-Knopf darf der Prüfung nicht widersprechen.
 *
 * StripeCheck::run() rechnet die Kontoart des Schlüssels aus, die Seite hat
 * sie verworfen und nur `ok`/`reason` gerendert — ein Live-Schlüssel im
 * Testplatz antwortet bei Stripe einwandfrei, und die Seite las darüber
 * „Geprüft: in Ordnung", während die Plakette oben „Testbetrieb" sagte.
 */
it('names the account a checked key belongs to, and says when it is the wrong one', function () {
    Http::fake(['*' => Http::response(['id' => 'acct_1'], 200)]);
    OperatingMode::set(OperatingMode::Test);
    app(SecretVault::class)->put('stripe.secret', 'sk_live_REALMONEY', Operator::factory()->create(), OperatingMode::Test);

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->call('runCheck', 'billing.stripe_secret')
        ->assertSee(__('readiness.check_key_mode', ['mode' => __('readiness.mode.live')]))
        ->assertSee(__('readiness.check_key_mode_conflict', ['mode' => __('readiness.mode.test')]));
});

it('refuses to run a check that is not on the list', function () {
    // Ein Schlüssel aus dem Browser. Ohne diese Prüfung wäre das ein Weg,
    // beliebige Klassen aufzurufen.
    Http::fake();

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->call('runCheck', 'etwas.erfundenes')
        ->assertSet('results', []);
});

/**
 * Der Wächter dieser Spec.
 *
 * Ein künftig hinzugefügtes Zugangsdatum darf nicht still an der Übersicht
 * vorbeigehen. Genau das war der Befund vom 2026-07-30: eine grüne Testsuite
 * bei einer Installation, die keinen Host durchinstallieren konnte, weil
 * niemand die Liste geführt hat.
 */
it('covers every entry in the secret registry', function () {
    $covered = collect(Readiness::all())->pluck('key')->implode(' ');

    foreach (SecretVault::REGISTRY as $secret => $meta) {
        // Einträge, die diese Anwendung sich SELBST ausstellt, sind hier nicht
        // gemeint. Der Befund von 2026-07-30 war ein Zugangsdatum, das jemand
        // besorgen und einfügen muss und das niemand auf die Liste gesetzt hat
        // — eine Kennung, die auf Knopfdruck entsteht, kann so nicht fehlen.
        // Ob sie da ist, steht auf ihrer eigenen Karte unter Einstellungen.
        if ($meta['internal'] ?? false) {
            continue;
        }

        // `stripe.secret` → irgendein Prüfschlüssel, der `stripe_secret` enthält.
        $needle = str_replace('.', '_', $secret);

        // NICHT expect($covered)->toContain($needle, $message) — Pest's
        // toContain() ist variadic, ein zweites Argument ist ein zweiter
        // GEFORDERTER Substring, keine eigene Fehlermeldung. Genau diese Falle
        // dokumentiert tests/Feature/ModalHeightTest.php bereits selbst
        // ("asserted that the footer contains its own filename, which is why
        // this failed on a file that was correct") — der Brief-Code für diesen
        // Test tappte hinein: er scheiterte auf JEDEM Eintrag, auch auf
        // 'stripe_secret', das längst abgedeckt ist. str_contains() + toBeTrue()
        // mit Nachricht liefert die Prüfung UND den lesbaren Hinweis.
        expect(str_contains($covered, $needle))->toBeTrue(
            "Der Tresor-Eintrag [{$secret}] taucht auf der Bereitschaftsseite nicht auf."
        );
    }
});

/**
 * Fix-Runde, Befund 1: der Auflöser (checkUrl()) ist nur so gut wie seine
 * Quelle. Neun Prüfschlüssel trugen `tab: 'integrations'` — kein Mitglied von
 * Integrations::TABS — und landeten deshalb alle auf dem Standard-Reiter
 * ('services'), unabhängig davon, wo ihr Feld tatsächlich liegt. Dieser Test
 * hält JEDEN Prüfschlüssel gegen die eine Stelle, an der sein Feld wirklich
 * steht — nicht nur die neun, die diesmal falsch waren — damit der nächste
 * falsche Wert genauso durchrutscht wie diese.
 */
it('sends every check to the tab or page where its field actually lives', function () {
    $expectedTab = [
        'billing.stripe_secret' => 'services',
        'billing.stripe_webhook_secret' => 'services',
        'billing.company_details' => 'company',
        'billing.invoice_series' => 'company',
        'billing.catalogue_synced' => 'services',
        'onboarding.ssh_private_key' => 'platform',
        'onboarding.secrets_key' => 'env',
        'onboarding.vpn_config_key' => 'env',
        'onboarding.wg_hub' => 'platform',
        'onboarding.datacenter' => 'datacenters',
        'provisioning.dns_token' => 'services',
        'provisioning.dns_zone' => 'services',
        'provisioning.traefik_path' => 'services',
        'provisioning.usable_host' => 'hosts',
        'provisioning.vm_template' => 'plans',
        'provisioning.monitoring_token' => 'services',
        'delivery.mailer_not_log' => 'mail',
        'delivery.mailbox' => 'mail',
        'delivery.mail_templates' => 'templates',
        'delivery.inbound_mail_password' => 'services',
        'delivery.mail_lanes_moving' => 'mail-pace',
        'security.console_restricted' => 'security',
        'security.trusted_ranges_clean' => 'security',
        'security.ports_loopback' => 'env',
        'operation.scheduler' => 'env',
        'operation.queue_provisioning' => 'env',
    ];

    foreach (Readiness::all() as $check) {
        // NICHT expect($expectedTab)->toHaveKey($check->key, $message) — genau
        // dieselbe Falle wie bei toContain() weiter oben: toHaveKey()s zweites
        // Argument ist der erwartete WERT des Schlüssels, keine Fehlermeldung.
        expect(array_key_exists($check->key, $expectedTab))->toBeTrue(
            "Neuer Prüfschlüssel [{$check->key}] hat keinen erwarteten tab-Wert in diesem Test — bitte nachtragen."
        );

        expect($check->tab)->toBe(
            $expectedTab[$check->key],
            "{$check->key} zeigt auf '{$check->tab}', sein Feld liegt aber unter '{$expectedTab[$check->key]}'."
        );
    }
});

/**
 * Fix-Runde, Befund 2: mount() lässt hosts.manage ODER secrets.manage durch,
 * runCheck() verlangt aber secrets.manage allein. Ein Admin (nur
 * hosts.manage) erreicht die Seite und sah bislang auch den Knopf — der beim
 * Drücken 403 geworfen hätte. Ein sichtbarer Knopf, der beim Drücken
 * scheitert, ist schlimmer als gar keiner.
 */
it('hides the check button from an operator who could not press it anyway', function () {
    Livewire::actingAs(Operator::factory()->role('Admin')->create(), 'operator')
        ->test(ReadinessPage::class)
        ->assertDontSee(__('readiness.run_check'));
});

/**
 * Fix-Runde, Befund 3: die eine Eigenschaft, wegen der diese ganze
 * Konstruktion existiert — die vier RUNNABLE-Prüfungen laufen NUR auf
 * Knopfdruck, weil DnsTokenCheck einen echten TXT-Eintrag in die
 * Produktivzone schreibt — war bislang ungeprüft.
 */
it('sends nothing over the network on a plain page load', function () {
    Http::fake();

    Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator')
        ->test(ReadinessPage::class);

    Http::assertNothingSent();
});
