<?php
namespace ModulesAbstract\PluginReports\Report;

/**
 * TODO: re-check this description
 * The main controller class for the reports module
 * 	- checks if the module is setup and if it isn't it displays an error message and disallows report generation
 * 	- displays report filters
 * 	- displays report data (subreports & error report)
 * 		- allows individual report download
 * 		- allows per-report errors preview
 * 	- displays report history
 * 	- displays report details (generated date, used memory, server load)
 * 	- starts the report generation, locks the interface
 * 	- handles tags
 * 	- handles reports operations (delete, upload, zip-download)
 *
 ***************************************************************************************************************
 * UDEF support:
 * 	- in Config/Controller.php, allow action 'generateInputMatrixRow'
 * 	- in Config/View_Index.php, do something along the lines of:
 * 		$this
 *			->setUDEFDataProcessor(new \ModulesAbstract\PluginReports\ExtraSupport\UDEF\Model\DataProcessor())
 *			->setAvailableUDEFs(range(1, 99));
 *
 *		$matrix = $this->generateInputMatrix();
 *		$matrix
 *			->setName('UDEFConfigs')
 *			->setValue($formFields['UDEFConfigs']);
 *		$form
 *			->addContainer(\Engine\Form\Container\Fieldset::create()
 *				->setCollapsible(true)
 *				->addElement($matrix)
 *			);
 *
 *	- in Report/Model/Builder.php,
 *		- initialize ExtraSupport\UDEF\Model\DataProcessor() & ExtraSupport\UDEF\Model\ConfigProcessor()
 *		- per row, pickUDEFConfig() and processField() should be used
 *		(this part is more complicated, and can vary as needed, so look for real-life-examples)
 *
 * @author Lucian Sirbu
 */
abstract class Controller extends \App\Mvc\ControllerAbstract
{
	protected $absLang= [];

	/**
	 * The storage object
	 * @var \ModulesAbstract\PluginReports\Report\Model\ReportStorage\Disk
	 */
	protected $reportStorageObj = null;

	private $reportJustGenerated = false;

	/**
	 * (non-PHPdoc)
	 * @see \App\Mvc\Controller\Window\PaginatedListing\PaginatedListing::initialize()
	 */
	protected function initialize()
	{
		parent::initialize();

		$this->actionsCfg['index']= [];
		$this->actionsCfg['generateReport']= [];
		$this->actionsCfg['toggleTag']= [];
		$this->actionsCfg['reloadHistory']= [];
		$this->actionsCfg['loadReport']= [];
		$this->actionsCfg['loadFileErrors']= [];
		$this->actionsCfg['downloadFile']= [];
		$this->actionsCfg['downloadReportArchive']= [];
		$this->actionsCfg['deleteReport']= [];

		// $this->actionsCfg['uploadReport']= [];
		// $this->actionsCfg['generateAndUploadReport']= [];

		$this->absLang = \App\Loader_Lang::getInstance()->loadAppFolder('_customLoad/modulesAbstract/pluginReports/report')->getSection('ModulesAbstract/PluginReports/Report');
	}

	/**
	 * Returns true if the generateAndUploadReport action is enabled
	 * @return boolean
	 */
	public function hasGenerateAndUploadReportAction()
	{
		return array_key_exists('generateAndUploadReport', $this->actionsCfg);
	}

	/**
	 * Implement here the actual report generation, with specific generators, gatherers, builders
	 * @param array $R
	 * @return \Engine\Mvc\Response\Html
	 */
	protected function actionGenerateReport($R)
	{
		global $S;
		$S[$this->getModule()]['filters'] = $R;
		unset($S[$this->getModule()]['filters']['showMessage']);

		$messages = NULL;

		try {
			$idReport = $this->doGenerateReport($R, 'browser');

			if ($idReport) {
				$this->reportJustGenerated = true;
			}
		}
		catch (\Exception\Validator\Base $ex) {
			$messages = \App\MessagesCollector::create();
			$messages->addWarning($ex->getMessage());
		}
		catch (\App\MessagesException $ex) {
			$messages = $ex->getMessagesCollector();
		}

		return $this->actionIndex([], null, $messages);
	}

