<?php

namespace App\Cron\Job;

/**
 * This class adds calendar events indicating expired (or about-to-expire) contracts (for suppliers/corporations).
 */
class ExpiredContractsAlert extends \App\Cron\Job\Utils\GeneralAbstract
{
	/**
	 * Cronjob category: {system|operational|financial|reporting}
	 * @var string
	 */
	protected $category = 'operational';

	/**
	 * @param array $fields
	 * @return \Engine\Form\General
	 */
	public function getSettingsDialog(array $fields) : \Engine\Form\General
	{
		$form = parent::getSettingsDialog($fields);

		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();

		$formContainer = \Engine\Form\Container\Fieldset::create();

		$form->addContainer($formContainer);

		$period = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['ExpiredContractsAlert']['options']['period'], 'period', $fields['settings']['period']);
		$period->setMandatory();
		$period->setStyle('width', 100);
		$period->setDetails($_LANG['Admin']['CronJobs']['Management']['ExpiredContractsAlert']['options']['periodTooltip']);
		$period->setValidator(['type'=>'int', 'minValue'=>0]);

		$formContainer->addElement($period);

		$formContainer->groupInputsInArray("settings");

		return $form;
	}

	/**
	 * @return void
	 */
	protected function executeJob() : void
	{
		// GET EXPIRE DATE (CURRENT DATE + PERIOD SET IN CRON CONFINGS)
		$period = $this->settings['period'] ?: 7;

		$expireDateObj = new \Engine\DateTime();
		$expireDateObj->add(new \DateInterval("P{$period}D"));
		$expireDate = $expireDateObj->formatDateIso();

		$this->logger->info(sprintf('The expire date limit is `%s`.', $expireDate));

		// ADD ALERTS FOR CORPORATE CONTRACTS
		$this->sendCorporateContractsAlerts($expireDate);

		// ADD ALERTS FOR SUPPLIER CONTRACTS
		$this->sendSupplierContractsAlerts($expireDate);
	}

	/**
	 * Sends alerts for corporate contracts that are expired or are about to expire.
	 * Alerts are added as calendar events and directed to the responsible user or account manager.
	 *
	 * @param string $expireDate The expiration date to filter contracts that are expired or about to expire.
	 * @return void
	 */
	private function sendCorporateContractsAlerts(string $expireDate) : void
	{
		// get the corporate contracts list
		$entList = new \App\EntityList\CorporateContracts();
		$entList->setColumns(['idResponsibleUser', 'serviceCode', 'contractNumber', 'contractDate', 'expireDate', 'idCorporate']);
		$entList->setFilter('active', '=', 1);
		$entList->setFilter('expireDate', 'IS NOT NULL');
		$entList->setFilter('expireDate', '<', $expireDate);
		$entList->setOrderBy('expireDate', 'ASC');

		$expiredContracts = $entList->exportRecords();

		if ($expiredContracts) {
			$this->logger->info(sprintf("Total number of corporate contracts that have already expired or are about to expire until `%s`: %s.", $expireDate, count($expiredContracts)));
		}
		else {
			$this->logger->idle("No expired or about-to-expire corporate contracts found.");
			return;
		}

		$successCount = 0;
		$failCount = 0;
		$missingAlertUserCount = 0;

		foreach ($expiredContracts as $expiredContract) {
			$alertUserId = $expiredContract['idResponsibleUser'] ?: \Entity\Corporate::select($expiredContract['idCompany'])->idAccountManager;
			// skip if no resp user is set for the contract or no kam
			if (!$alertUserId) {
				$missingAlertUserCount++;
				continue;
			}

			$eventEntities = [['type' => 'corporateContract', 'id' => $expiredContract['id']]];

			$todayObj	= new \Engine\DateTime();
			$dateDiff = $todayObj->diff(\Engine\DateTime::createFromIsoDate($expireDate));

			// get service name
			if ($expiredContract['serviceCode']) {
				$serviceName = \App\Config\Services_Basic::getInstance()->getServiceName($expiredContract['serviceCode']);
			}
			else {
				$serviceName = '';
			}

			// compose description
			$description = "";
			if (!$dateDiff->days) {
				$description.="The client contract has expired today.";
			}
			elseif ($dateDiff->invert) {
				$description.="The client contract has expired for `{$dateDiff->days}` days.";
			}
			else {
				$description.="The client contract will expire in `{$dateDiff->days}` days.";
			}
			$description.="\r\nClient: ".\Entity\Corporate::select($expiredContract['idCorporate'])->alias.';';
			if ($expiredContract['serviceCode']) {
				$description.="\r\nService: ".$serviceName.';';
			}
			$description.="\r\nContract details: ".$expiredContract['contractNumber'].'/'.$expiredContract['contractDate'].'.';

			// ADD CALENDAR EVENT /////////////////////////////////////////////////////////////////
			if ($this->addEvent($description, $alertUserId, $eventEntities)) {
				$successCount++;
			}
			else {
				$failCount++;
			}
		}

		if ($successCount) {
			$this->logger->success(sprintf("%s calendar events for client contracts were successfully added.", $successCount));
		}
		if ($failCount) {
			$this->logger->error(sprintf("%s calendar events for client contracts could not be added due to a technical problem.", $failCount));
		}
		if ($missingAlertUserCount) {
			$this->logger->warning(sprintf("%s client contracts have no responsible user / KAM set and could not be alerted.", $missingAlertUserCount));
		}
	}

	/**
	 * Sends alerts for supplier contracts that have expired or are about to expire based on a given expiration date.
	 *
	 * @param string $expireDate The expiration date used to filter supplier contracts. Only contracts with an expire date earlier than this will be considered.
	 * @return void
	 */
	private function sendSupplierContractsAlerts(string $expireDate) : void
	{
		// get supplier contracts list
		$entList = new \App\EntityList\SupplierContracts();
		$entList->setColumns(['idResponsibleUser', 'serviceCode', 'contractNumber', 'contractDate', 'expireDate', 'idSupplier']);
		$entList->setFilter('active', '=', 1);
		$entList->setFilter('expireDate', 'IS NOT NULL');
		$entList->setFilter('expireDate', '<', $expireDate);
		$entList->setOrderBy('expireDate', 'ASC');

		$expiredContracts = $entList->exportRecords();

		if ($expiredContracts) {
			$this->logger->info(sprintf("Total number of supplier contracts that have already expired or are about to expire until `%s`: %s.", $expireDate, count($expiredContracts)));
		}
		else {
			$this->logger->idle("No expired or about-to-expire supplier contracts found.");
			return;
		}

		$successCount = 0;
		$failCount = 0;
		$missingAlertUserCount = 0;

		foreach ($expiredContracts as $expiredContract) {
			$alertUserId = $expiredContract['idResponsibleUser'];

			if (!$alertUserId) {
				$missingAlertUserCount++;
				continue;
			}
			$eventEntities = [['type' => 'supplierContract', 'id' => $expiredContract['id']]];

			$todayObj	= new \Engine\DateTime();
			$dateDiff = $todayObj->diff(\Engine\DateTime::createFromIsoDate($expireDate));

			// get service name
			if ($expiredContract['serviceCode']) {
				$serviceName = \App\Config\Services_Basic::getInstance()->getServiceName($expiredContract['serviceCode']);
			}
			else {
				$serviceName = '';
			}

			// compose description
			$description = "";
			if (!$dateDiff->days) {
				$description.="The supplier contract has expired today.";
			}
			elseif ($dateDiff->invert) {
				$description.="The supplier contract has expired for `{$dateDiff->days}` days.";
			}
			else {
				$description.="The supplier contract will expire in `{$dateDiff->days}` days.";
			}
			$description.="\r\nSupplier: ".\Entity\Supplier::select($expiredContract['idSupplier'])->alias.';';
			if ($expiredContract['serviceCode']) {
				$description.="\r\nService: ".$serviceName.';';
			}
			$description.="\r\nContract details: ".$expiredContract['contractNumber'].'/'.$expiredContract['contractDate'].'.';

			// ADD CALENDAR EVENT /////////////////////////////////////////////////////////////////
			if ($this->addEvent($description, $alertUserId, $eventEntities)) {
				$successCount++;
			}
			else {
				$failCount++;
			}
		}

		if ($successCount) {
			$this->logger->success(sprintf("%s calendar events for supplier contracts were successfully added.", $successCount));
		}
		if ($failCount) {
			$this->logger->error(sprintf("%s calendar events for supplier contracts could not be added due to a technical problem.", $failCount));
		}
		if ($missingAlertUserCount) {
			$this->logger->warning(sprintf("%s supplier contracts have no responsible user set and could not be alerted.", $missingAlertUserCount));
		}
	}

	/**
	 * Adds a calendar event based on the given description, alert user ID, and related event entities.
	 *
	 * @param string $description   The description of the event being added.
	 * @param int    $alertUserId   The ID of the user who will receive the alert for this event.
	 * @param array  $eventEntities The entities related to the event being added.
	 * @return bool Returns true if the event is successfully added, false otherwise.
	 */
	private function addEvent(string $description, int $alertUserId, array $eventEntities) : bool
	{
		try {
			$eventAdd = \Entity\CalendarEvent::insert();
			$eventAdd->title = "Auto event - expired contract";
			$eventAdd->description = $description;
			$eventAdd->allDay = false;
			$eventAdd->startDateTime = DATETIME;
			$eventAdd->endDateTime = DATETIME;
			$eventAdd->alertDateTime = date(DATETIME_FORMAT, TIMESTAMP - 300);
			$eventAdd->mailStatus = 1;
			$eventAdd->access = 'private';
			$eventAdd->idUser = $alertUserId;
			$eventAdd->idAddUser = 0;
			$eventAdd->entities = $eventEntities;
			$eventAdd->save();
			return true;
		} catch (\App\Entity\Exception $e) {
			$this->logger->warning($e->getMessageBulk());
			return false;
		}
	}

}