<?php

use App\Jobs\PacedMail;
use App\Mail\Concerns\RidesALane;
use App\Mail\InvoiceMail;
use App\Mail\ResetPasswordMail;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\User;
use App\Services\Billing\IssueInvoice;
use App\Services\Mail\MailLane;
use App\Support\CompanyProfile;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;

/**
 * Die Spur entsteht beim Einreihen, nicht beim Senden.
 *
 * Der Arbeiter liest die Schlangen in der Reihenfolge ihrer Dringlichkeit —
 * deshalb steht ein Kennwort-Zurücksetzen nie hinter zweihundert Rechnungen.
 * Das trägt nur, wenn die Rechnung wirklich in einer anderen Schlange liegt.
 *
 * Werkzeug: Queue::fake() statt Mail::fake(). Mail::fake() ersetzt den
 * ganzen Mailer — der Aufruf, der onQueue() setzt (Mailer::queue(), der
 * dann Mailable::queue() aufruft), fände dann nie statt, und der Test würde
 * grün, ohne die Spur je berechnet zu haben. Queue::fake() lässt genau
 * diesen Aufruf laufen und fängt erst dahinter ab, an der Queue-Fabrik —
 * die Stelle, an der pushOn() tatsächlich ankommt.
 *
 * Der eingereihte Auftrag heißt seit dem Versandtakt PacedMail und nicht mehr
 * SendQueuedMailable: eine Bindung im Container tauscht ihn für jede Mail aus.
 * Queue::fake() legt die Aufträge unter ihrem EXAKTEN Klassennamen ab, ein
 * Oberklassenname trifft dort nichts — die Spur, um die es hier geht, ist von
 * dem Tausch unberührt.
 */
function invoiceForTest(): Invoice
{
    CompanyProfile::put([
        'name' => 'CluPilot Cloud e.U.',
        'address' => 'Dreherstraße 66/1/8',
        'postcode' => '1110',
        'city' => 'Wien',
        'vat_id' => 'ATU00000000',
    ]);

    $customer = Customer::factory()->create(['name' => 'Muster GmbH']);
    $orders = collect([Order::factory()->create([
        'customer_id' => $customer->id, 'amount_cents' => 1900, 'currency' => 'EUR', 'status' => 'paid',
    ])]);

    return app(IssueInvoice::class)->forOrders($customer, $orders);
}

it('reiht jede Mail in die Schlange ihrer Spur ein', function () {
    Queue::fake();

    Mail::to('kunde@example.test')->queue(new InvoiceMail(invoiceForTest(), 'Muster'));

    Queue::assertPushedOn(MailLane::CALM, PacedMail::class);
});

it('reiht eine Direkt-Mail in die Direkt-Spur ein', function () {
    Queue::fake();

    $user = User::factory()->create();

    Mail::to('kunde@example.test')->queue(new ResetPasswordMail($user, 'https://example.test/reset', 60));

    Queue::assertPushedOn(MailLane::DIRECT, PacedMail::class);
});

it('folgt einer verschobenen Zuordnung', function () {
    Queue::fake();
    MailLane::assign(InvoiceMail::class, MailLane::URGENT);

    Mail::to('kunde@example.test')->queue(new InvoiceMail(invoiceForTest(), 'Muster'));

    Queue::assertPushedOn(MailLane::URGENT, PacedMail::class);
});

/**
 * Rekursiv statt `glob('Mail/*.php')`: `app/Mail` hat mit `Concerns/` und
 * `Transport/` schon zwei Unterordner und wird eher mehr als weniger. Eine
 * fünfzehnte Mailklasse in einem Unterordner sähe der alte `glob()`-Aufruf
 * nie — der Wächter schwiege genau in dem Moment, in dem er gebraucht wird.
 *
 * Eingeschränkt auf echte Mailables (Unterklassen von
 * `Illuminate\Mail\Mailable`) statt auf „liegt irgendwo unter app/Mail":
 * ein Trait wie `RidesALane` selbst, oder eine künftige Basisklasse, ist
 * keine versendbare Mail, braucht keine Spur und soll den Test nicht rot
 * machen, nur weil sie im selben Ordner liegt.
 */
it('lässt keine Mailklasse ohne Spur', function () {
    $missing = collect(File::allFiles(app_path('Mail')))
        ->map(fn ($file) => 'App\\Mail\\'.str_replace(
            [DIRECTORY_SEPARATOR, '.php'], ['\\', ''], $file->getRelativePathname()
        ))
        ->filter(fn (string $class) => is_subclass_of($class, Mailable::class))
        ->reject(fn (string $class) => in_array(RidesALane::class, class_uses_recursive($class), true))
        ->values()
        ->all();

    expect($missing)->toBe([]);
});
