<?php

namespace Podeli\Payment;

class Client
{
	protected $sandboxUrl = 'https://api-sand.podeli.ru/partners/v1/orders/';
    protected $prodUrl = 'https://api.podeli.ru/partners/v1/orders/';

	protected $login    = '';
	protected $password = '';
	protected $certPath = '';
	protected $keyPath  = '';
	protected $logger   = null;
	protected $sandbox  = false;
	protected $curlHandler  = false;
	protected $tmpCertFile  = false;
	protected $tmpKeyFile = false;

	public function __construct($login, $password) {
		$this->login = $login;
		$this->password = $password;
	}

	public function setCertPath($certPath)
	{
		if (strpos($certPath, "-----BEGIN CERTIFICATE-----") !== false) {
			$this->tmpCertFile = tmpfile();
			fwrite($this->tmpCertFile, trim($certPath));
			$tempPath = stream_get_meta_data($this->tmpCertFile);
			$certPath = $tempPath['uri'];
		}

		$this->certPath = $certPath;
		if (!file_exists($this->certPath)) {
			throw new \Exception('Cert path did\'t exist: ' . $this->certPath);
		}
		if (!is_readable($this->certPath)) {
			throw new \Exception('Can\'t read cert file: ' . $this->certPath);
		}
		return $this;
	}

	public function setKeyPath($keyPath)
	{
		if (
			strpos($keyPath, "-----END PRIVATE KEY-----") !== false
			||
			strpos($keyPath, '-----END RSA PRIVATE KEY-----') !== false
			) {
			$this->tmpKeyFile = tmpfile();
			fwrite($this->tmpKeyFile, trim($keyPath));
			$tempPath = stream_get_meta_data($this->tmpKeyFile);
			$keyPath = $tempPath['uri'];
		}
		$this->keyPath = $keyPath;
		if (!file_exists($this->keyPath)) {
			throw new \Exception('Key path did\'t exist: ' . $this->keyPath);
		}

		if (!is_readable($this->keyPath)) {
			throw new \Exception('Can\'t read key file: ' . $this->keyPath);
		}

		$this->keyPath = $keyPath;
		return $this;
	}

	public function setLogger(LoggerInterface $logger)
	{
		$this->logger = $logger;
		return $this;
	}

	public function setSandboxMode()
	{
		$this->sandbox = true;
		return $this;
	}

	public function useCurl()
	{
		$this->curlHandler = true;
	}


	static public function generateCorrelationId()
	{
		return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
			mt_rand(0, 0xffff), mt_rand(0, 0xffff),
			mt_rand(0, 0xffff),
			mt_rand(0, 0x0fff) | 0x4000,
			mt_rand(0, 0x3fff) | 0x8000,
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
		);
	}

	public function execute($action, $data, $method, $correlationId = '') {
		if($correlationId === '') {
			$correlationId = self::generateCorrelationId();
		}
		if (!$this->curlHandler) {
			return $this->fileRequestHandler($action, $data, $method, $correlationId);
		}
		return $this->curlRequestHandler($action, $data, $method, $correlationId);
	}

	protected function curlRequestHandler($action, $data, $method, $correlationId)
	{
		$headers = [
			"Content-Type: application/json",
			"X-Correlation-ID: $correlationId",
			"Authorization: Basic ".base64_encode("{$this->login}:{$this->password}")
		];

		$responseHeaders = '';
		if (!function_exists("curl_init")) {
			throw new \Exception("Curl error");
		}

		$url = $this->prodUrl;
		if ($this->sandbox) {
			$url = $this->sandboxUrl;
		}
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url.$action);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
		curl_setopt($ch, CURLINFO_HEADER_OUT, true);
		curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) {
			$responseHeaders .= $header;
			return strlen($header);
		});

		curl_setopt($ch, CURLOPT_SSLCERT, $this->certPath);
		curl_setopt($ch, CURLOPT_SSLKEY, $this->keyPath);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		$encodedData = '';
		if (!empty($data) || $method == 'POST') {
			$encodedData = $this->encode($data);
			curl_setopt($ch, CURLOPT_POST, true);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
		}

		$out = curl_exec($ch);

		$curlError = curl_error($ch);
		if ($curlError) {
			throw new \Exception($curlError);
		}

		$code     = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		if ($this->logger) {
			$this->logger->info($method. ' ' . $action);
			$this->logger->info($encodedData, 'request');
			$this->logger->info($code . ':' . $out, 'response');
		}

		$response = json_decode($out, true);
		if ($code == 200) {
			return $response;
		} elseif ($code == 429) {
			$headers = $this->parseHeadersToArray($responseHeaders);
			sleep($headers['X-Retry-After']);
			return $this->execute($action, $data, $method, $correlationId);
		}

		$error = 'Error: ' . $code;

		if (isset($response['error'])) {
			$error .= ' ' . $response['error']['text'];
		}

		if (!$response) {
			$error .= $out;
		}
		throw new \Exception($error, $code);
	}

	protected function fileRequestHandler($action, $data, $method, $correlationId)
	{
		$headers = [
			"Content-Type: application/json",
			"X-Correlation-ID: $correlationId",
			"Authorization: Basic ".base64_encode("{$this->login}:{$this->password}")
		];

		$streamOptions = [
			'http' => [
				'method'  => "GET",
				'header'  => implode("\r\n", $headers),
				'ignore_errors' => true
			],
		];

		$streamOptions['ssl'] = [
				'verify_peer' => true,
				'local_cert' => $this->certPath,
				'local_pk' => $this->keyPath
			];
			
		$encodedData = '';
		if (!empty($data) || $method == 'POST') {
			$encodedData = $this->encode($data);
			$streamOptions['http']['method'] = 'POST';
			$streamOptions['http']['content'] = $encodedData;
		}

		$context = stream_context_create($streamOptions);
		$url = $this->prodUrl;
		if ($this->sandbox) {
			$url = $this->sandboxUrl;
		}
		$url = $url.$action;
		$out = file_get_contents($url, false, $context);

		$statusLine = $http_response_header[0] ?? '';
	    preg_match('{HTTP\/\S*\s(\d{3})}', $statusLine, $match);
	    $code = intval($match[1] ?? '');

		if ($this->logger) {
			$this->logger->info($method. ' ' . $action);
			$this->logger->info($encodedData, 'request');
			$this->logger->info( $code . ':' . $out, 'response');
		}

		$response = json_decode($out, true);
		if ($code == 200) {
			return $response;
		} elseif ($code == 429) {
			$headers = $this->parseHeadersToArray(implode("\r\n", $http_response_header));
			sleep($headers['X-Retry-After']);
			return $this->execute($action, $data, $method, $correlationId);
		}

		$error = 'Error: ' . $code;

		if (isset($response['error'])) {
			$error .= ' ' . $response['error']['text'];
		}

		if (!$response) {
			$error .= $out;
		}
		throw new \Exception($error, $code);
	}


	protected function parseHeadersToArray($rawHeaders)
	{
		$lines = explode("\r\n", $rawHeaders);
		$headers = [];
		foreach($lines as $line) {
			if (strpos($line, ':') === false ){
				continue;
			}
			list($key, $value) = explode(': ', $line);
			$headers[$key] = $value;
		}
		return $headers;
	}

	protected function encode($data)
	{
		if (is_string($data)) {
			return $data;
		}
		$result = json_encode($data);
		$error = json_last_error();
		if ($error != JSON_ERROR_NONE) {
			throw new \Exception('JSON Error: ' . json_last_error_msg());
		}
		return $result;
	}
}
