<?php

namespace App\Webhook;

class QueueManager
{

/**
	 * Singleton instance object.
	 * @var self
	 */
	static private $instance;

	/**
	 * The webhook queue DB table name.
	 * @var string
	 */
	private $dbTable;

	/**
	 * Stores the webhook DB table.
	 */
	private function __construct()
	{
		global $DBTWebhookQueue;
		$this->dbTable = $DBTWebhookQueue;
	}

	/**
	 * Singleton instantiation of current class.
	 * @return \App\Webhook\QueueManager
	 */
	static public function getInstance()
	{
		if (!self::$instance) {
			!self::$instance = new self();
		}
		return self::$instance;
	}


	/**
	 * Returns the details of the record
	 * @param string $entity
	 * @param int $id
	 * @param string $action
	 * @param boolean $forUpdate
	 * @return NULL
	 */
	public function getRecord(string $entity, int $id, string $action, bool $forUpdate=false)
	{

		$db = \DB::getInstance();

		$sql = "SELECT * FROM `{$this->dbTable}` WHERE entity=? AND id=? AND action=?";
		if ($forUpdate) {
			$sql .= " FOR UPDATE";
		}
		$record = $db->GetRow($sql, [$entity, $id, $action]);

		return $record ? : null;

	}

	/**
	 * Add a new record in the webhook queue.
	 * @param string $entity
	 * @param int $id
	 * @param string $action
	 * @throws \Exception
	 * @throws \DBException
	 */
	public function insertEntityAction(string $entity, int $id, string $action)
	{

		// CHECK INPUT ----------------------------------------------------------------------------

		// check if inside transaction
		if (!\DB::isInsideTransaction()) {
			throw new \Exception('You must be inside a transaction when running '.__METHOD__);
		}

		// check if the entity-action is active
		$entActionCfg = \App\Config\WebhookQueue::getInstance();
		if (!$entActionCfg->get('enabled')) {
			return false;
		}
		if (!$entActionCfg->getActiveEntityAction($entity, $action)) {
			return false;
		}

		// check if the record already exists
		$record = $this->getRecord($entity, $id, $action, true);
		$db = \DB::getInstance();

		// RECORD DOES NOT EXIST - ADD IT ---------------------------------------------------------
		if (!$record) {
			$insertSQL = "INSERT INTO `{$this->dbTable}` ( `entity`, `id`, `action`, `lastSentValue`, `toSend`, `addTime`) VALUES (?, ?, ?, ?, ?, ?)";
			$insertEx = $db->Execute($insertSQL, [$entity, $id, $action, null, 1, DATETIME]);
			// check the SQL
			if (!$insertEx) {
				throw new \DBException('Could not insert a new record in the webhook queue.');
			}
		}
		// RECORD EXISTS - UPDATE THE FLAG toSend -------------------------------------------------
		else {
			if (!$record['toSend']) {
				$updateSQL = "UPDATE `{$this->dbTable}` SET toSend=?, lastUpdateTime=? WHERE `entity`=? AND `id`=? AND `action`=?";
				$updateEx = $db->Execute($updateSQL, [1, DATETIME, $entity, $id, $action]);
				if (!$updateEx) {
					throw new \DBException('Could not update a record in the webhook queue.');
				}
			}
		}

	}


	/**
	 * Confirms the entity action from the queue.
	 */
	public function confirmEntityAction(string $entity, int $id, string $action, $newSentValue)
	{
		if (!\DB::isInsideTransaction()) {
			throw new \Exception('You must be inside a transaction in order to call '.__METHOD__);
		}

		$db = \DB::getInstance();

		$updateRecordSQL = "UPDATE `{$this->dbTable}` SET toSend=0, lastSentValue=?, lastSendTime=? WHERE entity=? AND id=? AND action=?";
		$updateRecordEx = $db->Execute($updateRecordSQL, [$newSentValue, \Engine\DateTime::create('now')->formatDatetimeIso(), $entity, $id, $action]);
		if (!$updateRecordEx) {
			throw new \DBException('Could not confirm entity action in webhook queue.');
		}

	}


	/**
	 * Returns all entities that are supposed to be sent.
	 * @param int $limit
	 * @throws \DBException
	 * @return array
	 */
	public function getAllEntitiesToSend($limit=0)
	{

		$getRecordsSQL = "SELECT entity, id, action FROM `{$this->dbTable}` WHERE toSend=1 ORDER BY addTime ASC, lastUpdateTime ASC";
		if (is_numeric($limit) and $limit>0) {
			$getRecordsSQL .= " LIMIT ".$limit;
		}
		$db = \DB::getInstance();
		$recordsList = $db->GetAll($getRecordsSQL);
		// check sql
		if ($db->ErrorNo()) {
			throw new \DBException('Could not get webhook entities to be sent.');
		}

		return $recordsList;

	}

	/**
	 * Cleans all records touched (added/updated) before this datetime and that are set toSend = 0 (so they were already sent).
	 * @param \Engine\DateTime $datetime
	 */
	public function cleanupOldEntityActions(\Engine\DateTime $datetime)
	{
		$db = \DB::getInstance();
		$cleanQueueSQL = "DELETE FROM `{$this->dbTable}` WHERE toSend=? AND addTime<=?";
		$db->Execute($cleanQueueSQL, [0, $datetime->formatDatetimeIso()]);
		if ($db->ErrorNo()) {
			throw new \DBException('Could not clean old entities.');
		}
	}

}