<?php

namespace Analyst;

use Exception;
use Analyst\Contracts\HttpClientContract;
use Analyst\Contracts\RequestorContract;

class ApiRequestor implements RequestorContract
{
	/**
	 * Supported http client
	 *
	 * @var HttpClientContract
	 */
	protected $httpClient;

	/**
	 * @var string
	 */
	protected $clientId;

	/**
	 * @var string
	 */
	protected $clientSecret;

	/**
	 * @var string
	 */
	protected $apiBase;

	/**
	 * Default headers to be sent
	 *
	 * @var array
	 */
	protected $defaultHeaders = [
		'accept' => 'application/json',
		'content-type' => 'application/json'
	];

	/**
	 * Prioritized http clients
	 *
	 * @var array
	 */
	protected $availableClients = [
		'Analyst\Http\WordPressHttpClient',
		'Analyst\Http\CurlHttpClient',
		'Analyst\Http\DummyHttpClient',
	];

	/**
	 * ApiRequestor constructor.
	 * @param $id
	 * @param $secret
	 * @param $apiBase
	 * @throws \Exception
	 */
	public function __construct($id, $secret, $apiBase)
	{
		$this->clientId = $id;
		$this->clientSecret = $secret;

		$this->setApiBase($apiBase);

		$this->httpClient = $this->resolveHttpClient();
	}

	/**
	 * Set api base url
	 *
	 * @param $url
	 */
	public function setApiBase($url)
	{
		$this->apiBase = $url;
	}

	/**
	 * Get request
	 *
	 * @param $url
	 * @param array $headers
	 * @return mixed
	 */
	public function get($url, $headers = [])
	{
		return $this->request('GET', $url, null, $headers);
	}

	/**
	 * Post request
	 *
	 * @param $url
	 * @param $body
	 * @param array $headers
	 * @return mixed
	 */
	public function post($url, $body = [], $headers = [])
	{
		return $this->request('POST', $url, $body, $headers);
	}

	/**
	 * Put request
	 *
	 * @param $url
	 * @param $body
	 * @param array $headers
	 * @return mixed
	 */
	public function put($url, $body = [], $headers = [])
	{
		return $this->request('PUT', $url, $body, $headers);
	}

	/**
	 * Delete request
	 *
	 * @param $url
	 * @param array $headers
	 * @return mixed
	 */
	public function delete($url, $headers = [])
	{
		return $this->request('DELETE', $url, null, $headers);
	}

	/**
	 * Make request to api
	 *
	 * @param $method
	 * @param $url
	 * @param array $body
	 * @param array $headers
	 * @return mixed
	 */
	protected function request($method, $url, $body = [], $headers = [])
	{
		$fullUrl = $this->resolveFullUrl($url);

		$date = date('r', time());

		$headers['date'] = $date;
		$headers['signature'] = $this->resolveSignature($this->clientSecret, $method, $fullUrl, $body, $date);

		// Lowercase header names
		$headers = $this->prepareHeaders(
			array_merge($headers, $this->defaultHeaders)
		);

		$response = $this->httpClient->request($method, $fullUrl, $body, $headers);

		// TODO: Check response code and take actions

		return $response;
	}

	/**
	 * Set one default header
	 *
	 * @param $header
	 * @param $value
	 */
	public function setDefaultHeader($header, $value)
	{
		$this->defaultHeaders[
			$this->resolveValidHeaderName($header)
		] = $value;
	}

	/**
	 * Resolves supported http client
	 *
	 * @return HttpClientContract
	 * @throws Exception
	 */
	protected function resolveHttpClient()
	{
		$clients = array_filter($this->availableClients, $this->guessClientSupportEnvironment());

		if (!isset($clients[0])) {
			throw new Exception('There is no http client which this application can support');
		}

		// Instantiate first supported http client
		return new $clients[0];
	}

	/**
	 * This will filter out clients which is not supported
	 * by the current environment
	 *
	 * @return \Closure
	 */
	protected function guessClientSupportEnvironment()
	{
		return function ($client) {
			return forward_static_call([$client, 'hasSupport']);
		};
	}

	/**
	 * Resolves valid header name
	 *
	 * @param $headerName
	 * @return string
	 */
	private function resolveValidHeaderName($headerName)
	{
		return strtolower($headerName);
	}

	/**
	 * Lowercase header names
	 *
	 * @param $headers
	 * @return array
	 */
	private function prepareHeaders($headers)
	{
		return array_change_key_case($headers, CASE_LOWER);
	}

	/**
	 * Sign request
	 *
	 * @param $key
	 * @param $method
	 * @param $url
	 * @param $body
	 * @param $date
	 *
	 * @return false|string
	 */
	private function resolveSignature($key, $method, $url, $body, $date)
	{
		$string = implode('\n', [$method, $url, md5(json_encode($body)), $date]);

		$contentSecret = hash_hmac('sha256', $string, $key);

		return sprintf('%s:%s', $this->clientId, $contentSecret);
	}

	/**
	 * Compose full url
	 *
	 * @param $url
	 * @return string
	 */
	private function resolveFullUrl($url)
	{
		return sprintf('%s/%s', $this->apiBase, trim($url, '/'));
	}
}
