<?php
declare(strict_types=1);

namespace App\Service\Rest;

use App\Service\Utils\UrlBuilder;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * Base class for services sending requests to an external API
 */
class ApiRequestService implements ApiRequestInterface
{
    public function __construct(
        protected HttpClientInterface $client
    ) {}

    /**
     * Sends a GET request and returns the response's body decoded as json
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return array<mixed>
     * @throws HttpException
     * @throws \JsonException
     */
    public function getJsonContent(string $path, array $parameters = [], array $options = []): array
    {
        return json_decode($this->getContent($path, $parameters, $options), true, 512, JSON_THROW_ON_ERROR);
    }

    /**
     * Sends a GET request and returns the response's body
     *
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return string
     * @throws HttpException
     */
    public function getContent(string $path, array $parameters = [], array $options = []): string
    {
        try {
            return $this->get($path, $parameters, $options)->getContent();
        } catch (ClientExceptionInterface | TransportExceptionInterface | RedirectionExceptionInterface | ServerExceptionInterface $e) {
            throw new HttpException($e->getCode(), 'Request error : ' . $e->getMessage(), $e);
        }
    }

    /**
     * Sends a GET request and returns the response
     *
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return ResponseInterface
     * @throws HttpException
     */
    public function get(string $path, array $parameters = [], array $options = []): ResponseInterface
    {
        return $this->request('GET', $path, $parameters, $options);
    }

    /**
     * Sends a POST request and returns the response
     *
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return ResponseInterface
     * @throws HttpException
     */
    public function post(string $path, array $parameters = [], array $options = []): ResponseInterface
    {
        return $this->request('POST', $path, $parameters, $options);
    }

    /**
     * Sends a PUT request and returns the response
     *
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return ResponseInterface
     * @throws HttpException
     */
    public function put(string $path, array $parameters = [], array $options = []): ResponseInterface
    {
        return $this->request('PUT', $path, $parameters, $options);
    }

    /**
     * Sends a DELETE request and returns the response
     *
     * @param string $path
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return ResponseInterface
     * @throws HttpException
     */
    public function delete(string $path, array $parameters = [], array $options = []): ResponseInterface
    {
        return $this->request('DELETE', $path, $parameters, $options);
    }

    /**
     * Send an HTTP request to a REST API,
     * and return the decoded content of the response's body
     *
     * @param string $method
     * @param string $url
     * @param array<mixed> $parameters
     * @param array<mixed> $options
     * @return ResponseInterface
     * @throws HttpException
     */
    public function request(
        string $method,
        string $url,
        array $parameters = [],
        array $options = []
    ): ResponseInterface
    {
        $url = ltrim($url, '/');
        $url = UrlBuilder::concatParameters($url, $parameters);

        try {
            return $this->client->request($method, $url, $options);
        } catch (TransportExceptionInterface $e) {
            throw new HttpException($e->getCode(), 'Request error : ' . $e->getMessage(), $e);
        }
    }
}
