<?php

namespace App\Services\Dns;

use App\Services\Secrets\SecretVault;
use App\Support\ProvisioningSettings;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use RuntimeException;

/**
 * Hetzner DNS API client. Zone from ProvisioningSettings (console-stored,
 * falling back to CLUPILOT_DNS_ZONE); the token comes from the vault
 * (console-stored, falling back to HETZNER_DNS_TOKEN).
 * Not unit-tested against the real service (live I/O).
 */
class HttpHetznerDnsClient implements HetznerDnsClient
{
    private function http(): PendingRequest
    {
        // Read HERE, at the point of use, rather than in a constructor or
        // overlaid onto config at boot — see SecretVault's own docblock (rule
        // 3): an overlay would add a query to every request, and a
        // long-running queue worker would keep whatever token was true when
        // it started.
        return Http::withHeaders(['Auth-API-Token' => (string) app(SecretVault::class)->get('dns.token')])
            ->baseUrl('https://dns.hetzner.com/api/v1')
            ->timeout(15);
    }

    private function zoneId(): string
    {
        $zone = ProvisioningSettings::dnsZone();
        $zones = $this->http()->get('/zones', ['name' => $zone])->throw()->json('zones', []);

        return $zones[0]['id'] ?? throw new RuntimeException("DNS zone {$zone} not found");
    }

    public function upsertRecord(string $fqdn, string $type, string $value): string
    {
        $zoneId = $this->zoneId();
        $name = rtrim(str_replace('.'.ProvisioningSettings::dnsZone(), '', $fqdn), '.');

        $existing = collect($this->http()->get('/records', ['zone_id' => $zoneId])->throw()->json('records', []))
            ->first(fn ($r) => $r['name'] === $name && $r['type'] === $type);

        if ($existing) {
            $this->http()->put("/records/{$existing['id']}", [
                'zone_id' => $zoneId, 'type' => $type, 'name' => $name, 'value' => $value, 'ttl' => 300,
            ])->throw();

            return (string) $existing['id'];
        }

        return (string) $this->http()->post('/records', [
            'zone_id' => $zoneId, 'type' => $type, 'name' => $name, 'value' => $value, 'ttl' => 300,
        ])->throw()->json('record.id');
    }

    public function deleteRecord(string $recordId): void
    {
        $response = $this->http()->delete("/records/{$recordId}");

        // A record that is already gone is the outcome we wanted. Without this,
        // a lost response or a retry after a later failure turns every further
        // attempt into a 404 — and a host that can then never be purged.
        if ($response->notFound()) {
            return;
        }

        $response->throw();
    }
}
