<?php

namespace App\Cron\Job;


class HardwareCheck extends \App\Cron\Job\Utils\ShortRunningCronJobAbstract
{
	/**
	 * Cronjob category: {system|operational|financial|reporting}
	 * @var string
	 */
	protected $category = 'system';


	/**
	 * Configures the interval of time used to send emails (from x to x seconds)
	 * @var int
	 */
	protected $emailIntervalSeconds = 1800;// 30min

	/**
	 * Default options values
	 * @var array
	 */
	protected $defaultValues = [
		'minDiskFreeSpace'     => 5,	// in GB
		'logsMaxDays'          => 1460,
		'logsStartTime'        => "07:00",
		'logsEndTime'          => "20:00",

	];

	protected $maxLoad = null;

	/**
	 * default time(seconds) for SQL Execution
	 * @var integer
	 */
	protected $sqlExecutionMinTimeDefault = 30; //seconds

	public function __construct()
	{
		parent::__construct();

		$this->maxLoad = 		(Float)\App\Config\ServerLoadLimits::getInstance()->get('overload');
	}

	/**
	 * The method returns the form which is needed for the the current cronjob settings saving.
	 */
	public function getSettingsDialog(array $fields)
	{
		$form = parent::getSettingsDialog($fields);

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

		$formContainer = \Engine\Form\Container\Fieldset::create($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['legend']);
		$formContainer->setStyle('width', '50');
		$form->addContainer($formContainer);

		// min disk free space
		$fldSpace = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['minDiskSpace'], 'minDiskFreeSpace', $fields['settings']['minDiskFreeSpace']);
		$fldSpace->setMandatory();
		$fldSpace->setTooltip($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['minDiskSpaceExpl']);
		$fldSpace->setCustomHTML($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['gb']);
		$formContainer->addElement($fldSpace);

