<?php

// TODO: [notImportant] tabelul de reports data ar trebui sa faca si optimize din cand in cand

namespace App\Cron\Job;

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

	/**
	 * The cron will auto-stop after it executes this number of transactions.
	 * @var integer
	 */
	private $stopAfterTransactionsCount = 30000;

	/**
	 * {@inheritDoc}
	 * @see \App\Cron\Job\Utils\GeneralAbstract::validateSettings()
	 */
	public function validateSettings(\App\Entity\Messages $msgs, array $fields)
	{

		if ($fields['scheduleOptions']['type']=='interval' ) {
			list($h, $m, $s) = explode(':', $fields['scheduleOptions']['repeatPeriod']);
			$repeatPeriod = $h*3600 + $m*60 + $s;
			if ($repeatPeriod<120) {
				$msgs->addWarningText("The process should not run more often than 2 minutes.", 'type');
			}
			if ($repeatPeriod>7200) {
				$msgs->addWarningText("The process should not run more seldom than 2 hours.", 'type');
			}
		} else {
			$msgs->addWarningText("The process should be set as `multiple times per day`.", 'type');
		}

	}


	/**
	 * {@inheritDoc}
	 * @see \App\Cron\Job\Utils\GeneralAbstract::executeJob()
	 */
	protected function executeJob()
	{
		// INITIAL CHECKS /////////////////////////////////////////////////////////////////////////
		// check the initial status
		if (!in_array(\App\ReportsData\Params::getInstance()->getStatus(), ['running', 'pending'])) {
			$currSts = \App\ReportsData\Params::getInstance()->getStatus();
			if ($currSts=='blocked') {
				$blockedUntil = \App\ReportsData\Params::getInstance()->get('blockedUntil');
				$blockingApp = \App\ReportsData\Params::getInstance()->get('blockingApp');
				$this->logger->idle("The cron will not start - the current RD queue status is `{$currSts}` until {$blockedUntil} (blocking app: {$blockingApp}).");
			} else {
				$this->logger->idle("The cron will not start - the current RD queue status is `{$currSts}`.");
			}

			return ;
		}

		// check if the backup cron is running
		$bakCron = new \App\Cron\Job\TinaBackup();
		if ($bakCron->isAnotherInstanceRunning()) {
			$this->logger->idle("The cron will not execute as there is a TINA backup cron in progress.");
			return ;
		}

		// forces the RQ queue to 'running'
		\App\ReportsData\Params::getAdmin()->saveStatusToRunning();

		// INITIALIZATIONS ////////////////////////////////////////////////////////////////////////

		// indicates if the current process should stop completely
		$stopProcess = false;

		// get RD queue manager
		$queueMng = \App\ReportsData\ChangesQueue\Manager::getInstance();

		// empty fast queue onto main queue
		\DB::startTransaction();
		$movedTransactionsList = $queueMng->moveDataFromFastQueueToMainQueue();
		\DB::commitTransaction();

		if ($movedTransactionsList===false) {
			$this->logger->warning( "A deadlock was found while copying the content from the fast queue to the main queue. This warning can be ignored, the next execution of the cron should run normally. The current process will stop now." );
			$stopProcess = true;
		}

		// log the moved records
		if ($movedTransactionsList) {
			$movedRecordsLog = [];
			foreach ($movedTransactionsList as $row) {
				$movedRecordsLog[] = $row['entity'].' #'.$row['id'];
			}
			$this->logger->info( "Moved ".count($movedRecordsLog)." transactions from fast queue to main queue: " . implode(', ', $movedRecordsLog) );
		}

		// number of processed records (to indicate status)
		$processedTransCount = 0;

		// if cron will run normally, calculate various counts for the main loop
		if (!$stopProcess) {

			// number of records in the queue at the beginning of the script
			$totalRecordsInitialCount = $queueMng->getTotalCountMain();

			// if nothing to do -
			if (!$totalRecordsInitialCount) {
				$this->logger->idle("The RD queue is empty.");
				$stopProcess = true;
			} else {
				$this->logger->info(sprintf("There are %s records in the RD queue.", $totalRecordsInitialCount));
			}

			// total transactions count on this execution (limited to stopAfterTransactionsCount)
			$totalTransCount = min($totalRecordsInitialCount, $this->stopAfterTransactionsCount);
			$executedTransactions = [];
		}

		// flag used for displaying the first record info
		$displayedFirstRecord = false;

		///////////////////////////////////////////////////////////////////////////////////////////
		// INFINITE LOOP FOR THE CURRENT PROCESS //////////////////////////////////////////////////
		///////////////////////////////////////////////////////////////////////////////////////////
		while (!$stopProcess) {

			// stop after a cretin number of transactions
			if ($processedTransCount>=$this->stopAfterTransactionsCount) {
				$this->logger->info("The cron will stop now - it is set to automatically stop after {$this->stopAfterTransactionsCount} transactions.");
				$stopProcess = true;
			}

			// CHECK THE CURRENT STATUS ///////////////////////////////////////////////////////////
			$currentStatus = \App\ReportsData\Params::getInstance()->getStatus();
			if (!in_array($currentStatus, ['running', 'pending'])) {
				$stopProcess = true;
				$this->logger->info("The cron will stop now - the RD queue status was meanwhile set to `{$currentStatus}`.");
			}

			///////////////////////////////////////////////////////////////////////////////////////
			// MAIN PROCESSING OF THE QUEUE RECORDS ///////////////////////////////////////////////
			///////////////////////////////////////////////////////////////////////////////////////
			if (!$stopProcess) {

				// update progress; check if sleep is needed
				$this->updateStatus([ 'progress'=>$processedTransCount/$totalTransCount ]);

				try {
					// the main execution - processing records from the queue
					\DB::startTransaction();
					$oldestRecord = $queueMng->getOldestRecordMainQueue();

					if (!$displayedFirstRecord) {
						$this->logger->info(sprintf("The first record in the queue is: %s #%s.", $oldestRecord['entity'], $oldestRecord['id']));
						$displayedFirstRecord = true;
					}

					// check if there are records in the queue
					if (!$oldestRecord) {
						$stopProcess = true;
						$this->logger->info("The RD queue is now empty - the cron will stop.");
					} 
					else {
						$queueMng->processRecord($oldestRecord['entity'], $oldestRecord['id']);
					}
					
					\DB::commitTransaction();

					// increase transactions count - if the last one existed
					if ($oldestRecord) {
						$processedTransCount++;
						$executedTransactions[] = $oldestRecord['entity'].' #'.$oldestRecord['id'];
					}

				} catch (\DBException $e) {
					\DB::rollbackTransaction();
					$stopProcess = true;
					$this->logger->critical('Found a DB error while processing: '.$e->getMessage().'. The current process will stop.');
				}

				// once in a while, display a message
				if (!($processedTransCount%1000)) {
					$this->logger->info(sprintf('Processed %s records since current cron process execution.', $processedTransCount));
				}

			}

		}

		// display the records count
		if ($processedTransCount) {
			// add final message
			$this->logger->success( "Successfully processed {$processedTransCount} transactions." );
			$this->logger->info( "Processed transactions: " . implode(', ', $executedTransactions) );
		}

		// forces the RD queue to 'pending'
		$currSts = \App\ReportsData\Params::getInstance()->getStatus();
		if ($currSts=='running') {
			\App\ReportsData\Params::getAdmin()->saveStatusToPending();
		}

	}

}


