<?php

use App\Services\Mail\MailLane;
use App\Support\Readiness;
use App\Support\Readiness\Check;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;

/**
 * `deliveryCheck` ist in DeliveryChecksTest.php schon vergeben — beide Dateien
 * laufen im selben Prozess, eine zweite Deklaration wäre ein Fataler Fehler.
 */
function mailPaceCheck(string $key): ?Check
{
    return collect(Readiness::all())->firstWhere('key', $key);
}

/**
 * Legt einen rohen Redis-Auftrag mit einem bestimmten Einreihungszeitpunkt in
 * eine Spur — dieselbe `createdAt`-Nutzlast, die `RedisQueue::push()` selbst
 * schreibt (Illuminate\Queue\Queue::createPayloadArray()) und die
 * `creationTimeOfOldestPendingJob()` ausliest. Der Inhalt des restlichen
 * Auftrags ist für die Prüfung irrelevant — sie liest nie mehr als dieses eine
 * Feld —, deshalb bleibt er auf das Nötigste reduziert.
 */
function legeAufTraegInDieSpur(string $lane, DateTimeInterface $eingereihtAm): void
{
    Queue::connection('redis')->pushRaw(json_encode([
        'uuid' => (string) Str::uuid(),
        'job' => 'unbenutzt',
        'data' => [],
        'id' => Str::random(32),
        'attempts' => 0,
        'createdAt' => $eingereihtAm->getTimestamp(),
    ]), $lane);
}

/**
 * DB 0 ist die echte Schlange: derselbe Redis, den `docker-compose.yml`s
 * `queue`-Dienst rund um die Uhr mit `queue:work redis --queue=mail-direkt,
 * mail-wichtig,mail-ruhig,default` abhört (siehe die laufenden Container in
 * dieser Umgebung). Ein hier abgelegter Testauftrag liefe Gefahr, vom echten
 * Arbeiter weggeschnappt zu werden, bevor die Prüfung ihn liest — ein
 * Wettlauf mit einem fremden Prozess, kein deterministischer Test. DB 15 ist
 * in keiner Konfiguration dieses Projekts referenziert und für jenen
 * Arbeiter unsichtbar; `creationTimeOfOldestPendingJob()` selbst bleibt der
 * echte Weg über echtes Redis (Illuminate\Queue\RedisQueue), nur auf eine
 * Datenbank verschoben, die niemand sonst anfasst.
 *
 * `config()` allein reicht nur, WEIL Laravels TestCase je Testmethode einen
 * frischen Application-Container baut (`RedisManager` ist ein Singleton, der
 * seine Konfiguration einmal beim ersten `Redis::connection()` dieses
 * Containers einliest — in `artisan tinker`, wo EIN Container die ganze
 * Sitzung lang lebt, kam genau deshalb beim Nachprüfen trotz gesetzter
 * Config die 0 zurück, weil dort schon vorher etwas 'redis' aufgelöst hatte).
 * Der Wächter unten prüft die WIRKLICH gewählte Datenbank am Client selbst,
 * nicht nur die Einstellung, die sie hätte bewirken sollen — sollte diese
 * Annahme durch eine künftige Änderung (z. B. Parallelisierung der Suite)
 * doch einmal nicht mehr gelten, bricht der Test laut ab, statt still
 * `flushdb()` gegen die echte Datenbank 0 abzufeuern.
 */
beforeEach(function () {
    config(['database.redis.default.database' => 15]);
});

afterEach(function () {
    $tatsaechlicheDb = Redis::connection('default')->client()->getDbNum();

    if ($tatsaechlicheDb !== 15) {
        throw new RuntimeException(
            "Erwartete Redis-DB 15 fuer die Aufraeumung, aber die Verbindung steht auf {$tatsaechlicheDb} — flushdb() abgebrochen, um nicht versehentlich die echte Datenbank zu leeren."
        );
    }

    Redis::connection('default')->flushdb();
});

/**
 * Eine Spur, die steht, sieht von außen aus wie eine Spur, die leer ist. Die
 * Grenze liegt bei einer Stunde: 20 je 10 Minuten (der langsamste Takt, Task
 * 3) sind 120 in der Stunde, und ein Lauf dieser Größe steht bei elf Kunden
 * nicht an. Schlägt die Prüfung trotzdem an, klemmt etwas.
 */
it('meldet eine Spur, in der etwas laenger als eine Stunde liegt', function () {
    legeAufTraegInDieSpur(MailLane::CALM, now()->subHours(2));

    expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeFalse();
});

it('meldet eine Spur mit frischer Arbeit nicht', function () {
    legeAufTraegInDieSpur(MailLane::CALM, now());

    expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeTrue();
});

/**
 * Vollständigkeit: die drei Spuren sind gleichberechtigt vor der Prüfung.
 * Ohne diesen Test bewiese der obige Fall nur, dass EINE bestimmte Spur
 * beobachtet wird — mail-direkt oder mail-wichtig könnten unbeobachtet
 * bleiben, und ein Kunde wartet dort ebenso vergeblich auf eine
 * Kennwort-Zurücksetzung oder eine Wartungsankündigung.
 */
it('meldet jede der drei Spuren, wenn sie steht', function (string $lane) {
    legeAufTraegInDieSpur($lane, now()->subHours(2));

    expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeFalse();
})->with([
    'mail-direkt' => [MailLane::DIRECT],
    'mail-wichtig' => [MailLane::URGENT],
    'mail-ruhig' => [MailLane::CALM],
]);

/**
 * Der Normalzustand: nichts wurde je eingereiht. `creationTimeOfOldestPendingJob()`
 * liefert dann `null`, nicht 0 — ein Wert, der von einer erfundenen "leer"
 * nicht zu unterscheiden wäre. Die Prüfung muss `null` als gesund lesen, ohne
 * je selbst irgendetwas einzureihen.
 */
it('ist erfuellt, wenn noch nie etwas in einer Spur lag', function () {
    expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeTrue();
});

it('names what breaks when a mail lane is stuck', function () {
    expect(mailPaceCheck('delivery.mail_lanes_moving')->breaks)->not->toBe('');
});
