<?php

namespace App\Services\Mail;

use App\Console\Commands\PruneDormantAccounts;
use App\Mail\CloudResumedMail;
use App\Mail\CloudSuspendedMail;
use App\Mail\Concerns\SendsFromMailbox;
use App\Mail\DormantAccountWarningMail;
use App\Mail\DunningNoticeMail;
use App\Mail\InvoiceMail;
use App\Mail\MaintenanceAnnouncementMail;
use App\Mail\MaintenanceCancelledMail;
use App\Mail\NewDeviceSignInMail;
use App\Mail\OperatorInvitationMail;
use App\Mail\OperatorMessageMail;
use App\Mail\OrderConfirmationMail;
use App\Mail\ResetPasswordMail;
use App\Mail\SecurityBlockMail;
use App\Mail\ServiceEndingSoonMail;
use App\Mail\VerifyEmailMail;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\Invoice;
use App\Models\MaintenanceWindow;
use App\Models\Operator;
use App\Models\Order;
use App\Models\SecurityBlock;
use App\Models\User;
use App\Models\UserDevice;
use App\Services\Billing\DunningSchedule;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Support\Carbon;

/**
 * Every mail this application sends, built from sample data so it can be looked
 * at without waiting for the event that would send it.
 *
 * There is no other way to see most of these. An invoice mail needs an invoice, a
 * maintenance announcement needs a window, and "register an account to check
 * whether the confirmation reads well on a phone" is not a workflow — which is
 * how a mail with a hard 600px table went out for months before anybody opened
 * one on a telephone.
 *
 * ## Nothing is written
 *
 * Every record here is built with `make()`, never `create()`: a preview must not
 * leave an invoice, an order or a maintenance window behind in the database. The
 * models exist for the length of one render.
 *
 * The one exception is by omission: where a real record already exists, it is
 * NOT used. Rendering somebody's actual invoice to check a layout puts their
 * figures in an operator's inbox for no reason.
 */
class MailPreviews
{
    /**
     * The list, in the order somebody would read them: sign-up, purchase,
     * operation, money, security.
     *
     * Read from MailCatalogue rather than kept as a second list — that WAS
     * this list, before MailRoute needed a default purpose per mail type
     * too. Two lists is the shape of bug that forgets the seventeenth mail.
     *
     * @return array<string, string> key => label
     */
    public function all(): array
    {
        return array_map(fn (array $entry) => $entry['label'], MailCatalogue::all());
    }

    public function has(string $key): bool
    {
        return array_key_exists($key, $this->all());
    }

