<?php
namespace App\Cron\Job;

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

	/**
	 * 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();
		$form->setStyle('width', '200');
		$form->addContainer($formContainer);

		$fld = new \Engine\Form\Input\Select($_LANG['Admin']['CronJobs']['Management']['AmadeusImportFromSFTP']['options']['filelimit'], 'filelimit', $fields['settings']['filelimit'], [
			'all' =>  	'all files',
			'10000' =>  '10k files',
			'5000' =>   '5k files',
			'1000' =>   '1k files',
			'100' =>    '100 files',
			'1' =>    	'1 files',
		]);
		$fld->setTooltip($_LANG['Admin']['CronJobs']['Management']['FluXCheck']['options']['filelimit']);
		$formContainer->addElement($fld);


		$fld = new \Engine\Form\Input\Text($_LANG['Admin']['CronJobs']['Management']['AmadeusImportFromSFTP']['options']['usedFluxInstanceId'], 'usedFluxInstanceId', $fields['settings']['usedFluxInstanceId']);
		$fld->setTooltip($_LANG['Admin']['CronJobs']['Management']['FluXCheck']['options']['usedFluxInstanceId']);
		$formContainer->addElement($fld);

		$formContainer->groupInputsInArray("settings");

		return $form;
	}

	public function validateSettings(\App\Entity\Messages $msgs, array $fields)
	{
		if (!\Entity\FluxInstance::select($fields['settings']['usedFluxInstanceId'])->recordExists() || \Entity\FluxInstance::select($fields['settings']['usedFluxInstanceId'])->disabled) {
			$msgs->addWarningText(sprintf("Specified FluX instance doesn't exist or is disabled"));
		}

		$fluxInstance = \Entity\FluxInstance::select($fields['settings']['usedFluxInstanceId']);
		if ($fluxInstance->behaviourOnInvalidFile!='moveToSkipped') {
			$msgs->addWarningText(sprintf("Specified FluX instance is not configured to move files to the skipped folder on errors"));
		}

		$transfer = new \App\Utils\FileTransfer\SftpCliTransfer();
		// $transfer->setLogger(\Log::getInstance())->setDebugOptions(['command', 'output', ]);	// for remote debugging, logs all sent and received data

		$sftpAuthenticationData = unserialize(\App\Amadeus\Config::getInstance()->get("sftp"));
		$transfer->connect($sftpAuthenticationData['hostname'], $sftpAuthenticationData['port'], $sftpAuthenticationData['username'], $sftpAuthenticationData['password']);
		if ($sftpAuthenticationData['privateKey']) {
			$privateKeyStr = \App\Amadeus\Config::getInstance()->decryptStr($sftpAuthenticationData['privateKey']);
			$transfer->connectUsingPrivateKeyStr($privateKeyStr);
		}

		try {
			$transfer->getFolderContents($fluxInstance->basePath);
		}
		catch (\App\Utils\FileTransfer\TransferException $ex) {
			\Log::getInstance()->critical(sprintf("[%s] Basepath lising failed. Probably could not connect to SFTP server: %s", __METHOD__, $ex->getMessage()), [
				'ex' => new \Engine\Logger\Context\ExceptionAsHtml($ex),
			]);

			$msgs->addWarningText(sprintf("Specified FluX instance is not configured to load files from an existing folder (basePath) from the remote server, or the remote server is not accessible"));
		}

		try {
			$transfer->getFolderContents($fluxInstance->confirmedPath);
		}
		catch (\App\Utils\FileTransfer\TransferException $ex) {
			\Log::getInstance()->critical(sprintf("[%s] Basepath lising failed. Probably could not connect to SFTP server: %s", __METHOD__, $ex->getMessage()), [
				'ex' => new \Engine\Logger\Context\ExceptionAsHtml($ex),
			]);

			$msgs->addWarningText(sprintf("Specified FluX instance is not configured to move files to an existing folder (confirmedPath) on the remote server, or the remote server is not accessible"));
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Cron\Job\Utils\GeneralAbstract::executeJob()
	 */
	protected function executeJob()
	{
		$this->updateStatus(['currentStep'=>'initialize', 'progress'=>0.0, ]);

		$fluxInstance = \Entity\FluxInstance::select($this->settings['usedFluxInstanceId']);
		$sftpAuthenticationData = unserialize(\App\Amadeus\Config::getInstance()->get("sftp"));

		$transfer = new \App\Utils\FileTransfer\SftpCliTransfer();
		// $transfer->setLogger(\Log::getInstance())->setDebugOptions(['command', 'output', ]);	// for remote debugging, logs all sent and received data

		$this->logger->info(sprintf("Connecting %s@%s", $sftpAuthenticationData['username'], $sftpAuthenticationData['hostname']));
		$transfer->connect($sftpAuthenticationData['hostname'], $sftpAuthenticationData['port'], $sftpAuthenticationData['username'], $sftpAuthenticationData['password']);

		if ($sftpAuthenticationData['privateKey']) {
			$privateKeyStr = \App\Amadeus\Config::getInstance()->decryptStr($sftpAuthenticationData['privateKey']);
			$transfer->connectUsingPrivateKeyStr($privateKeyStr);
		}

		$files = $transfer->getFolderContents($fluxInstance->basePath);

		// remove folders from this listing, reformat everything as a simple linear list of strings
		$files = array_map(function($a) {
				return $a['name'];
			},
			array_filter($files, function($a) {
				return !$a['is_dir'];
			})
		);

		// sort the list by name
		usort($files, function($a, $b) {
			return strcmp($a, $b);
		});

		$this->logger->info(sprintf("Found %s files before filtering the files list", number_format(count($files))));

		$matcher = new \App\Utils\BaseParser\Utils\WildcardMatcher();
		$files = array_values(array_filter($files, function($file) use ($fluxInstance, $matcher) {
			return !$matcher->matchList($file, $fluxInstance->ignoredFiles);
		}));

		$this->logger->info(sprintf("Found %s files that can be loaded", number_format(count($files))));

		// limit the list to the first elements in the list, according to settings['filelimit]
		if ($this->settings['filelimit']=='all') {
			// we should no truncate the file list now, the user want to parse everything in one run
			$this->logger->info(sprintf("Parsing all files"));
		}
		else {
			$files = array_slice($files, 0, $this->settings['filelimit']);
			$this->logger->info(sprintf("Parsing %s files", number_format(count($files))));
		}

		$tempFolder = \Engine\Dir::tempnam("AmadeusImportFromSFTP");
		$controller = new \Modules\Orders\Amadeus\FluX\ImportFromTemporaryFolder();
		$controller->logger = $this->logger;

		$mainFluXResponse = new \ModulesAbstract\FluxRest\Response\Main();

		$idx = 0;
		$total = count($files);
		foreach ($files as $filename) {
			$idx++;

			$remoteFilePath = sprintf("%s/%s", $fluxInstance->basePath, $filename);
			$localFilePath = sprintf("%s/%s", $tempFolder, $filename);

			$this->logger->info(sprintf("%s", basename($filename)));
			$transfer->downloadFile($remoteFilePath, $localFilePath);

			$mainFluXResponse->clearOperations();
			$controller->parseAndSaveFileContents($mainFluXResponse, $filename, file_get_contents($localFilePath), $fluxInstance, []);

			foreach ($mainFluXResponse->operations as $op) {
				switch ($op->action ) {
					case 'NotifyUserLog':
						switch ($op->type) {
							case 'warning':
								$this->logger->warning(sprintf(" ... %s", $op->text));
							break;
							case 'error':
								$this->logger->error(sprintf(" ... %s", $op->text));
							break;
							case 'info':
								$this->logger->info(sprintf(" ... %s", $op->text));
							break;

							default:
								$this->logger->info(sprintf(" ... %s", $op->text));
							break;
						}
					break;

					case 'NotifyUserTray':
						$this->updateStatus([
							'message' => 				$op->text,	// this is ignored anyway
						]);
					break;

					case 'Wait':
						$this->logger->info(sprintf(" ... wait %.1s", $op->timeout));
						sleep(min(5, $op->timeout));	// sleep maximum 5 seconds
					break;

					case 'MoveFile':
						// make sure the older exists
						// unfortunately there is no way to correctly and atomically create a deep folder without creating all child subfolders first
						//	And existing child subfolders will raise an error when running mkdir
						try {
							$transfer->mkdir(\Engine\File::getPath($op->newpath));
						}
						catch (\App\Utils\FileTransfer\TransferException $ex) {
							// ignore errors here
						}

						$transfer->moveRemoteFileOnRemote($remoteFilePath, $op->newpath);
					break;

					default:
						$this->logger->error(sprintf("The cronJob didn't handle FluX operation '%s', params: %s", $op->action, json_encode($op)));
				}
			}

			$filesPerSecond = $idx/(microtime(true) - $this->startDate);

			$this->updateStatus([
				'progress' => 				$idx/$total,
				'file' => 					$filename,
				'files/s' => 				$filesPerSecond,
				'remainingFilesForTotal'=>  $total - $idx,
				'progressFromTotal' => 		$idx/$total,
			]);
		}

		\Engine\Dir::delete($tempFolder);
	}
}
