<?php

use App\Services\Secrets\SecretCipher;
use App\Support\OperatingMode;
use App\Support\Settings;
use App\Support\StripeCatalogueMode;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Jedes Zugangsdatum bekommt zwei Plätze — und die vorhandene Zeile wird
 * einsortiert statt umbenannt.
 *
 * Für vier der fünf Einträge ist die Antwort dieselbe: was da liegt, wurde für
 * den echten Betrieb hinterlegt, also `:live`. Nur Stripe kann es selbst sagen,
 * weil sein Schlüssel sein Präfix trägt — und daran hängt mehr als eine Zeile:
 * liegt dort ein Testschlüssel, war diese Installation erkennbar im
 * Testbetrieb, und der Modus folgt daraus statt aus einer Vorgabe.
 */
return new class extends Migration
{
    public function up(): void
    {
        $stripeMode = null;

        foreach (DB::table('app_secrets')->get() as $row) {
            // Schon einsortiert (etwa bei einem zweiten Lauf): unangetastet.
            if (Str::contains($row->key, ':')) {
                continue;
            }

            $mode = OperatingMode::Live;

            if ($row->key === 'stripe.secret') {
                $mode = $this->stripeModeOf($row->value);
                $stripeMode = $mode;
            }

            DB::table('app_secrets')
                ->where('id', $row->id)
                ->update(['key' => $row->key.':'.$mode->value]);
        }

        // Nur wenn ein Stripe-Schlüssel da war. Ohne ihn bleibt die Vorgabe
        // stehen, und die ist live.
        if ($stripeMode !== null) {
            Settings::set(OperatingMode::SETTING, $stripeMode->value);

            // Und derselbe Schluss noch einmal, eine Ebene weiter: was bei
            // Stripe an Produkten und Preisen angelegt wurde, wurde mit
            // GENAU DIESEM Schlüssel angelegt — ein anderer war auf dieser
            // Installation nie hinterlegt. Die IDs gehören also dem Konto, das
            // dieser Schlüssel öffnet, und das ist ab jetzt festgehalten
            // (StripeCatalogueMode). Ohne diesen Nachtrag stünde ein längst
            // abgeglichener Katalog als „Herkunft unbekannt" da, und die
            // Bereitschaftsseite verlangte einen neuen Abgleich, den niemand
            // braucht. Nur wenn wirklich IDs da sind: eine Zeile über einen
            // Katalog, den es nicht gibt, wäre eine Behauptung ohne Gegenstand.
            if (StripeCatalogueMode::hasStoredObjects()) {
                Settings::set(StripeCatalogueMode::SETTING, $stripeMode->value);
            }
        }
    }

    private function stripeModeOf(string $stored): OperatingMode
    {
        try {
            $secret = app(SecretCipher::class)->decrypt($stored);
        } catch (Throwable) {
            // Unlesbar heißt: wir wissen es nicht. `:live` ist der Zustand vor
            // dieser Migration, also die Fortsetzung des Status quo statt einer
            // neuen Behauptung. Die Bereitschaftsseite meldet ihn ohnehin.
            return OperatingMode::Live;
        }

        // Die Präfixregel steht in OperatingMode und wird hier gerufen, nicht
        // wiederholt: dieselbe Frage entscheidet auf der Bereitschaftsseite, ob
        // ein Schlüssel im falschen Platz liegt, und in StripeCheck, was der
        // Konsole als Kontoart gemeldet wird. `?? Live` ist wie oben der
        // Status quo für einen Wert, der sich nicht zuordnen lässt.
        return OperatingMode::ofStripeKey($secret) ?? OperatingMode::Live;
    }

    public function down(): void
    {
        $rows = DB::table('app_secrets')->get();

        // Zwei Durchläufe über dieselbe Menge, nicht einer — und die
        // Reihenfolge zwischen den beiden ist die Entscheidung, nicht eine
        // Nebenwirkung. `:live` geht zuerst und beansprucht den nackten
        // Schlüssel bedingungslos; `:test` sieht ihn danach schon vergeben und
        // weicht. Ein einziger Durchlauf, der beide Endungen im selben Schritt
        // behandelt, hätte stattdessen der Zeilenreihenfolge der Datenbank
        // überlassen, welcher der beiden Schlüssel überlebt — bei einem
        // Zahlungsschlüssel die falsche Art von Zufall.
        foreach ($rows as $row) {
            if (Str::endsWith($row->key, ':live')) {
                DB::table('app_secrets')->where('id', $row->id)->update(['key' => Str::beforeLast($row->key, ':')]);
            }
        }

        foreach ($rows as $row) {
            if (! Str::endsWith($row->key, ':test')) {
                continue;
            }

            $bare = Str::beforeLast($row->key, ':');

            // Eine Zeile je Eintrag kann zurück; die zweite hat im alten Schema
            // keinen Platz und würde am Eindeutigkeitsindex scheitern. Existiert
            // der nackte Schlüssel schon, hat ihn der Live-Durchlauf oben gerade
            // erst belegt — der Testwert wird verworfen, nie der Live-Wert.
            if (DB::table('app_secrets')->where('key', $bare)->exists()) {
                DB::table('app_secrets')->where('id', $row->id)->delete();

                continue;
            }

            DB::table('app_secrets')->where('id', $row->id)->update(['key' => $bare]);
        }

        Settings::forget(OperatingMode::SETTING);
        Settings::forget(StripeCatalogueMode::SETTING);
    }
};