    /**
     * One mailable, filled with sample data.
     *
     * Returns null for a key nobody knows rather than throwing: this is reached
     * from a URL, and a URL is a string somebody can retype.
     */
    public function make(string $key): ?Mailable
    {
        if (! $this->has($key)) {
            return null;
        }

        $customer = $this->customer();
        $user = $this->user();

        return match ($key) {
            'verify-email' => new VerifyEmailMail($user),
            'reset-password' => new ResetPasswordMail($user, 'https://example.test/reset/beispiel-token', 60),
            'operator-invitation' => new OperatorInvitationMail(
                $this->invitedOperator(),
                'Ines Inhaber',
                'https://example.test/admin/invitation/beispiel-token',
                // Aus der Broker-Einstellung, nicht als Zahl daneben: eine
                // Vorschau, die eine andere Frist nennt als die echte Mail,
                // ist genau die Sorte Beleg, auf die sich niemand verlassen
                // kann.
                OperatorInvitationMail::validHours(),
            ),
            'order-confirmation' => new OrderConfirmationMail($this->order($customer), $customer->name),
            'cloud-ready' => $this->cloudReady($customer),
            'maintenance-announcement' => new MaintenanceAnnouncementMail($this->window(), $customer),
            'maintenance-cancelled' => new MaintenanceCancelledMail($this->window(), $customer),
            'service-ending-soon' => new ServiceEndingSoonMail($this->endingInstance($customer)),
            'invoice' => new InvoiceMail($this->invoice($customer), $customer->name),
            'new-device' => new NewDeviceSignInMail($customer->name, $this->device($user), 'web'),
            'dormant-warning' => new DormantAccountWarningMail($user, PruneDormantAccounts::WARN_DAYS_BEFORE),
            'dunning-0', 'dunning-1', 'dunning-2', 'dunning-3' => new DunningNoticeMail(
                name: $customer->name,
                level: (int) substr($key, -1),
                amountCents: 21480,
                feeCents: DunningSchedule::feeCents((int) substr($key, -1)),
                feeTotalCents: (int) substr($key, -1) >= 2 ? 500 : 0,
                currency: 'EUR',
                billingUrl: route('billing'),
                suspendOn: now()->addDays(14)->local()->isoFormat('D. MMMM YYYY'),
            ),
            'cloud-suspended' => new CloudSuspendedMail(
                name: $customer->name, amountCents: 21480, feeTotalCents: 1500,
                currency: 'EUR', billingUrl: route('billing'),
            ),
            'cloud-resumed' => new CloudResumedMail(
                name: $customer->name, amountCents: 0, feeTotalCents: 0,
                currency: 'EUR', billingUrl: route('cloud'),
            ),
            'security-block' => new SecurityBlockMail($this->securityBlock($customer)),
            'operator-message' => new OperatorMessageMail(
                $customer,
                'Zur Datenübernahme',
                "Guten Tag Bea Berger,\n\nich habe mir Ihre Ausgangslage angesehen. Die Übernahme ist möglich.\n\nMit freundlichen Grüßen\nCluPilot",
            ),
            default => null,
        };
    }

    // ---- The sample records. make(), never create(). ----

    private function customer(): Customer
    {
        return Customer::make([
            'name' => 'Beispiel GmbH',
            'contact_name' => 'Bea Berger',
            'email' => 'bea@beispiel.test',
            'locale' => 'de',
        ]);
    }

    private function user(): User
    {
        // The verification mail signs its URL from the model key, so the sample
        // needs one — 0 is enough for a link nobody is meant to follow.
        return User::make(['name' => 'Bea Berger', 'email' => 'bea@beispiel.test'])
            ->forceFill(['id' => 0, 'email_verified_at' => null]);
    }

    /**
     * Der eingeladene Betreiber. make(), nie create() — wie alles hier.
     *
     * Ein Betreiber und kein User: die Einladung geht in die Konsole, und die
     * beiden Personengruppen teilen keine Identität (R21). Ein Beispiel, das
     * die falsche Klasse benutzt, wäre eine Vorschau auf eine Mail, die es so
     * nie gibt.
     */
    private function invitedOperator(): Operator
    {
        return Operator::make(['name' => 'Tom Neu', 'email' => 'tom@clupilot.test'])
            ->forceFill(['id' => 0]);
    }

    private function order(Customer $customer): Order
    {
        return Order::make([
            'plan' => 'start',
            // 39,00 €: der Monatspreis von Start auf der neuen Leiter.
            'amount_cents' => 3900,
            'currency' => 'EUR',
            'datacenter' => 'fsn',
            'status' => 'paid',
        ])->forceFill(['id' => 0, 'created_at' => Carbon::now()]);
    }

    private function invoice(Customer $customer): Invoice
    {
        // The document renders from its own snapshot, so a preview only needs a
        // convincing snapshot — not a series, not a number drawn from one.
        return Invoice::make([
            'number' => 'RE-2026-0042',
            'issued_on' => Carbon::now()->toDateString(),
            'due_on' => Carbon::now()->addDays(14)->toDateString(),
            // Dieselben 39,00 € netto wie order() oben, plus 20 % USt.
            'net_cents' => 3900,
            'tax_cents' => 780,
            'gross_cents' => 4680,
            'currency' => 'EUR',
            'snapshot' => ['meta' => ['number' => 'RE-2026-0042']],
        ])->forceFill(['id' => 0, 'uuid' => '00000000-0000-0000-0000-000000000000']);
    }