	/**
	 * Implement here the actual report generation, with specific generators, gatherers, builders
	 * @param array $R
	 * @return \Engine\Mvc\Response\Html
	 */
	protected function actionGenerateAndUploadReport($R)
	{
		// security check. This action should not be easily accessible
		if (!$this->hasGenerateAndUploadReportAction()) {
			throw new \Exception("The generateAndUploadReport action is not enabled!");
		}

		global $S;
		$S[$this->getModule()]['filters'] = $R;
		unset($S[$this->getModule()]['filters']['showMessage']);

		$messages = \App\MessagesCollector::create();

		try {
			// generate the report, "force" the generation mode to cronjob
			$idReport = $this->doGenerateReport($R, 'cronjob');
			$messages->addSuccess($this->absLang['index']['reportJustGenerated']);
			$this->doUploadReport($idReport, $messages);
		}
		catch (\App\MessagesException $ex) {
			$messages = $ex->getMessagesCollector();
		}

		return $this->actionIndex([], null, $messages);
	}

	/**
	 * Implement here the actual report generation, with specific generators, gatherers, builders
	 * @param array $R
	 * @param string $generationMode
	 */
	abstract protected function doGenerateReport(array $R, string $generationMode);


	/**
	 * Create the storage object
	 * @return \ModulesAbstract\PluginReports\Report\Model\ReportStorage\Disk
	 */
	abstract protected function createReportStorageObj();

	/**
	 * Get the local storage object
	 * @return \ModulesAbstract\PluginReports\Report\Model\ReportStorage\Disk|\ModulesAbstract\PluginReports\Report\Model\ReportStorage\DiskPciDss
	 */
	public function getReportStorageObj()
	{
		if ($this->reportStorageObj) {
			return $this->reportStorageObj;
		}

		$this->reportStorageObj = $this->createReportStorageObj();

		if (array_key_exists('uploadReport', $this->actionsCfg)) {
			$this->reportStorageObj->insertPossibleTag('uploaded', 		count($this->reportStorageObj->getPossibleTags())+1);
		}

		$this->reportStorageObj->setBasePath($this->getReportBasePath());
		return $this->reportStorageObj;
	}

	/**
	 * Get the report base path
	 * @return string
	 */
	abstract protected function getReportBasePath();

	/**
	 * {@inheritDoc}
	 * @see \App\Mvc\ControllerAbstract::buildViewObjectFromCurrentModuleForAction()
	 * @return \ModulesAbstract\PluginReports\Report\View_Index
	 */
	public function buildViewObjectFromCurrentModuleForAction($action)
	{
		$class = sprintf('\\%s\View_%s', str_replace('/', "\\", $this->getModule()), ucfirst($action));
		if (!class_exists($class)) {
			$class = sprintf('\ModulesAbstract\PluginReports\Report\View_%s', ucfirst($action));
		}

		$view = new $class($this);
		$view->setOutputVar('actionsCfg', $this->actionsCfg);
		return $view;
	}

	/**
	 * Returns true/false if the report is not configured - disabled form fields, hide generate button and display message
	 * @return boolean
	 */
	protected function isReportConfigured()
	{
		return true;
	}

	/**
	 * Returns default filters
	 * @return Array
	 */
	protected function getDefaultFilters()
	{
		return [];
	}

	/**
	 * Render the main page
	 * @param array $R
	 * @return \Engine\Mvc\Response\Html
	 */
	protected function actionIndex($R, ?\App\MessagesCollector $msgs = NULL, ?\App\MessagesCollector $reportDetailsMsgs = NULL)
	{
		// instantiate the main view
		$view = $this->buildViewObjectFromCurrentModuleForAction('index');

		/* @var $view \ModulesAbstract\PluginReports\Report\View_Index */

		// get the last generated report id
		$storage = $this->getReportStorageObj();

		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$reportsList = $this->sortReports($storage->getActiveReportsIds(), $storage);	// get the reports list, not filtered
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			$reportsList = $this->sortReports($this->filterReports($storage->getActiveReportsIds(), $storage));
			if ($reportsList) {
				$idReport = reset($reportsList);
			}
		}

		// format the reports list
		$reportsListDetailed= [];
		foreach ($reportsList as $id) {
			$reportsListDetailed[$id] = $this->generateReportLabel($id, $storage);
		}

		// send action-index data to the view
		$view->setOutputVar('reportsListDetailed', 	$reportsListDetailed);

		$view->setOutputVar('idReport', $idReport);
		$view->addInput('isReportConfigured', $this->isReportConfigured());


		global $S;
		if ($this->getAction()=='index') {
			$filters = array_merge($this->getDefaultFilters(), (array)$S[$this->getModule()]['filters'], $R);
		}
		else {
			$filters = $S[$this->getModule()]['filters'];
		}

