<?php
namespace ModulesAbstract\PluginReports\Report\Model\ReportStorage;

class DiskPciDss extends \ModulesAbstract\PluginReports\Report\Model\ReportStorage\Disk
{

	/**
	 * {@inheritDoc}
	 * @see \ModulesAbstract\PluginReports\Report\Model\ReportStorage\Disk::createReport()
	 */
	public function createReport($useUnencryptedFolder=false)
	{
		$idReport = parent::createReport($useUnencryptedFolder);

		$reportBaseDir = $this->getReportPath($idReport);

		if ($useUnencryptedFolder) {
			$dirpath = sprintf("%s/content_unencrypted/", $reportBaseDir);
			if (!\Engine\Dir::create($dirpath)) {
				throw new \Exception(sprintf("Cannot create folder: Path: %s", $dirpath));
			}
		}

		return $idReport;
	}

	/**
	 * Get the unencryped folder path
	 * @param string $idReport
	 * @return string
	 */
	public function getReportRealPathUnencrypted($idReport)
	{
		return sprintf("%s/content_unencrypted/", $this->getReportPath($idReport));
	}

	/**
	 * Get the unencrypted exported files (could be in a different folder depending on the PCI-DSS setting)
	 * @param string $idReport
	 * @return array|\Engine\array:\SplFileInfo
	 */
	public function getReportExportedFilesUnencrypted($idReport)
	{
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		$pciDssCompliant = $CFG['pciDss']['compliant'];

		if ($pciDssCompliant) {
			$generatedFilesList = \Engine\Dir::getFileListRecursive($this->getReportRealPathUnencrypted($idReport));
		}
		else {
			$generatedFilesList = \Engine\Dir::getFileListRecursive($this->getReportRealPath($idReport));
		}

		if (!$generatedFilesList) {
			return [];
		}

		return $generatedFilesList;
	}

	/**
	 * Delete unencrypted report files
	 * @param string $idReport
	 */
	public function doDeleteReportUnencryptedFolder($idReport)
	{
		if (\Engine\Dir::check($this->getReportRealPathUnencrypted($idReport))) {
			return \Engine\Dir::delete($this->getReportRealPathUnencrypted($idReport));
		}
	}