    private function window(): MaintenanceWindow
    {
        return MaintenanceWindow::make([
            'title' => 'Sicherheitsupdates Nextcloud',
            'starts_at' => Carbon::now()->addDays(3)->setTime(2, 0),
            'ends_at' => Carbon::now()->addDays(3)->setTime(4, 0),
            'state' => 'scheduled',
        ])->forceFill(['id' => 0]);
    }

    private function device(User $user): UserDevice
    {
        return UserDevice::make([
            'guard' => 'web',
            'name' => 'Firefox auf Linux',
            'last_ip' => '203.0.113.42',
            'last_seen_at' => Carbon::now(),
        ])->forceFill(['id' => 0]);
    }

    /**
     * Eine Sperre samt der Instanz, an der sie hängt — beide nur im Speicher
     * (setRelation statt eines echten Fremdschlüssels), damit SecurityBlockMail
     * bis zum Kunden durchgreifen kann, ohne dass eine dieser drei Zeilen in
     * der Datenbank landet.
     */
    private function securityBlock(Customer $customer): SecurityBlock
    {
        $instance = Instance::make(['subdomain' => 'nc-beispiel', 'status' => 'active'])
            ->forceFill(['id' => 0])
            ->setRelation('customer', $customer);

        return SecurityBlock::make([
            'ip' => '203.0.113.7',
            'reason' => 'instance_login',
            'attempts' => 12,
            'strikes' => 1,
            'blocked_at' => Carbon::now(),
            'expires_at' => Carbon::now()->addHour(),
        ])->forceFill(['id' => 0])->setRelation('instance', $instance);
    }

    /**
     * Eine gekündigte Instanz sechs Tage vor dem Laufzeitende, mit
     * gewünschtem Export — die Kombination, die den zusätzlichen Absatz
     * zeigt. make(), nie create(), wie alles hier.
     */
    private function endingInstance(Customer $customer): Instance
    {
        return Instance::make([
            'subdomain' => 'nc-beispiel',
            'status' => 'cancellation_scheduled',
            'service_ends_at' => Carbon::now()->addDays(6),
            'export_wish' => true,
        ])->forceFill(['id' => 0])->setRelation('customer', $customer);
    }

    /**
     * The credentials mail is a notification rather than a Mailable.
     *
     * Built through its own toMail() so the preview shows the real thing, not a
     * second copy of the wording that would then drift.
     */
    private function cloudReady(Customer $customer): Mailable
    {
        $mail = new class($customer) extends Mailable
        {
            use SendsFromMailbox;

            public function __construct(public Customer $customer)
            {
                // The same mailbox the real notification sends from. Without
                // this the preview took the framework default — an address no
                // mailbox owns, over whichever account mail.default logs in
                // with, which is the "553 Sender address rejected" every
                // properly configured mail server answers with.
                $this->mailer(MailCatalogue::mailer('cloud-ready'));
            }

            public function envelope(): Envelope
            {
                return $this->mailboxEnvelope(MailPurpose::PROVISIONING, __('provisioning.mail.ready_subject'), 'cloud-ready');
            }

            public function content(): Content
            {
                // The same keys App\Notifications\CloudReady hands the view. A
                // preview that invented its own would render a mail nobody ever
                // receives, and drift from the real one without saying so.
                return new Content(view: 'mail.cloud-ready', with: [
                    'name' => $this->customer->name,
                    'url' => 'https://beispiel.clupilot.cloud',
                    'plan' => 'start',
                    'storage' => __('provisioning.mail.value_storage', ['gb' => 100]),
                    'location' => 'Falkenstein, Deutschland',
                    'dashboardUrl' => route('dashboard'),
                ]);
            }
        };

        return $mail;
    }
}