		$view->setOutputVar('filterContainer', 		$view->generateFilterContainer($filters));	// used to read the LANG's back or render, as needed

		if ($idReport) {
			$errReportsSize = (is_file($storage->getErrorsReportPath($idReport))?filesize($storage->getErrorsReportPath($idReport)):0);

			$exportedFiles = $this->sortReportExportedFiles($storage->getReportExportedFiles($idReport));
			// report-specific variables
			$reportScucesfullyGenerated = !(!$exportedFiles
										&& !$storage->getReportInternalLog($idReport)
										&& !$storage->getReportStats($idReport)
										&& !$storage->getReportTags($idReport));

			if ($this->reportJustGenerated) {
				$view->getReportDetailsMessagesCollector()->addSuccess($this->absLang['index']['reportJustGenerated']);
			}

			if ($R['showMessage']) {
				foreach($R['showMessage'] as $msg) {
					$view->getReportDetailsMessagesCollector()->addMessage(new \App\Message($msg['type'], $msg['text']));
				}
			}

			$view->setOutputVar('reportScucesfullyGenerated', 	$reportScucesfullyGenerated);
			$view->setOutputVar('errorsReportSize', 			$errReportsSize);
			$view->setOutputVar('errorsReportExists', 			$errReportsSize>0);
			$view->setOutputVar('reportExportedFiles', 			$exportedFiles);
			$view->setOutputVar('reportFilters', 				$storage->getReportFilters($idReport));
			$view->setOutputVar('reportHistory', 				$storage->getReportHistory($idReport));
			$view->setOutputVar('reportInternalLog', 			$storage->getReportInternalLog($idReport));
			$view->setOutputVar('reportStats', 					$storage->getReportStats($idReport));
			$view->setOutputVar('reportTags', 					$storage->getReportTags($idReport));
			$view->setOutputVar('possibleTags', 				$storage->getPossibleTags());

		}

		if ($msgs) {
			$view->getMessagesCollector()->appendMessages($msgs);
		}
		if ($reportDetailsMsgs) {
			$view->getReportDetailsMessagesCollector()->appendMessages($reportDetailsMsgs);
		}

