<?php

namespace App\Services\Billing;

use App\Mail\CloudResumedMail;
use App\Mail\CloudSuspendedMail;
use App\Mail\DunningNoticeMail;
use App\Models\DunningCase;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Throwable;

/**
 * Wer bei einem Mahnfall was erfährt — an einer Stelle.
 *
 * Der Versand liegt nicht im Tageslauf und nicht in den Actions, weil er an
 * DREI Stellen ausgelöst wird (Fall eröffnet, Stufe erreicht, Fall beglichen)
 * und dreimal dieselbe Frage beantworten muss: an welche Adresse, mit welchem
 * Betrag, mit welchen Gebühren.
 *
 * **Ein Versand darf nie den Vorgang kippen.** Ein Mailserver, der gerade nicht
 * antwortet, darf keinen Fall daran hindern, weiterzurücken — sonst bleibt der
 * Kunde auf der Stufe stehen, auf der die Mail scheiterte, und die Sperre
 * kommt nie. Deshalb wird geloggt und geschluckt; die Mail selbst ist ohnehin
 * `ShouldQueue` und wird dort erneut versucht.
 */
class DunningMailer
{
    public function level(DunningCase $case, int $level): void
    {
        $customer = $case->subscription?->customer;
        $address = $customer?->email;

        if (blank($address)) {
            return;
        }

        $gebuehren = $this->feesSoFar($case);

        $this->send($address, new DunningNoticeMail(
            name: (string) ($customer->name ?? ''),
            level: $level,
            amountCents: (int) ($case->subscription->price_cents ?? 0),
            feeCents: DunningSchedule::feeCents($level),
            feeTotalCents: $gebuehren,
            currency: 'EUR',
            billingUrl: route('billing'),
            // Das Datum, an dem abgeschaltet wird — aber nur, solange es noch
            // bevorsteht. Eine Drohung ohne Termin ist keine Auskunft, und ein
            // Termin in der Vergangenheit wäre schlimmer als keiner.
            suspendOn: $level < DunningSchedule::SUSPENDED
                ? $case->opened_at->copy()
                    ->addDays(DunningSchedule::dayOfLevel(DunningSchedule::SUSPENDED))
                    ->local()->isoFormat('D. MMMM YYYY')
                : null,
        ));
    }

    public function suspended(DunningCase $case): void
    {
        $customer = $case->subscription?->customer;

        if (blank($customer?->email)) {
            return;
        }

        $this->send($customer->email, new CloudSuspendedMail(
            name: (string) ($customer->name ?? ''),
            amountCents: (int) ($case->subscription->price_cents ?? 0),
            feeTotalCents: $this->feesSoFar($case),
            currency: 'EUR',
            billingUrl: route('billing'),
        ));
    }

    public function resumed(DunningCase $case): void
    {
        $customer = $case->subscription?->customer;

        if (blank($customer?->email)) {
            return;
        }

        $this->send($customer->email, new CloudResumedMail(
            name: (string) ($customer->name ?? ''),
            // Beglichen ist beglichen: keine Zahl mehr, die nach Restschuld
            // aussieht.
            amountCents: 0,
            feeTotalCents: 0,
            currency: 'EUR',
            billingUrl: route('cloud'),
        ));
    }

    /** Alle bisher angefallenen Mahnspesen dieses Falls. */
    private function feesSoFar(DunningCase $case): int
    {
        return collect(array_keys($case->fee_invoice_ids ?? []))
            ->sum(fn ($level) => DunningSchedule::feeCents((int) $level));
    }

    private function send(string $address, object $mailable): void
    {
        try {
            Mail::mailer($mailable->mailer)->to($address)->queue($mailable);
        } catch (Throwable $e) {
            // Siehe Kopfkommentar: laut, aber nicht kippend.
            Log::error('Eine Mahnungs-Mail liess sich nicht einreihen', [
                'mailable' => $mailable::class,
                'exception' => $e->getMessage(),
            ]);
        }
    }
}