		// long running sqls
		$fldLongRunSqls = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['longRunSqlsDescr'], 'longRunSqlsTime', $fields['settings']['longRunSqlsTime']);
		$fldLongRunSqls->setMandatory();
		$fldLongRunSqls->setTooltip(sprintf($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['longRunSqlsExpl']));
		$fldLongRunSqls->setCustomHTML($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['seconds']);
		$formContainer->addElement($fldLongRunSqls);

		$formContainer2 = \Engine\Form\Container\Fieldset::create($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['archive']);
		$formContainer2->setStyle('width', '50');
		$form->addContainer($formContainer2);

		// arhive start time
		$fldPeriod = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['logingIntervalStartDescr'], 'loging[startTime]',$fields['settings']['loging']['startTime']);
		$fldPeriod->setMandatory();
		$fldPeriod->setValidatorParam('type', 'time');
		$fldPeriod->setTooltip($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['logingIntervalStartExpl']);
		$formContainer2->addElement($fldPeriod);

		// archive end time
		$fldPeriod = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['logingIntervalEndDescr'], 'loging[endTime]',$fields['settings']['loging']['endTime']);
		$fldPeriod->setMandatory();
		$fldPeriod->setValidatorParam('type', 'time');
		$fldPeriod->setTooltip($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['logingIntervalEndExpl']);
		$formContainer2->addElement($fldPeriod);

		// archive days
		$fldDays = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['archiveDaysDescr'], 'loging[archiveDays]',$fields['settings']['loging']['archiveDays']);
		$fldDays->setMandatory();
		$fldDays->setTooltip(sprintf($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['editOptions']['archiveDaysExpl'], $this->defaultValues['logsMaxDays']));
		$formContainer2->addElement($fldDays);

		$formContainer->groupInputsInArray("settings");
		$formContainer2->groupInputsInArray("settings");

		return $form;
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Cron\Job\Utils\GeneralAbstract::executeJob()
	 */
	protected function executeJob()
	{

		if (!$this->maxLoad || !$this->settings['minDiskFreeSpace']) {
			$this->logger->critical('Please configure cronjob!');
			return;
		}

		// CHECK FREE SPACE
		$dir = realPath("../")."/";
		$freeSpace = disk_free_space($dir)/(1024*1024*1024);

		$this->logger->info(sprintf("Disk free space is %sGB", number_format($freeSpace, 2)));
		if ($freeSpace<=$this->settings['minDiskFreeSpace']) {
			$this->logger->critical(sprintf("Attention, you are running out of free space, minimum set free space is %sGB", number_format($this->settings['minDiskFreeSpace'], 2)));
		}

		// CHECK LOAD
		// $load[0] - last minute avg load
		// $load[1] - last 5 minutes avg load
		// $load[2] - last 15 minutes avg load
		$load = sys_getloadavg();
		$this->logger->info(sprintf("Server load is %s", $load[0]));
		if ($load[0]>$this->maxLoad) {
			$this->logger->critical(sprintf("Attention, the server load is too high, maximum set load is %s", $this->maxLoad));
		}

		// SAVE STATS TO DATABASE TABLE
		if ($this->settings['loging']['archiveDays']) {
			// clear old data
			$this->deleteOldStats();
			// save stats
			$this->saveHardwareStats($freeSpace, $load[0]);
		}

		// Logs mysql queries

		$longRunSqlsTimeValue = "";
		if (is_null($this->settings['longRunSqlsTime'])) {
		    $longRunSqlsTimeValue = $this->sqlExecutionMinTimeDefault;
		} elseif ($this->settings['longRunSqlsTime'] == 0) {
		    $longRunSqlsTimeValue = 0;
		} else {
		    $longRunSqlsTimeValue = $this->settings['longRunSqlsTime'];
		}

		$this->showLongRunningSQLs($longRunSqlsTimeValue);
	}

	protected function shouldNotifyUsers()
	{
		$shouldSend = false;
		$storageKey = sprintf("%s:%s", __CLASS__, 'timestampOfLastNotification');
		$hasErrors = ($this->logger->has('error') || $this->logger->has('critical'));

		$lastSentTimestamp = (float)\Engine\Storage\General::getInstance('persistent')->get($storageKey);

		if ($hasErrors) {
			if (($lastSentTimestamp + $this->emailIntervalSeconds)<TIMESTAMP) {
				$shouldSend = true;
				\Engine\Storage\General::getInstance('persistent')->set($storageKey, TIMESTAMP);
				$this->logger->info(sprintf("Sending emails"));
			}
			else {
				$this->logger->info(sprintf("Not sending any more emails until %s", \Engine\DateTime::createFromTimestamp($lastSentTimestamp + $this->emailIntervalSeconds)->formatDatetimeIso()));
			}
		}

		return $shouldSend;
	}


	/**
	 * Delete old stats, daily
	 */
	private function deleteOldStats() :void
	{
		global $DBTServerStats;

		// get last clean date from cache
		$lastClean = \Engine\Cache\General::getInstance('disk')->get("cronJojbs_HardwareCheck_lastCleanDate");

		// if no cache or date has changed
		if ($lastClean!=TODAY_ISO){

			$maxStatsTime = strtotime("-{$this->settings['loging']['archiveDays']} days");

			$deleteOldStatsSQL = "DELETE FROM `$DBTServerStats` WHERE `timestamp`<?";
			\DB::getInstance()->Execute($deleteOldStatsSQL, [$maxStatsTime]);

			if (\DB::getInstance()->ErrorNo()) {
				throw new \DBException('Could not clean old data from server stats.');
			}

			// save last clean time
			\Engine\Cache\General::getInstance('disk')->set("cronJojbs_HardwareCheck_lastCleanDate", TODAY_ISO);

			$this->logger->info('Eliminated old records from archive.');

		}

	}

	/**
	 * Save stats in database
	 * @param $freeSpace
	 * @param $load
	 */
	private function saveHardwareStats($freeSpace, $load)
	{
		global $DBTServerStats;
		$db = \DB::getInstance();

		$insert = false;
		// check if current time is in selected log interval
		if ($this->settings['loging']['startTime'] && $this->settings['loging']['endTime']) {
			$startTime= (int)str_ireplace(":", "", $this->settings['loging']['startTime']);
			$endTime  = (int)str_ireplace(":", "", $this->settings['loging']['endTime']);
			$thisTime = (int)date("Hi");

			if ($endTime>=$startTime && $thisTime>=$startTime && $thisTime<=$endTime) {
				$insert = true;
			}

		}

		if ($insert) {
			$insertStatSQL = "
				INSERT INTO `$DBTServerStats` (`timestamp`, `serverLoad`, `diskSpace`)
				VALUES (?, ?, ?)
				";
			$db->Execute($insertStatSQL, [time(), $load, $freeSpace]);

			if ($db->ErrorNo()) {
				throw new \DBException("Failed to insert stats in database table", $insertStatSQL);
			}
		}

		return $insert;
	}

	/**
	 *
	 * {@inheritDoc}
	 * @see \App\Cron\Job\Utils\GeneralAbstract::validateSettings()
	 */
	public function validateSettings(\App\Entity\Messages $msgs, array $fields)
	{
		$_LANG = \App\Loader_Lang::getInstance()->getGlobal();

		if ($fields['settings']) {
			// min space disk
			if (!$fields['settings']['minDiskFreeSpace'] || !is_Numeric($fields['settings']['minDiskFreeSpace'])) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['minDiskFreeSpace_invalid']);
				return false;
			}

			// start time
			if (!$fields['settings']['loging']['startTime'] || !preg_match("/^(?:2[0-3]|[01][0-9]):[0-5][0-9]$/", $fields['settings']['loging']['startTime'])) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['startTime_invalid']);
				return false;
			}

			// end Time
			if (!$fields['settings']['loging']['endTime'] || !preg_match("/^(?:2[0-3]|[01][0-9]):[0-5][0-9]$/", $fields['settings']['loging']['endTime'])) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['endTime_notSetOrInvalid']);
				return false;
			}

			// end Time >= start time
			$startTime= (int)str_ireplace(":", "", $fields['settings']['loging']['startTime']);
			$endTime  = (int)str_ireplace(":", "", $fields['settings']['loging']['endTime']);
			if (!($endTime >= $startTime)) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['endTime_invalid']);
				return false;
			}

			// archive days
			if (!$fields['settings']['loging']['archiveDays'] || !is_Numeric($fields['settings']['loging']['archiveDays'])) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['archiveDays_invalid']);
				return false;
			}

			// long running SQL
			if (!is_Numeric($fields['settings']['longRunSqlsTime']) || $fields['settings']['longRunSqlsTime']<0 || $fields['settings']['longRunSqlsTime']>3600) {
				$msgs->addWarningText($_LANG['Admin']['CronJobs']['Management']['HardwareCheck']['field']['warning']['longRunSqls_invalid']);
				return false;

			}
		}
	}


	/**
	 * Logs mysql queries
	 * @param $sqlTimeLimit integer
	 */
	protected function showLongRunningSQLs($sqlTimeLimit)
	{

	    //if value is 0, no logging
	    if ($sqlTimeLimit !== 0) {

	        $db = \DB::getInstance();

	        $getProcessesListSQL = "SHOW FULL PROCESSLIST";
	        $processesList = $db->getAll($getProcessesListSQL);

	        if ($db->ErrorNo()) {
	            throw new \DBException("Failed to get processlist from db", $getProcessesListSQL);
	        }

	        foreach ($processesList as $eachSql){
	            if ($eachSql['Time']>=$sqlTimeLimit && $eachSql['Info']){
	                $this->logger->warning(sprintf("This sql is running for %s seconds: %s", $eachSql['Time'], $eachSql['Info']));
	            }
	        }
	    }
	}
}
