<?php

namespace App\Webhook;

/**
 * The class manages the sending of the webhook requests.
 * @author Gabriel
 */
class SendRequest
{

	/**
	 * The entity type.
	 * @var string
	 */
	private $entity;

	/**
	 * The entity id.
	 * @var int
	 */
	private $id;

	/**
	 * The entity action.
	 * @var string
	 */
	private $action;

	/**
	 * Indicates if the request was actually made - if the entity field has not changed, it is confirmned but no request is done.
	 * @var boolean
	 */
	private $actuallySent = false;


	public function __construct(string $entity, int $id, string $action)
	{
		$this->entity	= $entity;
		$this->id		= $id;
		$this->action	= $action;
	}


	public function send()
	{

		// CHECK MAIN INPUT ///////////////////////////////////////////////////////////////////////
		$this->checkInput();

		// SEND THE NOTIFICATION (IF IT SHOULSD)) /////////////////////////////////////////////////
		$entityDetails = \App\Webhook\QueueManager::getInstance()->getRecord($this->entity, $this->id, $this->action, true);
		$entityChanged = new \App\Webhook\EntityChanged($this->entity, $this->id, $this->action, $entityDetails['lastSentValue']);
		$entityChanged->process();

		// sends the webhook - if anythiong goes wrong, it will throw an exception and not continue
		if ($this->shouldSend($entityDetails, $entityChanged)) {
			$this->doSend($entityDetails);
			$this->actuallySent = true;
		}

		// ELIMINATE THE RECORD FROM THE QUEUE ////////////////////////////////////////////////////
		if ($entityDetails['toSend']) {
			\App\Webhook\QueueManager::getInstance()->confirmEntityAction($this->entity, $this->id, $this->action, $entityChanged->getNewSendValue());
		}

	}


	/**
	 * Indicates if the webhook was actually sent before confirming the entity action in the queue.
	 * @return boolean
	 */
	public function wasActuallySent()
	{
		return $this->actuallySent;
	}


	/**
	 * Check main input for the entity.
	 * @throws \App\MessagesException
	 */
	private function checkInput()
	{

		if (!\DB::isInsideTransaction()) {
			throw new \Exception('You must be inside a transaction when using '.__CLASS__);
		}

		$msgs = new \App\MessagesCollector();
		if (!$this->entity) {
			$msgs->addWarning('The `entity` field is not set when sending the webhook.');
		}
		if (!is_numeric($this->id) or !$this->id) {
			$msgs->addWarning('The `id` field is not set when sending the webhook.');
		}
		if (!$this->action) {
			$msgs->addWarning('The `action` field is not set when sending the webhook.');
		}

		$webhookCfg = \App\Config\WebhookQueue::getInstance();

		// check that the webhook is enabled
		if (!$webhookCfg->get('enabled')) {
			$msgs->addWarning('The `webhook` is disabled.');
		}

		if ($msgs->hasErrorOrWarning()) {
			throw new \App\MessagesException($msgs);
		}

	}

	/**
	 * Indicates if the current request should be sent by checking the current entity toSend flag
	 * and also if the configuration allows it.
	 * @param array $entityDetails
	 * @param \App\Webhook\EntityChanged $entityChangedObj
	 * @return boolean
	 */
	private function shouldSend(array $entityDetails, \App\Webhook\EntityChanged $entityChangedObj)
	{

		// if in the meantime it is not set to be sent (probably will not happen if only the cron initiates the requests)
		if (!$entityDetails['toSend']) {
			return false;
		}

		// check if this entity and action are still available for sending
		$webhookCfg = \App\Config\WebhookQueue::getInstance();
		if (!$webhookCfg->getActiveEntityAction($this->entity, $this->action)) {
			return false;
		}

		if (!$entityChangedObj->shouldNotify()) {
			return false;
		}

		return true;

	}

	/**
	 * Does the actual transfer - if anything is wrong with it, it will throw an exception.
	 * @throws \App\MessagesException
	 */
	private function doSend()
	{

		// PREPARE POST DATA ----------------------------------------------------------------------
		$postData = [
			'entity'	=> $this->entity,
			'id'		=> $this->id,
			'action'	=> $this->action,
		];

		$webhookCfg = \App\Config\WebhookQueue::getInstance();

		$msgs = new \App\MessagesCollector();

		// EXECUTE THE REQUEST --------------------------------------------------------------------
		$ch = curl_init();
		if (!$ch) {
			$msgs->addError( 'Could not send webhook request due to a technical problem. Could not initialize CURL.' );
			throw new \App\MessagesException($msgs);
		}

		curl_setopt($ch, CURLOPT_URL,  				$webhookCfg->get('url'));
		curl_setopt($ch, CURLOPT_POST, 				1);
		curl_setopt($ch, CURLOPT_POSTFIELDS, 		http_build_query($postData));
		curl_setopt($ch, CURLOPT_RETURNTRANSFER,	1);
		curl_setopt($ch, CURLOPT_USERAGENT, 		"TINA Webhook");
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,	5);
		curl_setopt($ch, CURLOPT_TIMEOUT,			10);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION,	1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,	true); // TODO: [notImportant] sa fie configurabil din setari?
		// set the headers
		$headers = ['Content-Type: application/x-www-form-urlencoded'];

		if ($webhookCfg->get('username')) {
			$headers[] = 'Authorization: Basic '.base64_encode($webhookCfg->get('username').':'.$webhookCfg->get('password'));
		}
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

		// do the actual request
		$response = curl_exec ($ch);

		// CHECK THE REQUEST ----------------------------------------------------------------------

		// check request/transfer issues
		if (curl_errno($ch)) {
			$msgs->addError( sprintf( 'CURL error code and number: %s / %s', curl_errno($ch), curl_error($ch) ) );
			throw new \App\MessagesException($msgs);
		}

		// check HTTP code
		$curlDetails = curl_getinfo($ch);
		if ($curlDetails['http_code']!=200) {
			$msgs->addError( sprintf( 'The received HTTP code is %s instead of 200.', $curlDetails['http_code'] ) );
			throw new \App\MessagesException($msgs);
		}

		// check response issues
		if ($response!='OK') {
			$msgs->addError( sprintf( 'The request was made, but the response did not indicate a successful execution - it should have been `OK`. Actual response: %s', $response ) );
			throw new \App\MessagesException($msgs);
		}

	}

}