	/**
	 * Generate a random encryption key
	 * @param string $idReport
	 * @return string
	 */
	public function generateEncryptionKey(string $idReport):string
	{
		$reportBaseDir = $this->getReportPath($idReport);

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		switch ($CFG['pciDss']['encryption']) {
			case 'AmazonKms':
				// @see https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKey.html

				$awsKeyId = $CFG['pciDss']['amazonKmsCredentials']['keyId'];
				$awsKMSConfig = [
					'region'	=> $CFG['pciDss']['amazonKmsCredentials']['region'],
					'version'	=> 'latest',
				];

				// AWS KMS can load credentials from the environment, so they are optional locally
				if ($CFG['pciDss']['amazonKmsCredentials']['key'] or $CFG['pciDss']['amazonKmsCredentials']['secret']) {
					$awsKMSConfig['credentials'] = [
						'key' => 	$CFG['pciDss']['amazonKmsCredentials']['key'],
						'secret' => $CFG['pciDss']['amazonKmsCredentials']['secret'],
					];
				}

				$kmsClient = new \Aws\Kms\KmsClient($awsKMSConfig);

				$dataKey = $kmsClient->generateDataKey([
					'KeyId' => 		   $awsKeyId,
					'NumberOfBytes' => SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
				]);

				$plaintextDataKey = $dataKey['Plaintext'];

				// write on disk $plaintextDataKey
				if (\Engine\File::create($reportBaseDir."/AWS-KMS-CiphertextBlob.bin", $dataKey['CiphertextBlob'])===false) {
					throw new \RuntimeException("Cannot write AWS-KMS-CiphertextBlob.bin in the report path");
				}
			break;

			case 'Dummy':
			case 'LocalCodeKey':
				// non-KMS mode
				$uniqueDataArr = [];
				$uniqueDataArr[] = $CFG['pciDss']['localCodeKey'];
				$uniqueDataArr[] = basename($this->getBasePath());
				$uniqueDataArr[] = $idReport;

				// append all file names
				foreach ($this->getReportExportedFilesUnencrypted($idReport) as $k=>$file) {
					$uniqueDataArr[]= $k;
				}
				$hash = hash('sha256', implode(',', $uniqueDataArr), true);
				$plaintextDataKey = substr($hash, 0, SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
			break;

			default:
				throw new \Exception('Unknown encryption mode: '.$CFG['pciDss']['encryption']);
		}

		return $plaintextDataKey;
	}

	/**
	 * Generate a random decryption key
	 * @param string $idReport
	 * @return string
	 */
	public function generateDecryptionKey(string $idReport):string
	{
		$reportBaseDir = $this->getReportPath($idReport);

		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();
		switch ($CFG['pciDss']['encryption']) {
			case 'AmazonKms':
				// @see https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html

				// read from disk $plaintextDataKey. !!! For Amazon KMS this key is mandatory !!!
				$CiphertextBlob = \Engine\File::getContents($reportBaseDir."/AWS-KMS-CiphertextBlob.bin");
				if ($CiphertextBlob===false) {
					throw new \RuntimeException(sprintf("Cannot read AWS-KMS-CiphertextBlob.bin in the report path %s", $reportBaseDir));
				}

				$awsKeyId = $CFG['pciDss']['amazonKmsCredentials']['keyId'];
				$awsKMSConfig = [
					'region'	=> $CFG['pciDss']['amazonKmsCredentials']['region'],
					'version'	=> 'latest',
				];

				// AWS KMS can load credentials from the environment, so they are optional locally
				if ($CFG['pciDss']['amazonKmsCredentials']['key'] or $CFG['pciDss']['amazonKmsCredentials']['secret']) {
					$awsKMSConfig['credentials'] = [
						'key' => 	$CFG['pciDss']['amazonKmsCredentials']['key'],
						'secret' => $CFG['pciDss']['amazonKmsCredentials']['secret'],
					];
				}

				$kmsClient = new \Aws\Kms\KmsClient($awsKMSConfig);

				$dataKey = $kmsClient->decrypt([
					'KeyId' => 		   $awsKeyId,
					'CiphertextBlob' => $CiphertextBlob
				]);

				$plaintextDataKey = $dataKey['Plaintext'];
			break;

			case 'Dummy':
			case 'LocalCodeKey':
				// non-KMS mode
				$uniqueDataArr = [];
				$uniqueDataArr[] = $CFG['pciDss']['localCodeKey'];
				$uniqueDataArr[] = basename($this->getBasePath());
				$uniqueDataArr[] = $idReport;

				// append all file names
				foreach ($this->getReportExportedFilesUnencrypted($idReport) as $k=>$file) {
					$uniqueDataArr[]= $k;
				}
				$hash = hash('sha256', implode(',', $uniqueDataArr), true);
				$plaintextDataKey = substr($hash, 0, SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
			break;

			default:
				throw new \Exception('Unknown encryption mode: '.$CFG['pciDss']['encryption']);
		}

		return $plaintextDataKey;
	}

	/**
	 * Generate a random encryption nonce
	 * @param string $idReport
	 * @return string
	 */
	public function generateEncryptionNonce(string $idReport):string
	{
		// safest way to generate a nonce would be to simply do this:
		// $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

		// but that means we also need to store it in the DB, and DB is not available here, so we need to generate it dynamically
		$uniqueDataArr = [];
		$uniqueDataArr[] = $idReport;
		foreach ($this->getReportExportedFilesUnencrypted($idReport) as $k=>$file) {
			$uniqueDataArr[]= $k;
		}
		$hash = hash('sha256', implode(',', $uniqueDataArr), true);
		$nonce = substr($hash, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

		return $nonce;
	}

	/**
	 * Generate a random decryption nonce
	 * @param string $idReport
	 * @return string
	 */
	public function generateDecryptionNonce(string $idReport):string
	{
		return $this->generateEncryptionNonce($idReport);
	}

}
