<?php

namespace classes;

use RuntimeException;

class hetzner_cloud_api_exception extends RuntimeException
{
    public function __construct(
        string $message,
        private readonly int $statusCode = 0,
        private readonly string $apiCode = ''
    ) {
        parent::__construct($message, $statusCode);
    }

    public function statusCode(): int
    {
        return $this->statusCode;
    }

    public function apiCode(): string
    {
        return $this->apiCode;
    }
}

class hetzner_cloud_client
{
    private const BASE_URL = 'https://api.hetzner.cloud/v1';

    public function __construct(private readonly string $token, private readonly int $timeoutSeconds = 8)
    {
        if (trim($token) === '') {
            throw new RuntimeException('Hetzner Cloud API token is required.');
        }
    }

    public function getLoadBalancer(int|string $id): array
    {
        return $this->request('GET', '/load_balancers/' . rawurlencode((string)$id))['load_balancer'] ?? [];
    }

    public function addIpTarget(int|string $loadBalancerId, string $ip): array
    {
        return $this->request('POST', '/load_balancers/' . rawurlencode((string)$loadBalancerId) . '/actions/add_target', [
            'type' => 'ip',
            'ip' => ['ip' => $ip],
        ]);
    }

    public function removeIpTarget(int|string $loadBalancerId, string $ip): array
    {
        return $this->request('POST', '/load_balancers/' . rawurlencode((string)$loadBalancerId) . '/actions/remove_target', [
            'type' => 'ip',
            'ip' => ['ip' => $ip],
        ]);
    }

    public function addService(int|string $loadBalancerId, string $protocol, int $listenPort, int $destinationPort, array $options = []): array
    {
        return $this->request('POST', '/load_balancers/' . rawurlencode((string)$loadBalancerId) . '/actions/add_service', self::servicePayload(
            $protocol,
            $listenPort,
            $destinationPort,
            $options
        ));
    }

    public function updateService(int|string $loadBalancerId, string $protocol, int $listenPort, int $destinationPort, array $options = []): array
    {
        return $this->request('POST', '/load_balancers/' . rawurlencode((string)$loadBalancerId) . '/actions/update_service', self::servicePayload(
            $protocol,
            $listenPort,
            $destinationPort,
            $options
        ));
    }

    private static function servicePayload(string $protocol, int $listenPort, int $destinationPort, array $options): array
    {
        $payload = [
            'protocol' => strtolower($protocol),
            'listen_port' => $listenPort,
            'destination_port' => $destinationPort,
            'proxyprotocol' => false,
        ];

        foreach (['health_check', 'http'] as $key) {
            if (isset($options[$key]) && is_array($options[$key])) {
                $payload[$key] = $options[$key];
            }
        }

        return $payload;
    }

    private function request(string $method, string $path, ?array $payload = null): array
    {
        $curl = curl_init(self::BASE_URL . $path);
        if ($curl === false) {
            throw new RuntimeException('Could not initialize Hetzner Cloud API request.');
        }

        $headers = [
            'Accept: application/json',
            'Authorization: Bearer ' . trim($this->token),
        ];

        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, min(3, $this->timeoutSeconds));
        curl_setopt($curl, CURLOPT_TIMEOUT, max(1, $this->timeoutSeconds));
        curl_setopt($curl, CURLOPT_NOSIGNAL, true);
        curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

        if ($payload !== null) {
            $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            if ($body === false) {
                throw new RuntimeException('Could not encode Hetzner Cloud API payload.');
            }
            $headers[] = 'Content-Type: application/json';
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
        }

        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

        $raw = curl_exec($curl);
        $error = curl_error($curl);
        $status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);

        if ($raw === false) {
            throw new RuntimeException('Hetzner Cloud API request failed: ' . $error);
        }

        $decoded = trim((string)$raw) === '' ? [] : json_decode((string)$raw, true);
        if (!is_array($decoded)) {
            $decoded = ['raw' => (string)$raw];
        }

        if ($status < 200 || $status >= 300) {
            $errorPayload = is_array($decoded['error'] ?? null) ? $decoded['error'] : [];
            $apiCode = (string)($errorPayload['code'] ?? $decoded['code'] ?? '');
            $message = (string)($errorPayload['message'] ?? $decoded['message'] ?? ('HTTP ' . $status));
            throw new hetzner_cloud_api_exception('Hetzner Cloud API request failed: ' . $message, $status, $apiCode);
        }

        return $decoded;
    }
}
