<?php
/**
 * WARNING: Do not edit by hand, this file was generated by Crank:
 *
 *   https://github.com/gocardless/crank
 */

namespace GoCardlessPro\Core;

use GoCardlessPro\Core\Exception\ApiException;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Response;

/**
 * HTTP Client class wrapped by the Client class and
 * used internally to route http requests.
 */
class ApiClient
{
    private $http_client;
    public $error_on_idempotency_conflict;

    /**
     * @param GuzzleHttp\ClientInterface $http_client An HTTP client to make requests
     * @param array                      $config      configuration for the ApiClient
     */
    public function __construct(ClientInterface $http_client, array $config)
    {
        $this->http_client = $http_client;
        $this->error_on_idempotency_conflict = $config['error_on_idempotency_conflict'];
    }

    /**
     * Make a GET request to the API
     *
     * @param string $path   The relative path for the API request. e.g. /records
     * @param array  $params Any query params to send with the request
     *
     * @return array The raw response
     */
    public function get(string $path, array $params = []): Response
    {
        if (is_array($params) && array_key_exists("query", $params)) {
            $params["query"] = $this->castBooleanValuesToStrings($params["query"]);
        }

        $response = $this->http_client->request('GET', $path, $params);
        $this->handleErrors($response);
        return $response;
    }

    /**
     * Make a PUT request to the API
     *
     * @param string $path   The relative path for the API request. e.g. /records
     * @param array  $params Body of the request, will be serialized to JSON
     *
     * @return array The raw response
     */
    public function put(string $path, array $params): Response
    {
        $response = $this->http_client->request('PUT', $path, $params);
        $this->handleErrors($response);
        return $response;
    }

    /**
     * Make a POST request to the API
     *
     * @param string $path   The relative path for the API request. e.g. /records
     * @param array  $params Body of the request, will be serialized to JSON
     *
     * @return array The raw response
     */
    public function post(string $path, array $params): Response
    {
        $idempotencyKey = uniqid("", true);
        $paramsWithHeaders = array("headers" => array("Idempotency-Key" => $idempotencyKey));
        $params = array_replace_recursive($paramsWithHeaders, $params);

        $response = $this->http_client->request('POST', $path, $params);

        $this->handleErrors($response);
        return $response;
    }

    /**
     * Make a DELETE request to the API
     *
     * @param string $path   The relative path for the API request. e.g. /records
     * @param array  $params Body of the request, will be serialized to JSON
     *
     * @return array The raw response
     */
    public function delete(string $path, array $params): Response
    {
        $idempotencyKey = uniqid("", true);
        $paramsWithHeaders = array("headers" => array("Idempotency-Key" => $idempotencyKey));
        $params = array_replace_recursive($paramsWithHeaders, $params);

        $response = $this->http_client->request('DELETE', $path, $params);

        $this->handleErrors($response);
        return $response;
    }

    /**
     * Handle any errors in the API response
     * 
     * If the response status is 204 - No Content, then we can skip response body
     * JSON checks.
     *
     * If the response doesn't contain JSON, we will fail to decode and throw a
     * MalFormedResponseException.
     *
     * If the response is JSON, but the status code is >= 400, then we return
     * the appropriate error depending on the code
     *
     * @param GuzzleHttp\Psr7\Response $response The raw API response
     */
    private function handleErrors(Response $response)
    {
        if ($response->getStatusCode() === 204) {
            return null;
        }

        $json = json_decode($response->getBody());

        if ($json === null) {
            $msg = "Malformed response received from server";
            throw new Exception\MalformedResponseException($msg, $response);
        }

        $status_code = $response->getStatusCode();
        if ($status_code < 400) {
            return null;
        }

        $error = $json->error;
        $exception_class = (string) ApiException::getError($status_code, $error->type);
        $exception_class = 'GoCardlessPro\\Core\\Exception\\' . $exception_class;

        $api_response = new ApiResponse($response);
        throw new $exception_class($api_response);
    }

    /**
     * Recursively maps through an array, casting any booleans to strings, where
     * true should be casted as "true", and false as "false". We use this to
     * prepare query parameters to be sent to the API, since PHP's in-build
     * http_build_query casts booleans as "1" and "" respectively, which is odd.
     *
     * @param array $query An array to map through, casting booleans to strings
     *
     * @return array The new array with booleans casted to strings
     */
    private function castBooleanValuesToStrings(array $query): array
    {
        return array_map(
            function ($value) {
                if ($value === true) {
                    return "true";
                } elseif ($value === false) {
                    return "false";
                } elseif (is_array($value)) {
                    return $this->castBooleanValuesToStrings($value);
                } else {
                    return $value;
                }
            }, $query
        );
    }
}
