<?php

namespace App\Livewire\Admin;

use App\Services\Mail\MailPreviews;
use App\Support\MailDelivery;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Throwable;

/**
 * Look at a mail before a customer does.
 *
 * There was no way to. An invoice mail needs an invoice, a maintenance
 * announcement needs a window, and "register an account to see whether the
 * confirmation reads well on a phone" is not a workflow — which is how every
 * mail went out with a hard 600px table until somebody opened one on a
 * telephone and had to scroll sideways to read a sentence.
 *
 * Two ways to look: in the browser, which answers the desktop question at once,
 * and as a real mail to the operator's OWN address, which is the only way to
 * answer the phone question. Never to a customer: a preview is for us.
 */
#[Layout('layouts.admin')]
class MailPreview extends Component
{
    public function mount(): void
    {
        $this->authorize('mail.manage');
    }

    /**
     * Send one to the signed-in operator.
     *
     * To themselves and nobody else — no address field. A preview that can be
     * addressed is a form for sending mail to strangers from inside the console,
     * which is not what this is for and not something to leave lying about.
     */
    public function sendToMe(string $key): void
    {
        $this->authorize('mail.manage');

        $operator = Auth::guard('operator')->user();
        $mailable = app(MailPreviews::class)->make($key);

        if ($operator === null || $mailable === null) {
            return;
        }

        try {
            // sendNow(), not send(): every mailable here implements ShouldQueue,
            // and send() silently defers to queue() for those — which would put
            // a failure in a worker's log while the button reported success. The
            // person pressing it is waiting for the mail, so the send happens in
            // their request and its failure reaches them.
            //
            // Through the mailable's OWN mailer. Mail::to() hands back a pending
            // mail bound to the DEFAULT mailer, and sendNow() then ignores the
            // mailer the mailable asked for — so a support mail addressed
            // support@ went out over the no-reply@ login and the server answered
            // "553 Sender address rejected: not owned by user". Every purpose
            // mailbox here has its own SMTP account, and the From has to belong
            // to the account that authenticates.
            Mail::mailer($mailable->mailer ?: null)
                ->to($operator->email)
                ->sendNow($mailable);
        } catch (Throwable $e) {
            $this->dispatch('notify', message: __('mail_preview.failed', [
                'reason' => mb_substr($e->getMessage(), 0, 160),
            ]));

            return;
        }

        $this->dispatch('notify', message: __('mail_preview.sent', ['email' => $operator->email]));
    }

    public function render()
    {
        $this->authorize('mail.manage');

        $previews = app(MailPreviews::class)->all();

        return view('livewire.admin.mail-preview', [
            'previews' => $previews,
            // Read off the mailables rather than kept as a second list: the
            // sender is decided by the purpose mailbox, and a table here would
            // be a copy that drifts.
            // From the envelope, not from the mailable's `from` property: that
            // property stays empty until the mail is built, so reading it here
            // showed nothing at all.
            'senders' => collect($previews)
                ->keys()
                ->mapWithKeys(fn (string $key) => [
                    $key => app(MailPreviews::class)->make($key)?->envelope()->from?->address,
                ])
                ->all(),
            'me' => Auth::guard('operator')->user()?->email ?? '',
            // A preview that goes nowhere is worse than none: with a
            // non-delivering mailer the browser view still works and the send
            // silently writes to a file.
            'delivers' => MailDelivery::delivers(),
        ]);
    }
}
