<?php

namespace App\Provisioning\Steps\Host;

use App\Models\ProvisioningRun;
use App\Provisioning\StepResult;
use App\Services\Dns\HostDnsDirectory;
use App\Support\HostName;
use Throwable;

/**
 * Gives the host a name: fsn-01.node.clupilot.com.
 *
 * The name itself is not chosen here — it was assigned once, at creation
 * (App\Support\HostName::claim, called from StartHostOnboarding), and this
 * step only publishes it. This entry points at the host's WireGuard address,
 * NOT its public IP, and it is written to the vpn-dns container's --hostsdir
 * — resolvable only inside the tunnel — rather than the public Hetzner zone.
 * The name is for us, so an operator can ssh fsn-01.node.… instead of
 * memorising addresses; publishing it in PUBLIC DNS would hand every scanner
 * the internal subnet and roughly how many hosts sit behind it, which is the
 * one thing the whole network design avoids. See App\Services\Dns\HostDnsDirectory.
 *
 * DNS is convenience, not a prerequisite for a working host: a failure here
 * logs and moves on rather than stranding an otherwise finished onboarding.
 */
class RegisterHostDns extends HostStep
{
    public function __construct(private HostDnsDirectory $dns) {}

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

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

        if (blank($host->wg_ip)) {
            return StepResult::fail('The host has no management address to publish.');
        }

        // Der Name steht seit dem Anlegen fest (StartHostOnboarding →
        // HostName::claim). Dieser Schritt bildete ihn früher ein zweites Mal
        // — daher kamen zwei Namen für dieselbe Maschine, von denen nur einer
        // auflöste. Er veröffentlicht jetzt nur noch, was schon gilt.
        $name = $host->name;
        $fqdn = HostName::fqdn($name);

        try {
            $this->dns->write($name, $fqdn, $host->wg_ip);
        } catch (Throwable $e) {
            // Retried first: the write is idempotent (overwrite-by-name), so a
            // retry after a transient failure (e.g. the shared volume briefly
            // unavailable) just rewrites the same entry rather than risking a
            // half-written file.
            $allowed = min(3, (int) $run->max_attempts);
            if ($run->attempt + 1 < $allowed) {
                return StepResult::retry(30, 'DNS unavailable: '.$e->getMessage());
            }

            // Out of attempts: a name is convenience, and a host that is
            // otherwise ready should not be stranded for the want of one.
            $run->events()->create([
                'step' => $this->key(),
                'outcome' => 'info',
                'message' => 'DNS name could not be registered: '.$e->getMessage(),
            ]);

            return StepResult::advance();
        }

        return StepResult::advance();
    }
}
