<?php

namespace App\Provisioning\Steps\Customer;

use App\Models\Instance;
use App\Models\ProvisioningRun;
use App\Provisioning\StepResult;
use App\Services\Monitoring\MonitoringClient;
use App\Services\Proxmox\ProxmoxClient;
use App\Support\ProvisioningSettings;
use Throwable;

/**
 * Backup and monitoring, after a plan change — checked, and almost always left
 * alone.
 *
 * Neither of them depends on the package today. Every instance is backed up
 * nightly at 02:00 whatever it costs, and the monitoring check is
 * `https://{subdomain}/status.php`, which a plan change does not move. So the
 * honest thing for this step to do in the ordinary case is nothing: deleting and
 * re-creating a vzdump job to arrive at the same schedule would be remote work
 * on a live host in exchange for a second job to keep in step with, and
 * re-registering a monitor would leave two checks alerting on one instance.
 *
 * It is a step rather than an omission for two reasons. It is the named place a
 * plan-dependent backup or monitoring rule goes the day there is one — the
 * alternative being that somebody adds it to whichever step they happen to be
 * reading. And there is one real gap it closes: an instance whose build
 * registered neither, because monitoring was unreachable and the run went on
 * DEGRADED, or because the build was interrupted after these steps. A plan
 * change is a reasonable moment to notice that and put it right.
 */
class SettlePlanServices extends CustomerStep
{
    public function __construct(
        private ProxmoxClient $pve,
        private MonitoringClient $monitoring,
    ) {}

    public function key(): string
    {
        return 'settle_plan_services';
    }

    public function maxDuration(): int
    {
        return 120;
    }

    public function execute(ProvisioningRun $run): StepResult
    {
        $instance = $this->instance($run);

        if ($instance === null) {
            return StepResult::fail('no_instance');
        }

        // Every package, the same nightly window — see the class docblock for
        // why this is read from one place rather than derived from the plan.
        $schedule = '02:00';

        if (! $instance->backups()->exists()) {
            $jobId = $this->pve->forHost($instance->host)
                ->createBackupJob((string) $run->context('node'), (int) $instance->vmid, $schedule);

            // Local row before anything else can fail, exactly as RegisterBackup
            // does it, so a retry finds the job rather than creating a second.
            $instance->backups()->firstOrCreate(
                ['external_job_id' => $jobId],
                ['schedule' => $schedule, 'status' => 'scheduled'],
            );
        }

        $this->settleMonitoring($run, $instance);

        return StepResult::advance();
    }

    /**
     * Monitoring is observability, not the product.
     *
     * The same judgement RegisterMonitoring makes during a build applies here
     * with more force: a customer's plan change must not fail because Kuma is
     * down. The gap is recorded on the run and the change goes through.
     */
    private function settleMonitoring(ProvisioningRun $run, Instance $instance): void
    {
        $url = 'https://'.$instance->subdomain.'.'.ProvisioningSettings::dnsZone().'/status.php';

        if ($instance->monitoringTargets()->where('url', $url)->exists()) {
            return;
        }

        try {
            $targetId = $this->monitoring->registerTarget($instance->subdomain, $url);
        } catch (Throwable $e) {
            $run->events()->create([
                'step' => $this->key(),
                'attempt' => $run->attempt,
                'outcome' => 'info',
                'message' => 'Monitoring übersprungen (Dienst nicht erreichbar): '.$e->getMessage(),
            ]);

            return;
        }

        $instance->monitoringTargets()->firstOrCreate(
            ['external_id' => $targetId],
            // 'unknown', not 'up': registering a check is not passing one.
            ['url' => $url, 'status' => 'unknown'],
        );
    }
}