		// render the view
		return new \Engine\Mvc\Response\Html($view->render());
	}

	protected function sortReports($reportsList)
	{
		return $reportsList;
	}

	protected function sortReportExportedFiles($exportedFilesList)
	{
		return $exportedFilesList;
	}

	protected function filterReports($reportsList, \ModulesAbstract\PluginReports\Report\Model\ReportStorage\GeneralAbstract $storage)
	{
		$minDate = \Engine\DateTime::create()->modify("-1 years")->formatDatetimeIso();

		return array_slice(array_filter($reportsList, function($idReport) use ($storage, $minDate) {
			$components = $storage->extractReportLabelComponents($idReport);
			return $components['datetime'] > $minDate;
		}), 0, 30);
	}

	/**
	 * Check if a report is expired
	 *
	 * @param String $idReport
	 * @param \ModulesAbstract\PluginReports\Report\Model\ReportStorage\GeneralAbstract $storage
	 * @return void
	 */
	protected function reportIsExpired($idReport, \ModulesAbstract\PluginReports\Report\Model\ReportStorage\GeneralAbstract $storage)
	{
		// by default, filter reports older than 1 year
		$components = $storage->extractReportLabelComponents($idReport);
		$minDate = \Engine\DateTime::create()->modify("-1 years")->formatDatetimeIso();
		return $components['datetime'] < $minDate;
	}

	protected function generateReportLabel($idReport, $storage)
	{
		$components = $storage->extractReportLabelComponents($idReport);
		$tags = $storage->getReportTags($idReport);

		$dt = \Engine\DateTime::createFromIsoDatetime($components['datetime']);
		$d1 = \Engine\Smarty\Modifiers\DateTimeFormat::create($dt);
		$d2 = $dt->format("H:i");

		if ($tags) {
			return sprintf($this->absLang['reportsList']['label'], sprintf("%s, %s (%s)", $d1, $d2, implode(", ", $tags)));
		}
		else {
			return sprintf($this->absLang['reportsList']['label'], sprintf("%s, %s", $d1, $d2));
		}
	}

	/**
	 * Toggle the specified tag
	 * @param array $R
	 * @return \Engine\Mvc\Response\Json
	 */
	protected function actionToggleTag($R)
	{
		// initialize
		$storage = $this->getReportStorageObj();

		// validate
		$idReport = null;
		$tag = $R['tag'];
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		if (!$tag) {
			throw new \Engine\Mvc\ControllerException("No tag specified");
		}

		// do the actual action
		$tags = $storage->toggleReportTag($idReport, $tag)->getReportTags($idReport);

		$storage->appendReportHistoryEvent($idReport, "toggleTag", ['tag'=>$tag, 'newValue'=>in_array($tag, $tags)], CURRENT_ID_USER, time());

		// instantiate the main view
		$view = $this->buildViewObjectFromCurrentModuleForAction('index');

		// build the response
		$response = new \Engine\Mvc\Response\Json();
		$json = $response->getBody();
		$json->tag = $tag;
		$json->value = in_array($tag, $tags);
		$json->reportLabel = $this->generateReportLabel($idReport, $storage);
		$json->reportHistory = $view->renderReportHistory($idReport, $storage->getReportHistory($idReport));
		return $response;
	}

	protected function actionReloadHistory($R)
	{
		// initialize
		$storage = $this->getReportStorageObj();

		// validate
		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		// instantiate the main view
		$view = $this->buildViewObjectFromCurrentModuleForAction('index');

		// build the response
		$response = new \Engine\Mvc\Response\Json();
		$json = $response->getBody();
		$json->reportHistory = $view->renderReportHistory($idReport, $storage->getReportHistory($idReport));
		return $response;
	}

	protected function actionLoadReport($R)
	{
		$response = new \Engine\Mvc\Response\Json();
		$json = $response->getBody();

		// instantiate the view
		$view = $this->buildViewObjectFromCurrentModuleForAction('index');

		// get the last generated report id
		$storage = $this->getReportStorageObj();

		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}


		$errReportsSize = (is_file($storage->getErrorsReportPath($idReport))?filesize($storage->getErrorsReportPath($idReport)):0);

		// format the reports list
		$reportsListDetailed= [];
		foreach ($this->sortReports($this->filterReports($storage->getActiveReportsIds(), $storage)) as $id) {
			$reportsListDetailed[$id] = $this->generateReportLabel($id, $storage);
		}

		// send action-index data to the view
		$view->setOutputVar('reportsListDetailed', 	$reportsListDetailed);

		// report-specific variables
		$view->addInput('isReportConfigured', $this->isReportConfigured());

		$filters= [];
		$view->setOutputVar('filterContainer', 		$view->generateFilterContainer($filters));	// we're generating the filters form to be able to read the LANG's back in the "used filters list" section
		$view->setOutputVar('idReport', $idReport);

		$reportScucesfullyGenerated = !(!$storage->getReportExportedFiles($idReport)
										&& !$storage->getReportInternalLog($idReport)
										&& !$storage->getReportStats($idReport)
										&& !$storage->getReportTags($idReport));

		$view->setOutputVar('reportScucesfullyGenerated', 	$reportScucesfullyGenerated);
		$view->setOutputVar('errorsReportSize', 	$errReportsSize);
		$view->setOutputVar('errorsReportExists', 	$errReportsSize>0);
		$view->setOutputVar('reportExportedFiles', 	$storage->getReportExportedFiles($idReport));
		$view->setOutputVar('reportFilters', 		$storage->getReportFilters($idReport));
		$view->setOutputVar('reportHistory', 		$storage->getReportHistory($idReport));
		$view->setOutputVar('reportInternalLog', 	$storage->getReportInternalLog($idReport));
		$view->setOutputVar('reportStats', 			$storage->getReportStats($idReport));
		$view->setOutputVar('reportTags', 			$storage->getReportTags($idReport));
		$view->setOutputVar('possibleTags', 		$storage->getPossibleTags());

		// render the view
		$json->id = $idReport;
		$json->html = $view->renderReport($idReport);

		return $response;
	}

	protected function actionDownloadFile($R)
	{
		global $G;
		$R['fileId'] = $G['fileId'];	// allow filenames like "Ana are 'me & pe' câ    ĂŞÎţ.pdf"

		// initialize
		$storage = $this->getReportStorageObj();

		// validate
		$idReport = null;
		$fileId = $R['fileId'];
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		if (!$fileId) {
			throw new \Engine\Mvc\ControllerException("No file specified");
		}

		// do the actual action
		$reportFiles = $storage->getReportExportedFiles($idReport);

		if ($fileId!='errors.csv') {
			if (!isset($reportFiles[$fileId])) {
				throw new \Engine\Mvc\ControllerException("File not found");
			}
		}

		if ($fileId=='errors.csv') {
			$storage->appendReportHistoryEvent($idReport, "downloadFile", ['fileId' => $fileId], CURRENT_ID_USER, time());
		}
		else {
			$storage->appendReportHistoryEvent($idReport, "downloadErrors", ['fileId' => $fileId], CURRENT_ID_USER, time());
		}

		// build the response
		if ($fileId=='errors.csv') {
			$response = new \Engine\Mvc\Response\FileDownload(\Engine\File::getContents($storage->getErrorsReportPath($idReport)));
			$response->setFilename($fileId);
		}
		else {
			$response = new \Engine\Mvc\Response\FileDownload(\Engine\File::getContents($reportFiles[$fileId]->getRealPath()));
			$response->setFilename($reportFiles[$fileId]->getFilename());
		}
		return $response;
	}

	protected function actionDownloadReportArchive($R)
	{
		// initialize
		$storage = $this->getReportStorageObj();

		// validate
		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		$archiveFile = sprintf("%s/archive.zip", $storage->getTemporaryReportPath($idReport));

		// do the actual action
		$zip = new \App\Archive\Compress\Zip();
		$zip
			->open($storage->getReportRealPath($idReport))
			->compressTo($archiveFile);
		$zip->close();


		$storage->appendReportHistoryEvent($idReport, "downloadReportArchive", [], CURRENT_ID_USER, time());

		$response = new \Engine\Mvc\Response\FileDownload(\Engine\File::getContents($archiveFile));
		$response->setFilename($this->actionDownloadReportArchive_generateArchiveName($storage, $idReport));
		return $response;
	}

	protected function actionDeleteReport($R)
	{
		// initialize
		$storage = $this->getReportStorageObj();

		// validate
		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		$this->doDeleteReport($idReport);

		$reportsList = $storage->getActiveReportsIds();
		if ($reportsList) {
			return $this->actionLoadReport(['idReport' => reset($reportsList)]);
		}
		else {

			$response = new \Engine\Mvc\Response\Json();
			$json = $response->getBody();

			// render the view
			$json->id = null;
			$json->html = "No reports generated";

			return $response;
		}
	}

	/**
	 * Delete a report and all its files
	 * @param string $idReport
	 * @return true
	 */
	public function doDeleteReport($idReport)
	{
		$storage = $this->getReportStorageObj();

		$storage->appendReportHistoryEvent($idReport, "delete", [], CURRENT_ID_USER, time());

		$storage->deleteReport($idReport);

		return true;
	}

	/**
	 * Delete expired reports
	 * @param Array $R
	 * @return Array
	 */
	public function doDeleteExpiredReports($R)
	{
		$storage = $this->getReportStorageObj();

		$self = $this;
		$exporedReportsList = array_filter($storage->getActiveReportsIds(), function($idReport) use ($storage, $self) {
			return $self->reportIsExpired($idReport, $storage);
		});

		foreach ($exporedReportsList as $idReport) {
			$storage->appendReportHistoryEvent($idReport, "delete", [], CURRENT_ID_USER, time());
			$storage->deleteReport($idReport);
		}

		return $exporedReportsList;
	}

	/**
	 * Delete expired reports, called by cronjob
	 * @param \Engine\Logger\MultipleLogger $logger
	 * @param string $reportLabel
	 */
	public function doDeleteExpiredReports_fromCronjob(\Engine\Logger\MultipleLogger $logger, $reportLabel)
	{
		$logger->info(sprintf("Start deleting expired reports from %s", $reportLabel));
		$deletedReports = $this->doDeleteExpiredReports([]);

		if ($deletedReports) {
			$logger->info(sprintf("Deleted reports:"));
			foreach ($deletedReports as $report) {
				$logger->info(sprintf(" > %s", $report));
			}
			$logger->success(sprintf("Deleted %d expired reports from %s", count($deletedReports), $reportLabel));
		}
		else {
			$logger->idle(sprintf("No reports expired in %s", $reportLabel));
		}

	}

	/**
	 * Delete any left unencrypted files using a cronjob
	 * TODO THIS IS NOT YET USED
	 * @param \Engine\Logger\MultipleLogger $logger
	 * @param string $reportLabel
	 */
	// public function doDeleteUnencryptedReports_fromCronjob(\Engine\Logger\MultipleLogger $logger, $reportLabel)
	// {
	// 	$storage = $this->getReportStorageObj();

	// 	foreach ($storage->getActiveReportsIds() as $idReport) {
	// 		if ($storage->doDeleteReportUnencryptedFolder($idReport)) {
	// 			$logger->info(sprintf('Deleted unencrypted report %s / %s', $reportLabel, $idReport));
	// 		}
	// 	}
	// }

	protected function actionDownloadReportArchive_generateArchiveName($storage, $idReport)
	{
		return "archive.zip";
	}

	/**
	 * Load the errors file
	 * @param array $R
	 * @throws \Engine\Mvc\ControllerException
	 * @return \Engine\Mvc\Response\Json
	 */
	protected function actionLoadFileErrors($R)
	{
		// initialize
		$storage = $this->getReportStorageObj();
		set_time_limit(1800);

		// validate
		$idReport = null;
		$fileId = $R['fileId'];
		$maxItems = ($R['maxItems']?:PHP_INT_MAX);
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		if (!$fileId) {
			throw new \Engine\Mvc\ControllerException("No file specified");
		}


		// do the actual action
		$reportFiles = $storage->getReportExportedFiles($idReport);
		if ($fileId!='errors.csv') {
			if (!isset($reportFiles[$fileId])) {
				throw new \Engine\Mvc\ControllerException("File not found");
			}
		}

		if ($fileId=='errors.csv') {
			$storage->appendReportHistoryEvent($idReport, "loadErrors", ['fileId'=>$fileId, 'maxItems'=>$maxItems,], CURRENT_ID_USER, time());
		}
		else {
			$storage->appendReportHistoryEvent($idReport, "loadFileErrors", ['fileId'=>$fileId, 'maxItems'=>$maxItems,], CURRENT_ID_USER, time());
		}

		$csvReader = new \App\Utils\CSV\Reader\Reader();
		$csvReader->openFile($storage->getErrorsReportPath($idReport));
		$csvReader->setHeaderRow($csvReader->next(false));

		$messages= [];
		$columnIdForThisFile = null;
		if ($fileId!='errors.csv') {
			$columnIdForThisFile = \Engine\File::getName($reportFiles[$fileId]->getRealPath());
		}

		$totalItems = -1;
		while ($row = $csvReader->next()) {
			$totalItems++;

			if (count($messages)<($maxItems-2)) {
				if ($columnIdForThisFile) {
					if ($row->get('report')==$columnIdForThisFile) {
						$messages[] = $row->getFields();
					}
				}
				else {
					$messages[] = $row->getFields();
				}
			}
		}

		// build the response
		$response = new \Engine\Mvc\Response\Json();
		$view = $this->buildViewObjectFromCurrentModuleForAction('index');
		$json = $response->getBody();
		$json->reportErrors = 	$view->renderReportErrors($idReport, $messages, $totalItems);
		$json->reportHistory = 	$view->renderReportHistory($idReport, $storage->getReportHistory($idReport));
		return $response;
	}

	/**
	 * Do the actual report uploading
	 * @param array $R
	 * @return \Engine\Mvc\Response\HtmlRedirect
	 */
	protected function actionUploadReport($R)
	{
		// validate
		$idReport = null;
		if (!$idReport && $R['idReport']) {
			$idReport = $R['idReport'];
		}
		if (!$idReport) {
			throw new \Engine\Mvc\ControllerException("No id specified");
		}

		$messageCollector = \App\MessagesCollector::create();
		$this->doUploadReport($idReport, $messageCollector);

		// before 2025-07-28, the message collector was not used, and all reports were reported as success, unless they thrown out exceptions, so code below is for compatibility
		if (!$messageCollector->getMessages()) {
			$messageCollector->addSuccess($this->absLang['index']['reportJustUploaded']);
		}

		return new \Engine\Mvc\Response\HtmlRedirect(\Engine\Mvc\Dispatcher::getInstance()->getAppUrl($this->getModule(), null, ['showMessage' => array_map(function($message) {
			return [
				'type' => $message->getType(),
				'text' => $message->getText(),
			];
		}, $messageCollector->getMessages())]));

	}

	/**
	 * Implement the actual report uploading
	 * @param string $idReport
	 * @throws \Exception
	 */
	public function doUploadReport($idReport, \App\MessagesCollector $messageCollector)
	{
		throw new \Exception("Not implemented");
	}

}
