<?php

namespace App\Support;

use App\Models\PlanFamily;
use App\Models\PlanPrice;
use App\Models\StripeAddonPrice;
use App\Models\StripePlanPrice;

/**
 * In welchem Stripe-KONTO die gespeicherten Objekt-IDs entstanden sind.
 *
 * Der Betriebsmodus schaltet die Zugangsdaten um. Er schaltet nicht um, was aus
 * ihnen entstanden ist: eine Stripe-Price-ID und eine Product-ID gehören dem
 * Konto, das sie ausgestellt hat, und ein Testkonto und ein Livekonto sind zwei
 * Konten. Jedes Zugangsdatum hat auf dieser Installation zwei Plätze; die
 * daraus abgeleiteten IDs stehen in einwertigen Spalten
 * (`plan_families.stripe_product_id`, `plan_prices.stripe_price_id`) und in den
 * zwei Registern (`stripe_plan_prices`, `stripe_addon_prices`).
 *
 * Ohne diese Zeile lief der geplante Ablauf dieser Installation ins Leere:
 * abgleichen im Testbetrieb, Live-Schlüssel hinterlegen, umschalten — und
 * `billing.catalogue_synced` meldete weiter „erfüllt", weil es nur prüfte, ob
 * die Spalte gefüllt ist. Die Seite sagte „Bereit für Livebetrieb", und die
 * erste echte Bestellung schickte eine Test-Preis-ID an die Live-API: *No such
 * price*, kein Auftrag. Genau die falsche Grünmeldung, gegen die diese Seite
 * gebaut ist, ausgelöst durch genau den Schalter, den dieses Vorhaben einführt.
 *
 * **Warum eine Einstellungszeile und nicht die Objekte selbst.** Stripe schreibt
 * die Kontoart nicht in die ID: `prod_…` und `price_…` sehen im Test- und im
 * Livekonto gleich aus (nur SCHLÜSSEL tragen `_test_`/`_live_` im Präfix — das
 * ist OperatingMode::ofStripeKey()). Die Herkunft ist aus einer gespeicherten ID
 * also nicht ableitbar, und nachfragen dürfte die Bereitschaftsseite nicht: sie
 * erreicht beim Seitenaufruf nichts über das Netz. Bleibt: beim Abgleich
 * festhalten, in welchem Modus die IDs entstanden sind.
 *
 * **Warum EINE Zeile für den ganzen Katalog.** Ein Lauf von
 * stripe:sync-catalogue benutzt genau einen Schlüssel, also genau ein Konto. Und
 * der Befehl weigert sich, in einen Katalog hineinzuarbeiten, der zu einem
 * anderen Konto gehört (siehe dort) — ohne diese Weigerung könnte ein Lauf
 * einen halb gemischten Zustand hinterlassen, über den eine einzelne Zeile dann
 * lügen würde.
 */
final class StripeCatalogueMode
{
    public const SETTING = 'billing.catalogue_mode';

    /** Der Modus, in dem der Katalog zuletzt angelegt wurde — null: noch nie. */
    public static function recorded(): ?OperatingMode
    {
        $stored = Settings::get(self::SETTING);

        return $stored === null ? null : OperatingMode::tryFrom((string) $stored);
    }

    /** Festhalten, wessen Konto die gerade angelegten IDs gehören. */
    public static function record(?OperatingMode $mode = null): void
    {
        Settings::set(self::SETTING, ($mode ?? OperatingMode::current())->value);
    }

    /**
     * Steht fest, dass der gespeicherte Katalog einem ANDEREN Konto gehört?
     *
     * Nur dann — nicht schon, wenn die Herkunft unbekannt ist. Das sind zwei
     * Zustände mit zwei Handlungsanweisungen, und sie zusammenzuwerfen war ein
     * teurer Fehler: „Herkunft nie festgehalten" entsteht ganz ohne Kontowechsel,
     * nämlich wenn ein Abgleich mitten im Anlegen abbricht oder wenn eine
     * Bestellung sich ihren fehlenden Preis selbst holt (CheckoutController →
     * PlanPrices::ensure(), BookAddon → SyncStripeAddonItems →
     * AddonPrices::ensure() — keiner der beiden ruft record()). Die Objekte
     * stammen dann aus genau dem geltenden Konto, und wer in diesem Zustand die
     * Leeranweisung für einen Kontowechsel liest, löscht einen Katalog, an dem
     * laufende Verträge abgerechnet werden.
     *
     * Unbekannte Herkunft ist trotzdem kein Freibrief: die Bereitschaftsseite
     * meldet sie weiter blockierend, nur mit dem Satz, der dazu passt („lauf den
     * Abgleich noch einmal"). Beweisen kann sie die Herkunft nicht.
     */
    public static function belongsToAnotherMode(): bool
    {
        $recorded = self::recorded();

        return $recorded !== null && $recorded !== OperatingMode::current();
    }

    /**
     * Liegt überhaupt eine Stripe-Objekt-ID in der Datenbank?
     *
     * Alle vier Orte, an denen der Abgleich etwas hinterlässt — auch die zwei
     * Register, weil PlanPrices::inStep() für den Netto-Preis eines
     * Reverse-Charge-Kunden ALLEIN das Register fragt: eine geleerte Spalte in
     * `plan_prices` ohne gelöschte Registerzeile ließe genau diese Hälfte des
     * Katalogs still auf das alte Konto zeigen.
     */
    public static function hasStoredObjects(): bool
    {
        return PlanFamily::query()->whereNotNull('stripe_product_id')->exists()
            || PlanPrice::query()->whereNotNull('stripe_price_id')->exists()
            || StripePlanPrice::query()->exists()
            || StripeAddonPrice::query()->exists();
    }
}
