<?php

use App\Jobs\PacedMail;
use App\Mail\MaintenanceAnnouncementMail;
use App\Mail\MaintenanceCancelledMail;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\Mailbox;
use App\Models\MaintenanceWindow;
use App\Notifications\CloudReady;
use App\Services\Mail\MailCatalogue;
use App\Services\Mail\MailPurpose;
use App\Services\Maintenance\MaintenanceNotifier;
use App\Support\Settings;
use Illuminate\Mail\SendQueuedMailable;
use Illuminate\Support\Facades\Queue;

beforeEach(function () {
    config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)));
    clearMailboxSeed();
});

it('sends from the mailbox its purpose points at', function () {
    Mailbox::factory()->create([
        'key' => 'no-reply',
        'address' => 'no-reply@clupilot.com',
        'display_name' => 'CluPilot',
        'no_reply' => true,
    ]);
    Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply');

    $mail = new MaintenanceAnnouncementMail(
        MaintenanceWindow::factory()->create(),
        Customer::factory()->create(),
    );

    $envelope = $mail->envelope();

    expect($envelope->from->address)->toBe('no-reply@clupilot.com')
        ->and($envelope->from->name)->toBe('CluPilot');
});

it('omits Reply-To on a no-reply mailbox, because that is what the name promises', function () {
    Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]);
    Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply');

    $envelope = (new MaintenanceAnnouncementMail(
        MaintenanceWindow::factory()->create(),
        Customer::factory()->create(),
    ))->envelope();

    expect($envelope->replyTo)->toBeEmpty();
});

it('sets Reply-To on an answerable mailbox, so a customer reply reaches a human', function () {
    Mailbox::factory()->create(['key' => 'support', 'address' => 'support@clupilot.com', 'no_reply' => false]);
    Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'support');

    $envelope = (new MaintenanceAnnouncementMail(
        MaintenanceWindow::factory()->create(),
        Customer::factory()->create(),
    ))->envelope();

    expect($envelope->replyTo[0]->address)->toBe('support@clupilot.com');
});

it('names the purpose mailer so the queue worker picks the right transport', function () {
    // A bare, never-queued mailable proves nothing here: the constructor sets
    // $this->mailer correctly regardless of what MaintenanceNotifier does.
    // Illuminate\Mail\Mailer::queue() (vendor Mailer.php) runs
    // `$view->mailer($this->name)->queue(...)` — if MaintenanceNotifier queues
    // through the bare `Mail` facade instead of `Mail::mailer($mail->mailer)`,
    // that resolves the DEFAULT mailer ('array' in tests) and overwrites the
    // mailable's own mailer with it BEFORE the queued job is even built. The
    // only way to see that is to actually queue it and inspect the job that
    // comes out the other side.
    //
    // The name is the MAIL TYPE's mailer, not the purpose's: since the routing
    // fix, `mail.route.<mailart>` moves the SMTP account as well as the From
    // address, so each catalogued type has a mailer of its own. Asked through
    // MailCatalogue rather than spelled out, so this assertion cannot drift
    // from the one list that names them.
    Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]);
    Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply');

    Queue::fake();

    $window = MaintenanceWindow::factory()->create();
    $customer = Customer::factory()->create();

    app(MaintenanceNotifier::class)->deliver(
        $window,
        $customer,
        'announcement',
        new MaintenanceAnnouncementMail($window, $customer),
    );

    // PacedMail statt SendQueuedMailable: seit dem Versandtakt tauscht eine
    // Bindung im Container den Auftrag für jede Mail aus, und Queue::fake()
    // legt ihn unter seinem EXAKTEN Klassennamen ab — ein Oberklassenname
    // trifft dort nichts. Der Mailer, um den es hier geht, ist unberührt.
    Queue::assertPushed(PacedMail::class, function (SendQueuedMailable $job) {
        return $job->mailable->mailer === MailCatalogue::mailer('maintenance-announcement');
    });
});

it('gives the cancellation mail the same sender as the announcement', function () {
    Mailbox::factory()->create(['key' => 'no-reply', 'address' => 'no-reply@clupilot.com', 'no_reply' => true]);
    Settings::set(MailPurpose::settingKey(MailPurpose::MAINTENANCE), 'no-reply');

    $mail = new MaintenanceCancelledMail(
        MaintenanceWindow::factory()->create(),
        Customer::factory()->create(),
    );

    expect($mail->envelope()->from->address)->toBe('no-reply@clupilot.com')
        ->and($mail->mailer)->toBe(MailCatalogue::mailer('maintenance-cancelled'));
});

it('falls back to the framework default envelope when no mailbox is configured for the purpose', function () {
    // Neither a purpose mapping nor a system fallback exists anywhere in
    // Settings — MailboxResolver::for() returns null. An installation
    // mid-setup must still hand back a usable Envelope (subject intact)
    // rather than dereferencing a null mailbox.
    $window = MaintenanceWindow::factory()->create();

    $envelope = (new MaintenanceAnnouncementMail($window, Customer::factory()->create()))->envelope();

    expect($envelope->from)->toBeNull()
        ->and($envelope->replyTo)->toBeEmpty()
        ->and($envelope->subject)->toBe(__('maintenance.mail_subject', ['title' => $window->title]));
});

it('sends the provisioning notification from the provisioning mailbox', function () {
    Mailbox::factory()->create([
        'key' => 'no-reply',
        'address' => 'no-reply@clupilot.com',
        'display_name' => 'CluPilot',
        'no_reply' => true,
    ]);
    Settings::set(MailPurpose::settingKey(MailPurpose::PROVISIONING), 'no-reply');

    $notification = new CloudReady(Instance::factory()->create());

    $message = $notification->toMail(new stdClass);

    expect($message->from[0])->toBe('no-reply@clupilot.com')
        ->and($message->mailer)->toBe(MailCatalogue::mailer('cloud-ready'))
        // no_reply is set, so no Reply-To — the same promise as everywhere else.
        ->and($message->replyTo)->toBeEmpty();
});

it('sets Reply-To on the provisioning notification when its mailbox allows replies', function () {
    // The mirror image of the no-reply case above: a support-style mailbox
    // behind PROVISIONING must still get a Reply-To, the same as maintenance
    // does. Without this, CloudReady's `if (! $box->no_reply)` branch is only
    // ever exercised by the no_reply=true side and a deleted replyTo() call
    // would go unnoticed.
    Mailbox::factory()->create(['key' => 'support', 'address' => 'support@clupilot.com', 'no_reply' => false]);
    Settings::set(MailPurpose::settingKey(MailPurpose::PROVISIONING), 'support');

    $notification = new CloudReady(Instance::factory()->create());

    $message = $notification->toMail(new stdClass);

    expect($message->replyTo[0][0])->toBe('support@clupilot.com');
});

it("leaves the provisioning notification's sender fields at MailMessage's defaults when no mailbox is configured", function () {
    // The mirror of the trait's own null-mailbox fallback test above, but for
    // CloudReady's own guard: neither a mailbox row nor a provisioning/system
    // Settings mapping exists, so MailboxResolver::for() returns null. Must
    // not throw dereferencing a null mailbox, and must leave from/replyTo
    // exactly as `new MailMessage` starts them — [] and [].
    $notification = new CloudReady(Instance::factory()->create());

    $message = $notification->toMail(new stdClass);

    expect($message->from)->toBeEmpty()
        ->and($message->replyTo)->toBeEmpty()
        ->and($message->mailer)->toBe(MailCatalogue::mailer('cloud-ready'));
});
