<?php

namespace App\ImportEmailBookings;

abstract class ManagerAbstract
{

	protected ?string $skipReason = null;

	/**
	 * The emailbox object that
	 * @var \App\Utils\EmailBox\GeneralAbstract
	 */
	protected \App\Utils\EmailBox\GeneralAbstract $emailBox;

	/**
	 * The object that manages the storage of imported emails by their id.
	 * @var EmailsStorage
	 */
	protected \App\ImportEmailBookings\EmailsStorage $emailStorage;

	/**
	 * The email account code.
	 * @var string
	 */
	protected string $account;

	/**
	 * Populates emailBox object to be used for searching and parsing emails.
	 * @param \App\Utils\EmailBox\GeneralAbstract $emailBox
	 */
	public function __construct( \App\Utils\EmailBox\GeneralAbstract $emailBox, \App\ImportEmailBookings\EmailsStorage $emailStorage, string $account)
	{
		$this->emailBox = $emailBox;
		$this->emailStorage = $emailStorage;
		$this->account = $account;
	}

	/**
	 * Returns the emails that must be processed - loads all supplier emails and then filters out
	 * the ones that were already imported/processed.
	 * @return array
	 */
	public function searchNewBookingEmails( ?\Engine\DateTime $oldestEmailDate=null ) :array
	{

		// gets all emails related to current supplier
		$emails = $this->searchAllBookingEmails($oldestEmailDate);

		// filters out the emails that are already imported
		$return = [];
		foreach ($emails as $idEmail => $parserVersion) {
			if (!$this->emailStorage->getDetails($this->account, $idEmail, false)) {
				$return[$idEmail] = $parserVersion;
			}
		}

		return $return;

	}

	/**
	 * Searches all booking emails based on current supplier and attaches the version of each email.
	 * The return is an associative array of idEmail => parsedVersion
	 * @return array
	 */
	abstract protected function searchAllBookingEmails( ?\Engine\DateTime $oldestEmailDate=null ) :array;

	/**
	 * Returns the list of email ids based on a certian search string.
	 * This is a utility method used for searching through emails.
	 * @param string $searchString
	 * @return array
	 */
	protected function filterEmailsListIds(string $searchString) :array
	{
		return $this->emailBox->search($searchString);
	}

	/**
	 * Gets the full details of an email by its id.
	 * @param string $idEmail
	 * @param bool $markAsSeen
	 * @return \PhpImap\IncomingMail
	 */
	public function getEmail(string $idEmail, bool $markAsSeen) :\PhpImap\IncomingMail
	{
		return $this->emailBox->getMail($idEmail, $markAsSeen);
	}


	/**
	 * Uses the email object and the mentioned version and populates the imported order - this
	 * is the main call of this class.
	 * @param \PhpImap\IncomingMail $email
	 * @param string $parserVersion
	 * @return \App\ImportedOrder\Order
	 */
	public function getImportedOrder(\PhpImap\IncomingMail $email, string $parserVersion) :?\App\ImportedOrder\Order
	{

		// create the parser class name
		$parserClass = "\\" . getClassNamespace(get_called_class()) . "\\" . $parserVersion . "\\Parser";

		// instantiate the parser and populate the content of the IO
		$parserObj = new $parserClass( $email->textHtml );

		$parserObj->parse();

		// create IO populator
		$ioPopulateClass = "\\" . getClassNamespace(get_called_class()) . "\\" . $parserVersion . "\\PopulateImportedOrder";

		$ioPopulateObj = new $ioPopulateClass($parserObj);

		$impOrder = new \App\ImportedOrder\Order();

		$ioPopulateObj->execute($impOrder);

		// returns null if the IO must be skipped
		$this->skipReason = $ioPopulateObj->getSkipReason();
		if ($this->skipReason) {
			return null;
		}

		return $impOrder;
	}

	/**
	 * Stores an email in the storage to mark it as imported/processed.
	 * @param string $idEmail
	 * @param int $idImportedOrder
	 */
	public function storeImportedEmail(string $idEmail, int $idImportedOrder) :void
	{

		// extracts the supplier from the class namespace
		$supplier  = end( explode("\\", getClassNamespace( get_called_class() )) );

		$this->emailStorage->insert($this->account, $idEmail, $supplier, $idImportedOrder);

	}

	/**
	 * Retrieves the reason for skipping the IO, if any.
	 *
	 * @return string|null The skip reason or null if not applicable.
	 */
	public function getSkipReason() :?string
	{
		return $this->skipReason;
	}

}
