<?php

namespace App\Services\Mail;

use App\Mail\CloudResumedMail;
use App\Mail\CloudSuspendedMail;
use App\Mail\ContactRequestMail;
use App\Mail\DormantAccountWarningMail;
use App\Mail\DunningNoticeMail;
use App\Mail\InvoiceMail;
use App\Mail\MaintenanceAnnouncementMail;
use App\Mail\MaintenanceCancelledMail;
use App\Mail\NewDeviceSignInMail;
use App\Mail\OperatorInvitationMail;
use App\Mail\OperatorMessageMail;
use App\Mail\OrderConfirmationMail;
use App\Mail\ResetPasswordMail;
use App\Mail\SecurityBlockMail;
use App\Mail\ServiceEndingSoonMail;
use App\Mail\VerifyEmailMail;
use App\Support\Settings;
use RuntimeException;

/**
 * In welcher Spur eine Mail fährt.
 *
 * Drei Spuren, und die Trennung läuft nicht zwischen Massenversand und
 * Einzelmail, sondern zwischen dringend und nicht dringend. Eine
 * Ausfallmeldung geht an alle UND eilt — sie bekommt deshalb ein höheres
 * Kontingent statt gar keines.
 *
 * Die Vorgaben stehen hier, die Änderung des Betreibers darüber in den
 * Einstellungen. Eine unbekannte Klasse fällt in die ruhige Spur: eine zu
 * langsam verschickte Mail ist ein kleinerer Fehler als ein ungedrosselter
 * Schub, den niemand vorhergesehen hat.
 */
final class MailLane
{
    public const DIRECT = 'mail-direkt';

    public const URGENT = 'mail-wichtig';

    public const CALM = 'mail-ruhig';

    /**
     * Die acht, auf die gerade jemand wartet.
     *
     * Sie entstehen einzeln, weil ein einzelner Mensch geklickt hat, und
     * können gar keinen Schub bilden. Drosseln nützt dort nichts und kostet
     * einen Supportfall je verzögertem Kennwort — deshalb sind sie gesperrt
     * und nicht nur voreingestellt.
     */
    private const LOCKED = [
        ResetPasswordMail::class,
        VerifyEmailMail::class,
        NewDeviceSignInMail::class,
        SecurityBlockMail::class,
        ContactRequestMail::class,
        OrderConfirmationMail::class,
        OperatorMessageMail::class,
        // Die Einladung in die Konsole. Der Inhaber hat gerade geklickt und
        // sagt dem Eingeladenen im selben Moment „schau in dein Postfach" —
        // eine Einladung, die eine Viertelstunde in der ruhigen Spur liegt,
        // ist ein Anruf.
        OperatorInvitationMail::class,
    ];

    /** @var array<class-string, string> */
    private const DEFAULTS = [
        MaintenanceAnnouncementMail::class => self::URGENT,
        MaintenanceCancelledMail::class => self::URGENT,
        CloudSuspendedMail::class => self::URGENT,
        CloudResumedMail::class => self::URGENT,
        // Kein Rundschreiben und keine Werbung, sondern eine Frist, auf die
        // der Empfänger wartet — wie die Wartungsankündigung daneben.
        ServiceEndingSoonMail::class => self::URGENT,
        InvoiceMail::class => self::CALM,
        DunningNoticeMail::class => self::CALM,
        DormantAccountWarningMail::class => self::CALM,
    ];

    /** Die Schlange, in die diese Mailklasse gehört. */
    public static function for(string $mailableClass): string
    {
        if (self::isLocked($mailableClass)) {
            return self::DIRECT;
        }

        $stored = (array) Settings::get('mail.lanes', []);
        $lane = $stored[$mailableClass] ?? self::DEFAULTS[$mailableClass] ?? self::CALM;

        return in_array($lane, [self::DIRECT, self::URGENT, self::CALM], true) ? $lane : self::CALM;
    }

    public static function isLocked(string $mailableClass): bool
    {
        return in_array($mailableClass, self::LOCKED, true);
    }

    /** Verschiebt eine Mailklasse. Wirft, wenn sie gesperrt ist. */
    public static function assign(string $mailableClass, string $lane): void
    {
        if (self::isLocked($mailableClass)) {
            throw new RuntimeException(
                "{$mailableClass} fährt immer direkt: darauf wartet gerade jemand."
            );
        }

        $stored = (array) Settings::get('mail.lanes', []);
        $stored[$mailableClass] = $lane;
        Settings::set('mail.lanes', $stored);
    }

    /**
     * Jede bekannte Mailklasse mit ihrer Spur, für die Konsole.
     *
     * @return array<class-string, string>
     */
    public static function all(): array
    {
        $classes = array_merge(self::LOCKED, array_keys(self::DEFAULTS));

        return collect($classes)
            ->mapWithKeys(fn (string $class) => [$class => self::for($class)])
            ->all();
    }
}
