<?php

declare(strict_types=1);

namespace mirzaev\ebala\controllers;

// Файлы проекта
use mirzaev\ebala\controllers\core,
	mirzaev\ebala\controllers\traits\errors,
	mirzaev\ebala\models\account,
	mirzaev\ebala\models\worker,
	mirzaev\ebala\models\market;

// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;

// Встроенные библиотеки
use exception;

/**
 * Контроллер сессии 
 *
 * @package mirzaev\ebala\controllers
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class session extends core
{
	use errors;

	/**
	 * Записать номер сотрудника во все буферы сессии
	 *
	 * Проверяет существование аккаунта сотрудника с этим номером
	 * и запоминает для использования в процессе аутентификации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function worker(array $parameters = []): void
	{
		// Инициализация буфера ответа
		$buffer = [];

		// Инициализация реестра возвращаемых параметров
		$return = explode(',', $parameters['return'], 50);

		try {
			// Проверка наличия обязательных параметров
			if (empty($parameters['worker'])) throw new exception('Необходимо передать номер');

			// Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки)
			preg_match('/^\d(\d{10})/', preg_replace("/[^\d]/", "", $parameters['worker']), $matches);

			// Инициализация номера
			$parameters['worker'] = isset($matches[1]) ? 7 . $matches[1] : $parameters['worker'];

			// Вычисление длины
			$length = mb_strlen($parameters['worker']);

			// Проверка параметров на соответствование требованиям
			if ($length === 0) throw new exception('Номер не может быть пустым');
			if ($length !== 11) throw new exception('Номер должен иметь 11 цифр');
			if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['worker'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));

			if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
				// Запрошено запоминание

				// Запись в cookie
				setcookie('entry_number', $parameters['worker'], [
					'expires' => strtotime('+1 day'),
					'path' => '/',
					'secure' => true,
					'httponly' => true,
					'samesite' => 'strict'
				]);
			}

			// Поиск аккаунта
			$worker = worker::read('d.number == "' . $parameters['worker'] . '"', amount: 1);

			// Генерация ответа по запрашиваемым параметрам
			foreach ($return as $parameter) match ($parameter) {
				'exist' => $buffer['exist'] = isset($worker),
				'account' => (function () use ($parameters, $remember, &$buffer) {
					// Запись в буфер сессии
					if ($remember) $this->session->write(['entry' => ['number' => $parameters['worker']]], $this->errors);

					// Поиск аккаунта и запись в буфер вывода
					$buffer['account'] = (new account($this->session, 'worker', $this->errors))?->instance() instanceof _document;
				})(),
				'verify' => $buffer['verify'] = true,
				'errors' => null,
				default => throw new exception("Параметр не найден: $parameter")
			};
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Запись реестра ошибок в буфер ответа
		if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);

		// Запись заголовков ответа
		header('Content-Type: application/json');
		header('Content-Encoding: none');
		header('X-Accel-Buffering: no');

		// Инициализация буфера вывода
		ob_start();

		// Генерация ответа
		echo json_encode($buffer);

		// Запись заголовков ответа
		header('Content-Length: ' . ob_get_length());

		// Отправка и деинициализация буфера вывода
		ob_end_flush();
		flush();

		// Запись в буфер сессии
		if (!in_array('account', $return, true) && ($remember ?? false))
			$this->session->write(['entry' => ['number' => $parameters['worker']]]);
	}

	/**
	 * Записать идентификатор администратора во все буферы сессии
	 *
	 * Проверяет существование аккаунта администратора с этим идентификатором
	 * и запоминает для использования в процессе аутентификации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function administrator(array $parameters = []): void
	{
		// Инициализация буфера ответа
		$buffer = [];

		// Инициализация реестра возвращаемых параметров
		$return = explode(',', $parameters['return'], 50);

		try {
			// Проверка наличия обязательных параметров
			if (empty($parameters['administrator'])) throw new exception('Необходимо передать идентификатор');

			// Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки)
			preg_match('/^\d{3,12}/', preg_replace("/[^\d]/", "", $parameters['administrator']), $matches);

			// Инициализация номера
			$parameters['administrator'] = $matches[0];

			// Вычисление длины
			$length = mb_strlen($parameters['administrator']);

			// Проверка параметров на соответствование требованиям
			if ($length === 0) throw new exception('Идентификатор аккаунта не может быть пустым');
			if ($length > 12) throw new exception('Идентификатор аккаунта должен иметь не более 12 цифр');
			if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['administrator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));

			if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
				// Запрошено запоминание

				// Запись в cookie
				setcookie(
					'entry__key',
					$parameters['administrator'],
					[
						'expires' => strtotime('+1 day'),
						'path' => '/',
						'secure' => true,
						'httponly' => true,
						'samesite' => 'strict'
					]
				);
			}

			// Поиск аккаунта
			$account = account::read('d._key == "' . $parameters['administrator'] . '"', amount: 1);

			// Генерация ответа по запрашиваемым параметрам
			foreach ($return as $parameter) match ($parameter) {
				'exist' => $buffer['exist'] = isset($account),
				'account' => (function () use ($parameters, $remember, &$buffer) {
					// Запись в буфер сессии
					if ($remember) $this->session->write(['entry' => ['_key' => $parameters['administrator']]], $this->errors);

					// Поиск аккаунта и запись в буфер вывода
					$buffer['account'] = (new account($this->session, 'administrator', $this->errors))?->instance() instanceof _document;
				})(),
				'verify' => $buffer['verify'] = true,
				'errors' => null,
				default => throw new exception("Параметр не найден: $parameter")
			};
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Запись реестра ошибок в буфер ответа
		if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);

		// Запись заголовков ответа
		header('Content-Type: application/json');
		header('Content-Encoding: none');
		header('X-Accel-Buffering: no');

		// Инициализация буфера вывода
		ob_start();

		// Генерация ответа
		echo json_encode($buffer);

		// Запись заголовков ответа
		header('Content-Length: ' . ob_get_length());

		// Отправка и деинициализация буфера вывода
		ob_end_flush();
		flush();

		// Запись в буфер сессии
		if (!in_array('account', $return, true) && ($remember ?? false))
			$this->session->write(['entry' => ['_key' => $parameters['administrator']]]);
	}

	/**
	 * Записать идентификатор оператора во все буферы сессии
	 *
	 * Проверяет существование аккаунта оператора с этим идентификатором
	 * и запоминает для использования в процессе аутентификации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function operator(array $parameters = []): void
	{
		// Инициализация буфера ответа
		$buffer = [];

		// Инициализация реестра возвращаемых параметров
		$return = explode(',', $parameters['return'], 50);

		try {
			// Проверка наличия обязательных параметров
			if (empty($parameters['operator'])) throw new exception('Необходимо передать идентификатор');

			// Очистка всего кроме цифр, а потом поиск 10 первых чисел (без восьмёрки)
			preg_match('/^\d{3,12}/', preg_replace("/[^\d]/", "", $parameters['operator']), $matches);

			// Инициализация номера
			$parameters['operator'] = $matches[0];

			// Вычисление длины
			$length = mb_strlen($parameters['operator']);

			// Проверка параметров на соответствование требованиям
			if ($length === 0) throw new exception('Идентификатор аккаунта не может быть пустым');
			if ($length > 12) throw new exception('Идентификатор аккаунта должен иметь не более 12 цифр');
			if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['operator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));

			if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
				// Запрошено запоминание

				// Запись в cookie
				setcookie('entry__key', $parameters['operator'], [
					'expires' => strtotime('+1 day'),
					'path' => '/',
					'secure' => true,
					'httponly' => true,
					'samesite' => 'strict'
				]);
			}

			// Поиск аккаунта
			$account = account::read('d._key == "' . $parameters['operator'] . '"', amount: 1);

			// Генерация ответа по запрашиваемым параметрам
			foreach ($return as $parameter) match ($parameter) {
				'exist' => $buffer['exist'] = isset($account),
				'account' => (function () use ($parameters, $remember, &$buffer) {
					// Запись в буфер сессии
					if ($remember) $this->session->write(['entry' => ['_key' => $parameters['operator']]], $this->errors);

					// Поиск аккаунта и запись в буфер вывода
					$buffer['account'] = (new account($this->session, 'operator', $this->errors))?->instance() instanceof _document;
				})(),
				'verify' => $buffer['verify'] = true,
				'errors' => null,
				default => throw new exception("Параметр не найден: $parameter")
			};
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Запись реестра ошибок в буфер ответа
		if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);

		// Запись заголовков ответа
		header('Content-Type: application/json');
		header('Content-Encoding: none');
		header('X-Accel-Buffering: no');

		// Инициализация буфера вывода
		ob_start();

		// Генерация ответа
		echo json_encode($buffer);

		// Запись заголовков ответа
		header('Content-Length: ' . ob_get_length());

		// Отправка и деинициализация буфера вывода
		ob_end_flush();
		flush();

		// Запись в буфер сессии
		if (!in_array('account', $return, true) && ($remember ?? false))
			$this->session->write(['entry' => ['_key' => $parameters['operator']]]);
	}

	/**
	 * Записать идентификатор магазина во все буферы сессии
	 *
	 * Проверяет существование аккаунта магазина с этим идентификатором
	 * и запоминает для использования в процессе аутентификации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function market(array $parameters = []): void
	{
		// Инициализация буфера ответа
		$buffer = [];

		// Инициализация реестра возвращаемых параметров
		$return = explode(',', $parameters['return'], 50);

		try {
			// Проверка наличия обязательных параметров
			if (empty($parameters['market'])) throw new exception('Необходимо передать идентификатор');

			// Вычисление длины
			$length = mb_strlen($parameters['market']);

			// Проверка параметров на соответствование требованиям
			if ($length === 0) throw new exception('Идентификатор аккаунта аккаунта не может быть пустым');
			if ($length > 3) throw new exception('Идентификатор аккаунта аккаунта должен иметь не более 3 символов');
			if (preg_match_all('/[^\d]+/u', $parameters['market'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));

			if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
				// Запрошено запоминание

				// Запись в cookie
				setcookie('entry_id', $parameters['market'], [
					'expires' => strtotime('+1 day'),
					'path' => '/',
					'secure' => true,
					'httponly' => true,
					'samesite' => 'strict'
				]);
			}

			// Поиск магазина
			$market = market::read('d.id == "' . $parameters['market'] . '"', amount: 1);

			// Генерация ответа по запрашиваемым параметрам
			foreach ($return as $parameter) match ($parameter) {
				'exist' => $buffer['exist'] = isset($market),
				'account' => (function () use ($parameters, $remember, &$buffer) {
					// Запись в буфер сессии
					if ($remember) $this->session->write(['entry' => ['id' => $parameters['market']]], $this->errors);

					// Поиск аккаунта и запись в буфер вывода
					$buffer['account'] = (new account($this->session, 'market', $this->errors))?->instance() instanceof _document;
				})(),
				'verify' => $buffer['verify'] = true,
				'errors' => null,
				default => throw new exception("Параметр не найден: $parameter")
			};
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Запись реестра ошибок в буфер ответа
		if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);

		// Запись заголовков ответа
		header('Content-Type: application/json');
		header('Content-Encoding: none');
		header('X-Accel-Buffering: no');

		// Инициализация буфера вывода
		ob_start();

		// Генерация ответа
		echo json_encode($buffer);

		// Запись заголовков ответа
		header('Content-Length: ' . ob_get_length());

		// Отправка и деинициализация буфера вывода
		ob_end_flush();
		flush();

		// Запись в буфер сессии
		if (!in_array('account', $return, true) && ($remember ?? false))
			$this->session->write(['entry' => ['id' => $parameters['market']]]);
	}

	/**
	 * Записать пароль любого типа аккаунта во все буферы сессии
	 *
	 * Проверяет на соответствие требованиям 
	 * и запоминает для использования в процессе аутентификации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function password(array $parameters = []): void
	{
		// Инициализация буфера ответа
		$buffer = [];

		// Инициализация реестра возвращаемых параметров
		$return = explode(',', $parameters['return'], 50);

		try {
			// Вычисление длины
			$length = strlen($parameters['password']);

			// Проверка параметров на соответствование требованиям
			if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов');
			if (preg_match_all('/[^\w\-\_\d\s]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));

			// Инициализация значения по умолчанию для типа аккаунта
			$parameters['type'] ??= 'worker';

			// Генерация ответа по запрашиваемым параметрам
			foreach ($return as $parameter) match ($parameter) {
				'account' => (function () use ($parameters, &$buffer) {
					// Запись в буфер сессии
					if (isset($parameters['remember']) && $parameters['remember'] === '1')
						$this->session->write(['entry' => ['password' => $parameters['password']]], $this->errors);

					// Поиск аккаунта и запись в буфер вывода
					$buffer['account'] = (new account($this->session, $parameters['type'], $this->errors))?->instance() instanceof _document;
				})(),
				'verify' => $buffer['verify'] = true,
				'errors' => null,
				default => throw new exception("Параметр не найден: $parameter")
			};
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Запись реестра ошибок в буфер ответа
		if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);

		// Запись заголовков ответа
		header('Content-Type: application/json');
		header('Content-Encoding: none');
		header('X-Accel-Buffering: no');

		// Инициализация буфера вывода
		ob_start();

		// Генерация ответа
		echo json_encode($buffer);

		// Запись заголовков ответа
		header('Content-Length: ' . ob_get_length());

		// Отправка и деинициализация буфера вывода
		ob_end_flush();
		flush();

		// Запись в буфер сессии
		if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
			$this->session->write(['entry' => ['password' => $parameters['password']]]);
	}

	/**
	 * Записать в буфер сессии
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void
	 */
	public function write(array $parameters = []): void
	{
		try {
			if ($this->account->status()) {
				// Авторизован аккаунт

				// Инициализация директорий для генерации
				$directories = explode('_', $parameters['name'], 100);

				// Инициализированы директории?
				if (count($directories) === 0) return;

				// Конвертация: filter -> filters
				if ($directories[1] === 'filter') $directories[1] .= 's';

				// Инициализация буфера вывода
				$response = [];

				// Инициализация буфера выполнения
				$buffer = &$response;

				foreach ($directories as $directory) {
					// Перебор директорий

					// Инициализация структуры
					$buffer[$directory] = ($next = next($directories) === false) ? $parameters['value'] : [];

					// Реинициализация цели для инициализации структуры (углубление в массив)
					if ($next) unset($buffer);
					else $buffer = &$buffer[$directory];
				}

				// Запись в буфер сессии
				$this->session->write($response);
			}
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
	}

	/**
	 * Прочитать из буфера сессии
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return ?string Данные из буфера сессии, если найдены
	 */
	public function read(array $parameters = []): ?string
	{
		try {
			if ($this->account->status()) {
				// Авторизован аккаунт

				// Инициализация директорий для генерации
				$directories = explode('_', $parameters['name'], 100);

				// Инициализированы директории?
				if (count($directories) === 0) return null;

				// Конвертация: filter -> filters
				if ($directories[1] === 'filter') $directories[1] .= 's';

				// Инициализация буфера хранилища
				$storage = $this->session->buffer[$_SERVER['INTERFACE']];

				// Инициализация буфера выполнения
				$buffer = &$storage[reset($directories)] ?? null;

				// Найдена первая директория в базе данных?
				if (isset($buffer) === 0) return null;

				foreach ($directories as &$directory) {
					// Перебор директорий

					// Инициализация новой целевой директории
					if (isset($buffer[$directory])) $buffer = &$buffer[$directory];
					else continue;

					// Реинициализация цели для инициализации структуры (углубление в массив)
					if (next($directories) === false) $buffer = $buffer[$directory];
				}

				// Возврат (успех)
				return is_array($buffer) ? null : $buffer;
			}
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors['session'][] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Возврат (провал)
		return null;
	}
}
