<?php

namespace App\Services\Mail;

use App\Models\Mailbox;
use App\Support\Settings;
use InvalidArgumentException;

/**
 * Which mailbox a given kind of mail goes out from.
 *
 * Read on every resolve rather than cached in a property: a queue worker lives
 * for hours, and a mapping cached at construction would keep sending from the
 * address that was configured when the worker started.
 */
final class MailboxResolver
{
    public function for(string $purpose): ?Mailbox
    {
        if (! in_array($purpose, MailPurpose::ALL, true)) {
            throw new InvalidArgumentException("Unknown mail purpose [{$purpose}].");
        }

        $box = $this->active($this->named(Settings::get(MailPurpose::settingKey($purpose))));

        if ($box !== null) {
            return $box;
        }

        // Fall back rather than fail: a mail that goes out from the wrong
        // address is recoverable, one that is never sent may not be noticed.
        return $purpose === MailPurpose::SYSTEM
            ? null
            : $this->active($this->named(Settings::get(MailPurpose::settingKey(MailPurpose::SYSTEM))));
    }

    /**
     * Das Postfach, das diese Art Mail TATSAECHLICH tragen wuerde — oder null.
     *
     * `for()` beantwortet „welches Postfach ist zugeordnet", das hier
     * beantwortet „welches kann senden": zugeordnet, aktiv, und mit
     * Zugangsdaten, falls es sich anmeldet. Das sind nicht dieselben Fragen.
     *
     * Entstanden aus einem gemeldeten Fehler (31.7.2026): die
     * „Passwort vergessen"-Mail kam nicht an, obwohl die Zustellung
     * eingeschaltet war. Die Bereitschaftsseite fragte eine DRITTE Frage — ob
     * ueberhaupt irgendein Postfach angelegt ist — und stand deshalb auf gruen,
     * waehrend jede Systemmail in der Warteschlange an
     * MailboxTransport::delegate() starb. Der Kunde las „sehen Sie in Ihr
     * Postfach", und im Postausgang stand nichts, weil RecordSentMail auf
     * MessageSent hoert: was nie gesendet wurde, hinterlaesst keine Zeile.
     *
     * `Mailbox::isConfigured()` bleibt die eine Stelle, die weiss, was
     * „kann senden" heisst; hier und im Transport wird sie nur gefragt.
     */
    public function usableFor(string $purpose): ?Mailbox
    {
        $box = $this->for($purpose);

        return $box !== null && $box->isConfigured() ? $box : null;
    }

    private function named(mixed $key): ?Mailbox
    {
        return is_string($key) && $key !== '' ? Mailbox::findByKey($key) : null;
    }

    /**
     * Deactivating a mailbox is an explicit operator action meaning "do not
     * use this" — it must fall back exactly like a mapping that points
     * nowhere or a row that is gone, so it is filtered here, at BOTH lookup
     * sites, rather than left for a caller to notice.
     *
     * A mailbox with no password is deliberately NOT filtered here: silently
     * sending support mail from billing@ because support@ has no password
     * yet is worse than a loud failure naming the mailbox that needs one —
     * that check stays a MailboxTransport concern, not this one.
     */
    private function active(?Mailbox $box): ?Mailbox
    {
        return $box?->active === true ? $box : null;
    }
}
