<?php
/**
 * Minimal client for the ProSolutionX CDN.
 *
 *   $cdn = new CdnClient(getenv('CDN_API_KEY'));
 *   $asset = $cdn->upload('/path/photo.jpg', ['slug' => 'blue-widget-hero']);
 *   echo $asset['url'];
 *
 * PHP 8.0+ with ext-curl. No dependencies.
 */

class CdnException extends RuntimeException
{
    public function __construct(string $message, public readonly int $status = 0, public readonly mixed $body = null)
    {
        parent::__construct($message, $status);
    }
}

class CdnClient
{
    public function __construct(
        private string $apiKey,
        private string $baseUrl = 'https://cdn.prosolutionx.com',
    ) {
        if ($apiKey === '') {
            throw new InvalidArgumentException('apiKey is required');
        }
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    private function request(string $method, string $path, array $options = []): mixed
    {
        $ch = curl_init($this->baseUrl . $path);
        $headers = ['Authorization: Bearer ' . $this->apiKey];

        if (isset($options['json'])) {
            $headers[] = 'Content-Type: application/json';
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($options['json']));
        } elseif (isset($options['form'])) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $options['form']);
        }

        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_TIMEOUT        => 120,
        ]);

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

        if ($raw === false) {
            throw new CdnException('Request failed: ' . $error);
        }

        $body = json_decode($raw, true);
        if ($status >= 400) {
            throw new CdnException($body['error'] ?? ('HTTP ' . $status), $status, $body);
        }
        return $body;
    }

    /** Who am I — a cheap way to check a key is live. */
    public function me(): array
    {
        return $this->request('GET', '/api/v1/me');
    }

    /** @param array{slug?:string,alt?:string,title?:string} $opts */
    public function upload(string $path, array $opts = []): array
    {
        if (!is_readable($path)) {
            throw new InvalidArgumentException("Cannot read $path");
        }
        $form = ['file' => new CURLFile($path)];
        // The slug becomes the readable part of the URL. Omit it and the
        // filename is used instead.
        foreach (['slug', 'alt', 'title'] as $key) {
            if (!empty($opts[$key])) {
                $form[$key] = $opts[$key];
            }
        }
        return $this->request('POST', '/api/v1/assets', ['form' => $form]);
    }

    public function list(int $limit = 50, ?int $cursor = null, ?string $q = null): array
    {
        $params = ['limit' => $limit];
        if ($cursor !== null) $params['cursor'] = $cursor;
        if ($q !== null)      $params['q'] = $q;
        return $this->request('GET', '/api/v1/assets?' . http_build_query($params));
    }

    public function get(string|int $idOrSlug): array
    {
        return $this->request('GET', '/api/v1/assets/' . rawurlencode((string) $idOrSlug));
    }

    /** Rename, or change alt / title. Renaming changes the readable URL. */
    public function update(string|int $idOrSlug, array $changes): array
    {
        return $this->request('PATCH', '/api/v1/assets/' . rawurlencode((string) $idOrSlug), ['json' => $changes]);
    }

    /** Replace the bytes behind an existing name. */
    public function replace(string|int $idOrSlug, string $path): array
    {
        return $this->request('PUT', '/api/v1/assets/' . rawurlencode((string) $idOrSlug), [
            'form' => ['file' => new CURLFile($path)],
        ]);
    }

    public function remove(string|int $idOrSlug): array
    {
        return $this->request('DELETE', '/api/v1/assets/' . rawurlencode((string) $idOrSlug));
    }

    /** Build a resized / converted URL from one the API returned. */
    public function transform(string $url, array $opts = []): string
    {
        $allowed = array_intersect_key($opts, array_flip(['w', 'f', 'q']));
        if (!$allowed) return $url;
        $sep = str_contains($url, '?') ? '&' : '?';
        return $url . $sep . http_build_query($allowed);
    }
}
