<?php

namespace App\Console\Commands;

use App\Actions\EndInstanceService;
use App\Models\Instance;
use Illuminate\Console\Command;

/**
 * End the services whose cancellation date has arrived.
 *
 * ConfirmCancelPackage schedules an ending — status `cancellation_scheduled`,
 * `service_ends_at` at the close of the paid period — and nothing in the
 * application ever kept the appointment. That is why `TraefikWriter::remove()`
 * had no caller: there was no moment at which an instance ended, so there was
 * never a moment at which its router came down. Cancelled instances kept their
 * hostname, their certificate and their route to a guest address the host may
 * later hand to a different customer.
 *
 * This is the appointment. It ends nothing early: EndInstanceService::hasEnded()
 * is the whole rule and a `cancellation_scheduled` instance whose date is still
 * in the future is left exactly alone — see that class for why that distinction
 * is the important one here.
 *
 * Hourly. The end of a paid term is a moment, and a sweep that ran once a night
 * would hand out up to a day of service nobody decided to give — while running
 * it every minute would mean a provider's bad five seconds became a failed job
 * sixty times over. Nothing here is taken away that the customer did not
 * themselves ask to give up, on a date they were shown, so there is no bad hour
 * for it.
 */
class EndDueServices extends Command
{
    protected $signature = 'clupilot:end-due-services
        {--dry-run : list what would end and change nothing}';

    protected $description = 'Withdraw the address of every instance whose cancellation date has passed';

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

        // Narrowed in the query for the sake of the database, decided by
        // hasEnded() for the sake of the rule: the query is an index, not a
        // second opinion.
        $due = Instance::query()
            ->with(['host', 'dnsRecords'])
            ->where('status', 'cancellation_scheduled')
            ->whereNotNull('service_ends_at')
            ->where('service_ends_at', '<=', now())
            ->cursor();

        foreach ($due as $instance) {
            if (! $end->hasEnded($instance)) {
                continue;
            }

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

            if ($dryRun || $end($instance)) {
                $ended++;
            }
        }

        $this->info($dryRun
            ? "Probelauf: {$ended} Dienst(e) würden beendet. Nichts wurde geändert."
            : "{$ended} Dienst(e) beendet.");

        return self::SUCCESS;
    }
}
