<?php

namespace App\Livewire\Admin;

use App\Services\Mail\MailLane;
use App\Support\Settings;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Queue;
use Livewire\Attributes\Layout;
use Livewire\Component;
use RuntimeException;

/**
 * Der Notschalter und die Sicht auf die drei Versandspuren.
 *
 * Eigene Seite statt eines fünften Abschnitts auf admin/mail (App\Livewire\
 * Admin\Mail): dort liegen Server, Postfächer, Zwecke und Wegwahl bereits
 * „alles in einer Wurst" — eine weitere Scheibe wäre keine Verbesserung. Es
 * ist auch inhaltlich eine andere Art Seite: admin/mail RICHTET EIN (einmal,
 * und es gilt), diese hier BEOBACHTET UND GREIFT EIN (laufend, im Ernstfall
 * mit einem Klick) — deshalb steht sie in der Navigation unter „Betrieb", wo
 * schon Provisioning, Wartungen und Störungen stehen, nicht unter „System".
 *
 * Dieselbe Fähigkeit wie admin/mail (`mail.manage`): wer die Postfächer und
 * den Server sehen und ändern darf, darf auch die Spuren sehen und die
 * Drossel abstellen.
 */
#[Layout('layouts.admin')]
class MailPace extends Component
{
    public bool $enabled = true;

    public int $urgentCount = 30;

    public int $urgentMinutes = 5;

    public int $calmCount = 20;

    public int $calmMinutes = 10;

    public function mount(): void
    {
        $this->authorize('mail.manage');

        $this->enabled = Settings::bool('mail.pace.enabled', true);
        $this->urgentCount = (int) Settings::get('mail.pace.urgent.count', 30);
        $this->urgentMinutes = (int) Settings::get('mail.pace.urgent.minutes', 5);
        $this->calmCount = (int) Settings::get('mail.pace.calm.count', 20);
        $this->calmMinutes = (int) Settings::get('mail.pace.calm.minutes', 10);
    }

    /**
     * Der Notschalter: nach dem Muster von Admin\Plans::toggleSales() — eine
     * Methode, eine Rückmeldung, kein Bestätigungsmodal. Er ist umkehrbar,
     * derselbe Klick legt ihn zurück.
     */
    public function togglePace(): void
    {
        $this->authorize('mail.manage');

        $this->enabled = ! $this->enabled;
        Settings::set('mail.pace.enabled', $this->enabled);

        $this->dispatch('notify', message: __($this->enabled ? 'mail_pace.enabled_on_notice' : 'mail_pace.enabled_off_notice'));
    }

    /**
     * Die beiden Kontingente speichern.
     *
     * `min:1` auf allen vier Feldern ist die zweite Hälfte der Untergrenze aus
     * MailPaceServiceProvider::mindestensEins() — die liest nur ab, was hier
     * gar nicht erst gespeichert werden darf. Ohne diese Prüfung käme eine
     * eingetippte 0 ungeprüft bis in die Einstellung durch und flöge erst beim
     * nächsten Auftrag an der Lesestelle auf, nachdem der Betreiber schon
     * „gespeichert" gesehen hat — ein Kontingent von 0 heißt dort: jede Mail
     * wird endlos zurückgelegt, ein Fenster von 0 Minuten schaltet die Drossel
     * still ab.
     */
    public function savePace(): void
    {
        $this->authorize('mail.manage');

        $data = $this->validate([
            'urgentCount' => ['required', 'integer', 'min:1'],
            'urgentMinutes' => ['required', 'integer', 'min:1'],
            'calmCount' => ['required', 'integer', 'min:1'],
            'calmMinutes' => ['required', 'integer', 'min:1'],
        ]);

        Settings::set('mail.pace.urgent.count', $data['urgentCount']);
        Settings::set('mail.pace.urgent.minutes', $data['urgentMinutes']);
        Settings::set('mail.pace.calm.count', $data['calmCount']);
        Settings::set('mail.pace.calm.minutes', $data['calmMinutes']);

        $this->dispatch('notify', message: __('mail_pace.saved'));
    }

