<?php

namespace App\Provisioning\Jobs;

use App\Models\Host;
use App\Services\Proxmox\ProxmoxClient;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;

/**
 * Asks every host whether it is still there.
 *
 * `hosts.last_seen_at` existed and drove the health dot in the console, and
 * nothing ever wrote it after onboarding — RegisterCapacity stamped it once and
 * that was the only write in the codebase. So every host read "offline" thirty
 * minutes after it was added, permanently, and the dot meant nothing. That is
 * also why host reachability could not appear on the status page: it was never
 * being measured.
 *
 * nodeStatus() is the cheapest question the API answers, and it is the right
 * one: it goes through the same credentials, the same network path and the same
 * token that provisioning depends on. A host that answers this can be worked
 * with; a host that cannot is down as far as this platform is concerned,
 * whatever a separate ICMP probe would say.
 *
 * Runs on the provisioning queue, which is where the Proxmox credentials are
 * usable.
 */
class PingHosts implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct()
    {
        $this->onQueue('provisioning');
    }

    public function handle(ProxmoxClient $proxmox): void
    {
        $hosts = Host::query()
            // Hosts still being onboarded, or already retired, are not a
            // statement about the platform's health.
            ->whereIn('status', ['active', 'error'])
            ->get();

        foreach ($hosts as $host) {
            try {
                $proxmox->forHost($host)->nodeStatus($host->node);

                // Only the timestamp. Whether a host is "active" or "error" is
                // an operator's decision and a provisioning outcome — a
                // heartbeat must not quietly promote a host somebody has
                // deliberately taken out of service.
                $host->forceFill(['last_seen_at' => now()])->save();
            } catch (Throwable) {
                // Deliberately silent, and deliberately not writing anything.
                // Absence IS the signal: healthState() reads the age of the
                // last successful answer, so a failure is recorded by the
                // timestamp not moving. Writing a failure count here would be a
                // second version of the same fact.
            }
        }
    }
}
