<?php

namespace WPDesk\ApiClient\Client;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use WPDesk\HttpClient\HttpClient;
use WPDesk\HttpClient\HttpClientResponse;
use WPDesk\ApiClient\Request\Request;
use WPDesk\ApiClient\Response\RawResponse;
use WPDesk\ApiClient\Response\Response;
use WPDesk\ApiClient\Serializer\Serializer;
use WPDesk\HttpClient\HttpClientRequestException;

class ClientImplementation implements Client, LoggerAwareInterface
{
    const CLIENT_VERSION = '1.6.5';

    const DEFAULT_TIMEOUT = 10;

    /** @var HttpClient */
    private $client;

    /** @var Serializer */
    private $serializer;

    /** @var LoggerInterface */
    private $logger;

    /** @var string */
    private $apiUrl;

    /** @var array */
    private $defaultRequestHeaders;

    /**
     * Client constructor.
     * @param HttpClient $client
     * @param Serializer $serializer
     * @param LoggerInterface $logger
     * @param string $apiUri
     * @param array $defaultRequestHeaders
     */
    public function __construct(
        HttpClient $client,
        Serializer $serializer,
        LoggerInterface $logger,
        $apiUri,
        array $defaultRequestHeaders
    ) {
        $this->client = $client;
        $this->serializer = $serializer;
        $this->logger = $logger;
        $this->apiUrl = $apiUri;
        $this->defaultRequestHeaders = $defaultRequestHeaders;
    }

    /**
     * Send given request trough HttpClient
     *
     * @param Request $request
     * @throws HttpClientRequestException
     * @return Response
     */
    public function sendRequest(Request $request)
    {
        $this->logger->debug("Sends request with METHOD: {$request->getMethod()}; to ENDPOINT {$request->getEndpoint()}",
            $this->getLoggerContext());
        try {
            $httpResponse = $this->client->send(
                $fullUrl = $this->prepareFullUrl($request),
                $method = $request->getMethod(),
                $body = $this->prepareRequestBody($request),
                $headers = $this->prepareRequestHeaders($request), self::DEFAULT_TIMEOUT
            );

            $this->logger->debug(
                "Sent request with: URL: {$fullUrl};\n METHOD: {$method};\n BODY: {$body};\n"
                . "HEADERS: " . json_encode($headers) . "\n\n and got response as CODE: {$httpResponse->getResponseCode()};\n"
                . "with RESPONSE BODY {$httpResponse->getBody()}",
                $this->getLoggerContext());


            return $this->mapHttpResponseToApiResponse($httpResponse);
        } catch (HttpClientRequestException $e) {
            $this->logger->error("Exception {$e->getMessage()}; {$e->getCode()} occurred while sending request");
            throw $e;
        }
    }

    /**
     * Returns full request url with endpoint
     *
     * @param Request $request
     * @return string
     */
    private function prepareFullUrl(Request $request)
    {
        $endpoint = $request->getEndpoint();
        if (strpos('http', $endpoint) === 0) {
            return $endpoint;
        }
        return $this->getApiUrl() . $endpoint;
    }

    /**
     * Map response from http client to api response using serializer
     *
     * @param HttpClientResponse $response
     * @return RawResponse
     */
    private function mapHttpResponseToApiResponse(HttpClientResponse $response)
    {
        $apiResponse = new RawResponse(
            $this->serializer->unserialize($response->getBody()),
            $response->getResponseCode(),
            $response->getHeaders()
        );

        return $apiResponse;
    }

    /**
     * Prepare serialized request body
     *
     * @param Request $request
     * @return string
     */
    private function prepareRequestBody(Request $request)
    {
        return $this->serializer->serialize($request->getBody());
    }

    /**
     * Prepares array of http headers
     *
     * @param Request $request
     * @return array
     */
    private function prepareRequestHeaders(Request $request)
    {
        $headers = array(
            'User-Agent' => 'saas-client-' . self::CLIENT_VERSION,
            'Accept-Encoding' => '*',
            'Content-Type' => $this->serializer->getMime()
        );
        $headers = array_merge($this->defaultRequestHeaders, $headers);
        return array_merge($headers, $request->getHeaders());
    }

    /**
     * @return HttpClient
     */
    public function getHttpClient()
    {
        return $this->client;
    }

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

    /**
     * @return Serializer
     */
    public function getSerializer()
    {
        return $this->serializer;
    }

    /**
     * Returns api url. Always without ending /
     *
     * @return string
     */
    public function getApiUrl()
    {
        return trim($this->apiUrl, '/');
    }

    /**
     * Sets logger
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * Returns logger context for
     *
     * @param string $additional_context Optional additional context
     * @return array
     */
    protected function getLoggerContext($additional_context = '')
    {
        $context = [
            Platform::LIBARY_LOGIN_CONTEXT,
            self::class
        ];
        if ($additional_context !== '') {
            $context[] = $additional_context;
        }
        return $context;
    }


}