    /**
     * Eine Mailklasse in eine andere Spur verschieben.
     *
     * Ein Ziel außerhalb der drei bekannten Spuren kommt nur über einen
     * manuellen Aufruf zustande — kein <select> im DOM bietet eines an — und
     * wird verworfen statt in die Einstellungen zu wandern, wo `MailLane::
     * for()` es beim nächsten Lesen ohnehin still auf „ruhig" zurückfallen
     * ließe, ohne dass der Betreiber sähe, dass sein Klick nichts bewirkt hat.
     */
    public function move(string $mailableClass, string $lane): void
    {
        $this->authorize('mail.manage');

        if (! in_array($lane, [MailLane::DIRECT, MailLane::URGENT, MailLane::CALM], true)) {
            return;
        }

        try {
            MailLane::assign($mailableClass, $lane);
        } catch (RuntimeException) {
            // Gesperrt: der Server lehnt so oder so ab (Task 1), das Schloss in
            // der Ansicht ist nur die Höflichkeit davor. Wer trotzdem hier
            // ankommt — kein <select> bietet einer gesperrten Klasse eines an,
            // also nur ein manueller Aufruf — bekommt eine Meldung statt eines
            // Serverfehlers.
            $this->dispatch('notify', message: __('mail_pace.locked_notice'));

            return;
        }

        $this->dispatch('notify', message: __('mail_pace.moved'));
    }

    public function render()
    {
        // Je Spur: die Warteschlange, die App\Mail\Concerns\RidesALane
        // tatsächlich befüllt (Task 2) — derselbe Name, den auch der Arbeiter
        // in docker-compose.yml abhört (Task 4). Im echten Betrieb (Redis)
        // eine ehrliche Zahl aus der tatsächlichen Liste; unter der
        // Sync-Warteschlange, die die Testsuite erzwingt, immer 0, weil dort
        // nichts je wartet statt verschickt zu werden — dort ist die Zahl
        // ebenso ehrlich, nur bedeutungslos für einen Testfall, der einen
        // Rückstand nachstellen wollte.
        $lanes = [
            [
                'key' => MailLane::DIRECT,
                'throttled' => false,
                'waiting' => Queue::size(MailLane::DIRECT),
            ],
            [
                'key' => MailLane::URGENT,
                'throttled' => true,
                'waiting' => Queue::size(MailLane::URGENT),
                'countField' => 'urgentCount',
                'minutesField' => 'urgentMinutes',
            ],
            [
                'key' => MailLane::CALM,
                'throttled' => true,
                'waiting' => Queue::size(MailLane::CALM),
                'countField' => 'calmCount',
                'minutesField' => 'calmMinutes',
            ],
        ];

        return view('livewire.admin.mail-pace', [
            'lanes' => $lanes,
            'laneOptions' => [MailLane::DIRECT, MailLane::URGENT, MailLane::CALM],
            'assignments' => $this->assignmentRows(),
        ]);
    }

    /**
     * Die Zuordnung mit lesbaren Namen statt Klassennamen.
     *
     * Die Seite zeigte `class_basename()` — „DormantAccountWarningMail" steht
     * dort, wo der Betreiber „Konto ohne Paket wird gelöscht" sucht. Ein
     * Klassenname ist eine Auskunft über den Bauplan, keine über die Mail.
     *
     * Die Namen liegen in der Sprachdatei und NICHT in MailCatalogue, obwohl
     * der sehr ähnlich klingt: der Katalog zählt Mail*arten* (vier
     * Mahnstufen, vier Einträge, vier Absenderwege), diese Seite verteilt
     * Mail*klassen* auf Spuren — und alle vier Mahnstufen sind EINE Klasse,
     * die als eine Zeile eine Spur bekommt. Zwei Listen, weil es zwei
     * verschiedene Dinge sind; MailPaceLabelsTest hält fest, dass keine
     * Klasse ohne Namen bleibt.
     *
     * Fehlt einer doch, steht der Klassenname da wie bisher — unschön, aber
     * lesbar, statt einer leeren Zeile.
     *
     * @return array<int, array{class: class-string, lane: string, label: string, locked: bool}>
     */
    private function assignmentRows(): array
    {
        return collect(MailLane::all())
            ->map(function (string $lane, string $class) {
                $kurz = class_basename($class);
                $schluessel = 'mail_pace.class.'.$kurz;

                return [
                    'class' => $class,
                    'lane' => $lane,
                    'label' => Lang::has($schluessel) ? __($schluessel) : $kurz,
                    'locked' => MailLane::isLocked($class),
                ];
            })
            ->values()
            ->all();
    }
}
