<?php

namespace App\Providers;

use App\Jobs\PacedMail;
use App\Services\Mail\MailLane;
use App\Support\Settings;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Mail\SendQueuedMailable;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

/**
 * Der Takt der beiden gedrosselten Spuren.
 *
 * Die Bindung ist die ganze Verkabelung: `Mailable::newQueuedJob()` erzeugt den
 * Auftrag über den Container, also fährt ab hier jede Mail über PacedMail —
 * ohne dass eine Absendestelle davon weiß.
 *
 * `Limit::perMinutes($minuten, $anzahl)` — Minuten zuerst. Die Reihenfolge ist
 * anders herum, als man sie liest, und ein vertauschtes Paar wäre ein Takt von
 * fünf Mails in dreißig Minuten statt dreißig in fünf.
 *
 * Die direkte Spur bekommt gar keine Drossel angemeldet, nicht bloß ein hohes
 * Kontingent: dort wartet gerade ein Mensch, der eben geklickt hat.
 */
class MailPaceServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(SendQueuedMailable::class, PacedMail::class);
    }

    public function boot(): void
    {
        // Die Einstellungen werden im Rückruf gelesen, nicht hier: der Rückruf
        // läuft im Arbeiter je Auftrag, und eine Änderung des Betreibers wirkt
        // damit ab dem nächsten Auftrag statt erst ab dem nächsten Neustart.
        RateLimiter::for(MailLane::URGENT, fn () => Limit::perMinutes(
            $this->mindestensEins('mail.pace.urgent.minutes', 5),
            $this->mindestensEins('mail.pace.urgent.count', 30),
        ));

        RateLimiter::for(MailLane::CALM, fn () => Limit::perMinutes(
            $this->mindestensEins('mail.pace.calm.minutes', 10),
            $this->mindestensEins('mail.pace.calm.count', 20),
        ));
    }

    /**
     * Eine Zahl aus den Einstellungen, mindestens eins.
     *
     * Die Vorgabe in `Settings::get()` greift nur bei FEHLENDER Zeile. Eine
     * gespeicherte `null` — oder ein Wort statt einer Zahl — kommt als Wert
     * durch und wird von `(int)` zu 0, ohne dass jemand 0 eingetippt hätte.
     * Ein Kontingent von 0 hieße: `tooManyAttempts` ist immer wahr, jede Mail
     * wird endlos zurückgelegt und nach sechs Stunden gesammelt zu
     * `failed_jobs` — genau der Ausgang, den dieser Takt verhindern soll, nur
     * über einen anderen Weg. Ein Fenster von 0 Minuten hieße umgekehrt: gar
     * keine Drossel.
     *
     * Die Untergrenze steht hier, weil der Verlust hier passiert. Was ein
     * eingetipptes 0 bedeuten SOLL, ist eine Frage an die Eingabe und gehört
     * an die Konsolenseite — nicht an diese Lesestelle.
     */
    private function mindestensEins(string $key, int $default): int
    {
        return max(1, (int) Settings::get($key, $default));
    }
}
