<?php

namespace App\Console\Commands;

use App\Models\Instance;
use App\Models\ProvisioningRun;
use App\Provisioning\Jobs\AdvanceRunJob;
use Illuminate\Console\Command;

/**
 * Traegt bestehenden Instanzen den Mailversand nach.
 *
 * Der Anlass: der Versand kam erst mit der Mitarbeiterverwaltung in die
 * Pipeline. Jede vorher gebaute Instanz kann bis heute keine Mail verschicken
 * — und die Einladung an einen Mitarbeiter geht von DORT aus, nicht von
 * CluPilot. Ohne diesen Lauf bliebe die Verwaltung fuer alle Altkunden tot.
 *
 * Ein Befehl und kein Zeitplan, aus derselben Begruendung wie bei
 * `clupilot:refresh-host-firewall`: das Loch ist endlich und schliesst sich
 * endgueltig; ein naechtlicher Lauf wuerde am Tag, an dem der Schritt in der
 * Pipeline still kaputtginge, fuer ihn einspringen und das Versagen
 * verbergen; und eine Reparatur, die der Betreiber anstoesst, ist eine, deren
 * Ausgabe er liest.
 */
class ConfigureInstanceMail extends Command
{
    protected $signature = 'clupilot:configure-instance-mail
        {--dry-run : auflisten, was liefe, und nichts anfassen}
        {--instance= : nur diese eine Instanz, per uuid}';

    protected $description = 'Traegt bestehenden Kundeninstanzen den Mailversand nach';

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

        /** @var array<string, int> reason => count */
        $skipped = [];

        $query = Instance::query()->where('status', 'active');

        if ($uuid = $this->option('instance')) {
            $query->where('uuid', $uuid);
        }

        foreach ($query->with('host')->get() as $instance) {
            $reason = $this->reasonToSkip($instance);

            if ($reason !== null) {
                $skipped[$reason] = ($skipped[$reason] ?? 0) + 1;

                continue;
            }

            $started++;

            if ($dryRun) {
                $this->line("{$instance->uuid}: würde nachgetragen");

                continue;
            }

            $run = ProvisioningRun::create([
                'pipeline' => 'instance-mail',
                'subject_type' => Instance::class,
                'subject_id' => $instance->id,
                'status' => ProvisioningRun::STATUS_PENDING,
                'current_step' => 0,
                'context' => [
                    'instance_id' => $instance->id,
                    'node' => $instance->host->node ?? 'pve',
                    'vmid' => (int) $instance->vmid,
                ],
            ]);

            AdvanceRunJob::dispatch($run->uuid);
            $this->info("{$instance->uuid}: Lauf {$run->id} gestartet");
        }

        foreach ($skipped as $reason => $count) {
            $this->line("übersprungen ({$reason}): {$count}");
        }

        $this->info($dryRun
            ? "Probelauf: {$started} Instanz(en) bekämen den Mailversand, ".array_sum($skipped).' übersprungen. Nichts wurde geändert.'
            : "{$started} Lauf/Läufe gestartet, ".array_sum($skipped).' übersprungen.');

        return self::SUCCESS;
    }

    /**
     * Warum diese Instanz uebergangen wird, oder null.
     *
     * Der Grund IST der Bericht: „2 uebergangen" allein ist keine Auskunft,
     * mit der jemand etwas anfangen kann — „2 haben keinen Host" und „2 laufen
     * bereits" sehen von hier aus gleich aus, sind aber völlig verschiedene
     * Reparaturen.
     */
    private function reasonToSkip(Instance $instance): ?string
    {
        if ($instance->host === null) {
            return 'kein Host';
        }

        if (blank($instance->vmid)) {
            return 'keine VMID';
        }

        if ($this->hasRunInFlight($instance)) {
            return 'anderer Lauf aktiv';
        }

        return null;
    }

    /**
     * JEDER Lauf gegen diese Instanz, absichtlich weit gefasst: das hier ist ein
     * Reparatur-Durchgang. Er läuft wieder, und eine heute übergangene Instanz ist
     * beim nächsten Aufruf dran. Nichts geht durch Warten verloren.
     */
    private function hasRunInFlight(Instance $instance): bool
    {
        return ProvisioningRun::query()
            ->where('subject_type', Instance::class)
            ->where('subject_id', $instance->id)
            ->inFlight()
            ->exists();
    }
}
