<?php

namespace App\Console\Commands;

use App\Mail\ServiceEndingSoonMail;
use App\Models\Instance;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Throwable;

/**
 * Die Erinnerung, bevor EndInstanceService die Adresse einzieht.
 *
 * Am Laufzeitende zieht EndInstanceService die Route und den DNS-Eintrag
 * einer gekündigten Instanz ein — richtig so, das bleibt unangetastet. Die
 * Folge, die bisher niemand aussprach: ab genau diesem Moment kommt der Kunde
 * selbst nicht mehr an seine Nextcloud. Wer eigene Dateien herunterladen
 * will, muss es VORHER tun, und dieser Befehl ist der einzige Ort im Bestand,
 * der ihm das vorher sagt.
 *
 * Das Gegenstück zu App\Console\Commands\EndDueServices — der beendet, was
 * fällig ist, dieser hier kündigt an, was demnächst fällig wird. Dieselbe
 * Bauform: `--dry-run`, eine Zeile pro betroffener Instanz, eine
 * zusammenfassende Meldung am Ende.
 *
 * ## Die Auswahl: höchstens sieben Tage, nicht genau sieben
 *
 * Ein Gleichheitsvergleich auf einen Tag verfehlt jede Instanz, die zwischen
 * zwei täglichen Läufen durchrutscht — ein Lauf, der einen Tag ausfällt,
 * oder eine Instanz, die mit kurzer Frist gekündigt wurde und deren Ende von
 * Anfang an weniger als sieben Tage entfernt lag. Der Preis eines verfehlten
 * Vergleichs ist, dass jemand ausgesperrt wird, ohne es gewusst zu haben —
 * lieber spät erinnert als gar nicht. `export_reminded_at` sorgt dafür, dass
 * daraus trotzdem nur EINE Mail wird: eine Erinnerung im Sechserpack liest
 * sich als Mahnung.
 */
class RemindEndingServices extends Command
{
    protected $signature = 'clupilot:remind-ending-services
        {--dry-run : listet auf, wer erinnert würde, und ändert nichts}';

    protected $description = 'Erinnert Kunden einmalig ans Selbst-Herunterladen, bevor ihre gekündigte Cloud unerreichbar wird';

    public function handle(): int
    {
        $dryRun = (bool) $this->option('dry-run');
        $reminded = 0;

        $due = Instance::query()
            ->with('customer')
            ->where('status', 'cancellation_scheduled')
            ->whereNotNull('service_ends_at')
            // In der Zukunft und höchstens sieben Tage entfernt — siehe
            // Kopfkommentar. Eine bereits abgelaufene Instanz gehört
            // EndDueServices, nicht mehr dieser Erinnerung.
            ->where('service_ends_at', '>', now())
            ->where('service_ends_at', '<=', now()->addDays(7))
            ->whereNull('export_reminded_at')
            ->cursor();

        foreach ($due as $instance) {
            $customer = $instance->customer;

            if ($customer === null || blank($customer->email)) {
                // $customer === null ist über die Anwendung praktisch
                // unerreichbar: instances.customer_id ist ein
                // Pflicht-Fremdschlüssel mit Kaskade, und customers.email ist
                // NOT NULL. Trotzdem abgefangen statt vorausgesetzt — eine
                // Instanz ohne ansprechbaren Kunden soll den ganzen Lauf
                // nicht mitreißen, welcher Zustand sie auch dorthin gebracht
                // hätte. Laut, aber ohne export_reminded_at zu setzen: eine
                // Mail, die nie eine Adresse hatte, ist keine, die verschickt
                // wurde.
                Log::warning('Instanz nähert sich dem Laufzeitende, aber kein erreichbarer Kunde dahinter.', [
                    'instance' => $instance->uuid,
                ]);

                continue;
            }

            $this->line(($dryRun ? '[dry-run] ' : '')
                ."{$instance->subdomain}: Laufzeit endet am "
                .$instance->service_ends_at->local()->isoFormat('LL'));

            if ($dryRun) {
                $reminded++;

                continue;
            }

            try {
                $mail = new ServiceEndingSoonMail($instance);
                Mail::mailer($mail->mailer)->to($customer->email)->queue($mail);

                // In DERSELBEN Absicherung wie der Versand, nicht danach:
                // hinge ausgerechnet dieser Schreibzugriff (Datenbank kurz
                // weg), bliebe die Marke leer, obwohl die Mail schon
                // eingereiht ist — derselbe Kunde bekäme morgen eine zweite.
                // Und ein ungefangener Fehler hier würde den ganzen Lauf
                // abbrechen, statt nur diese eine Instanz zu überspringen —
                // die restlichen Instanzen des Tages kämen erst morgen an
                // die Reihe, ein Siebtel der sieben Tage Vorlauf verloren.
                $instance->update(['export_reminded_at' => now()]);
            } catch (Throwable $e) {
                Log::warning('Die Erinnerung vor dem Laufzeitende ließ sich nicht einreihen oder nicht vermerken.', [
                    'instance' => $instance->uuid,
                    'exception' => $e->getMessage(),
                ]);

                continue;
            }

            $reminded++;
        }

        $this->info($dryRun
            ? "Probelauf: {$reminded} Erinnerung(en) würden verschickt. Nichts wurde geändert."
            : "{$reminded} Erinnerung(en) verschickt.");

        return self::SUCCESS;
    }
}
