<?php

namespace Opentalent\OtCore\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Opentalent\OtCore\Exception\ApiRequestException;
use Opentalent\OtCore\Logging\OtLogger;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Core\Environment;

class OpentalentApiService
{
    protected object $client;
    protected object $context;

    /**
     * @param object|null $client [For tests only]
     * @param object|null $context [For tests only]
     * @param object|null $cookieJar
     */
    public function __construct(
        ?object $client = null,
        ?object $context = null,
        ?object $cookieJar = null
    ) {
        // Get the current context (prod, dev...)
        if ($context === null) {
            $this->context = Environment::getContext();
        } else {
            $this->context = $context;
        }

        if ($client === null) {
            $args = ['base_uri' => $this->getApiUri()];
            if ($cookieJar != null) {
                $args['cookies'] = $cookieJar;
            }
            $this->client = new Client($args);
        } else {
            $this->client = $client;
        }
    }

    public function getClient() {
        return $this->client;
    }

    /**
     * @param Client $client
     */
    public function injectClient(Client $client) {
        $this->client = $client;
    }

    /**
     * Return the API URI for the current repository
     *
     * @param string $trailing_part
     * @return string
     */
    public function getApiUri(string $trailing_part = ""): string
    {
        $uri = OpentalentEnvService::get('API_BASE_URI');
        return rtrim($uri, '/') . '/' . ltrim($trailing_part, '/');
    }

    /**
     * Send a request to the API and
     * returns the Json response as an array
     *
     * @param string $uri
     * @param array $params
     * @return array
     * @throws ApiRequestException
     * @throws \JsonException
     */
    public function getJsonDecoded(
        string $uri,
        array $params = []
    ): array
    {
        $body = $this->getBody($uri, $params);
        $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        if ($data !== null) {
            return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
        }

        throw new \JsonException('Response can not be decoded as json: ' . substr($body, 0, 300) . '...');
    }

    /**
     * Send a request to the API and returns
     * the response's body as a string
     *
     * @param string $uri
     * @param array $params
     * @return string
     * @throws ApiRequestException
     */
    public function getBody(
        string $uri,
        array $params = []
    ): string
    {
        return (string)$this->get($uri, $params)->getBody();
    }

    /**
     * Send a GET request to the API and returns
     * the result as a Response object
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     * @return ResponseInterface
     * @throws ApiRequestException
     */
    public function get(
        string $uri,
        array $params = [],
        array $options = []
    ): ResponseInterface
    {
        return $this->request('GET', $uri, $params, $options);
    }

    /**
     * Send a request to the API and returns
     * the result as a Response object
     *
     * @param string $httpMethod
     * @param string $uri
     * @param array $params
     * @param array $options
     * @return ResponseInterface
     * @throws ApiRequestException
     */
    public function request(
        string $httpMethod,
        string $uri,
        array $params = [],
        array $options = []
    ): ?ResponseInterface
    {
        $parsedUrl = parse_url($uri);

        $query = http_build_query($params);
        if (isset($parsedUrl['query'])) {
            $query = '&' . $query;
        }

        $uri = rtrim($uri, '/');
        if ($query) {
            $uri .= '?' . $query;
        }

        try {
            if ($this->context->isDevelopment()) {
                OtLogger::info('API Call: ' . $uri);
            }
            return $this->client->request($httpMethod, $uri, $options);
        } catch (GuzzleException $e) {
            throw ApiRequestException::from_exception($e);
        }
    }
}
