<?php

declare(strict_types=1);

namespace mirzaev\ebala\controllers;

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

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

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

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

	/**
	 * Главная страница
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function index(array $parameters = []): ?string
	{
		// Авторизация
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт оператора или администратора

			foreach (['active', 'inactive'] as $name) {
				// Перебор фильтров статусов

				// Инициализация значения (приоритет у cookie)
				$value = $_COOKIE["markets_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0;

				// Инициализировано значение?
				if ($value === null || $value === 0) continue;

				// Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ)
				$this->view->{$name} = match ($value) {
					'0', 0 => 'earth',
					'1', 1 => 'sand',
					'2', 2 => 'river',
					default => 'earth'
				};
			}

			// Генерация представления
			$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'markets.html');
		} else $main = $this->authorization();

		// Возврат (успех)
		if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
		else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;

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

	/**
	 * Прочитать
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function read(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт оператора или администратора

			// Реинициализация актуальной страницы
			if (isset($parameters['page'])) $this->session->write(['markets' => ['page' => $parameters['page']]]);
			else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'])) $this->session->write(['markets' => ['page' => 1]]);

			// Инициализация буферов AQL-выражений для инъекции фильтра по статусам
			$filters_statuses_before_and = '';
			$filters_statuses_before_or = '';
			$filters_statuses_after_and = '';
			$filters_statuses_after_or = '';

			foreach (['active', 'inactive'] as $name) {
				// Перебор фильтров статусов

				// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
				if (empty($value = $_COOKIE["markets_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0)) continue;

				// Конвертация ярлыков
				$converted = match ($name) {
					'inactive' => 'active',
					default => $name
				};

				// Принадлежность фильтра
				$target = match ($converted) {
					'active' => 'account',
					default => 'market'
				};

				// Генерация выражения
				$expression = "$target.$converted == " . ($name === $converted ? 'true' : 'false');

				// Генерация AQL-выражения для инъекции в строку запроса
				if ($value === '1') ${'filters_statuses_' . ($target === 'account' ? 'before' : 'after') . '_and'} .= " && " . $expression;
				else if ($value === '2') ${'filters_statuses_' . ($target === 'account' ? 'before' : 'after') . '_or'} .= " || " . $expression;
			}

			// Очистка от бинарных операторов сравнения с только одним операндом (крайние)
			$filters_statuses_before_and = trim(trim(trim($filters_statuses_before_and), '&&'));
			$filters_statuses_before_or = trim(trim(trim($filters_statuses_before_or), '||'));
			$filters_statuses_after_and = trim(trim(trim($filters_statuses_after_and), '&&'));
			$filters_statuses_after_or = trim(trim(trim($filters_statuses_after_or), '||'));

			// Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ"
			$filters_statuses_before_merged = (empty($filters_statuses_before_and) ? '' : "($filters_statuses_before_and)") . (empty($filters_statuses_before_or) ? '' : (empty($filters_statuses_before_and) ? '' : ' || ') . "($filters_statuses_before_or)");
			$filters_statuses_after_merged = (empty($filters_statuses_after_and) ? '' : "($filters_statuses_after_and)") . (empty($filters_statuses_after_or) ? '' : (empty($filters_statuses_after_and) ? '' : ' || ') . "($filters_statuses_after_or)");

			// Инициализация общего буфера с AQL-выражениями
			$filters_before = '';
			$filters_after = '';

			// Объединение фильтров в единую строку с AQL-выражениями для инъекции
			if (!empty($filters_statuses_before_merged)) $filters_before .= empty($filters_before) ? $filters_statuses_before_merged : " && ($filters_statuses_before_merged)";
			if (!empty($filters_statuses_after_merged)) $filters_after .= empty($filters_after) ? $filters_statuses_after_merged : " && ($filters_statuses_after_merged)";

			// Инициализация строки поиска
			$search = $_COOKIE["markets_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters']['search'] ?? '';
			if (mb_strlen($search) < 3) $search = null;
			$search_query = empty($search)
				? null
				: <<<AQL
					SEARCH 
						a.commentary IN TOKENS(@search, 'text_ru') 
						|| a.address IN TOKENS(@search, 'text_ru') 
						|| STARTS_WITH(a._key, @search)
						|| STARTS_WITH(a.id, @search)
						|| STARTS_WITH(a.name.first, @search)
						|| STARTS_WITH(a.name.second, @search)
						|| STARTS_WITH(a.name.last, @search)
						|| STARTS_WITH(a.address, @search)
						|| STARTS_WITH(a.city, @search)
						|| STARTS_WITH(a.number, @search)
						|| STARTS_WITH(a.mail, @search)
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a._key, TOKENS(@search, 'text_en')[0], 2, true))
						|| (LENGTH(@search) > 2 && LEVENSHTEIN_MATCH(a.id, TOKENS(@search, 'text_en')[0], 1, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.address, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.city, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.number, TOKENS(@search, 'text_en')[0], 2, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.mail, TOKENS(@search, 'text_en')[0], 2, true))
					OPTIONS { collections: ["account", "market"] }
					AQL;

			// Инициализация данных для генерации HTML-документа с таблицей
			$this->view->rows = registry::markets(
				before: sprintf(
					<<<AQL
						%s
						FILTER a.deleted != true
					AQL,
					$search_query,
				),
				after: sprintf(
					<<<AQL
						let account = (IS_SAME_COLLECTION('account', a) ? a : b)
						let market = (IS_SAME_COLLECTION('market', a) ? a : b)
						FILTER account.type == 'market' && b.deleted != true
						%s
						%s
					AQL,
					empty($filters_before) ? null : "FILTER $filters_before",
					empty($filters_after) ? null : "FILTER $filters_after"
				),
				page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'],
				target: empty($search) ? account::COLLECTION : 'registry_accounts',
				binds: empty($search) ? [] : [
					'search' => $search
				]
			);

			// Запись в cookie (только таким методом можно записать "hostonly: true")		
			setcookie(
				'markets_page',
				(string) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'],
				[
					'expires' => strtotime('+1 hour'),
					'path' => '/',
					'secure' => true,
					'httponly' => false,
					'samesite' => 'strict'
				]
			);

			// Запись в глобальную переменную шаблонизатора обрабатываемой страницы
			$this->view->page = $parameters['page'];

			// Инициализация блока
			return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html');
		}

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

	/**
	 * Создать
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function create(array $parameters = []): void
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт администратора или оператора

			// Инициализация буфера ошибок
			$this->errors['account'] ??= [];

			try {
				// Проверка наличия переданного пароля
				if (!isset($parameters['account_password'])) throw new exception('Не получен пароль');
				else if (strlen($parameters['account_password']) < 6) throw new exception('Пароль должен быть не менее 6 символов');
				else if (!empty($parameters['market_number']) && strlen($parameters['market_number']) < 11) throw new exception('Несоответствие формату SIM-номера представителя');
				else if (!empty($parameters['account_number']) && strlen($parameters['account_number']) < 11) throw new exception('Несоответствие формату SIM-номера аккаунта представителя');
				else if (!empty($parameters['market_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['market_mail']) === 0) throw new exception('Несоответствие формату почты представителя');
				else if (!empty($parameters['account_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['account_mail']) === 0) throw new exception('Несоответствие формату почты аккаунта представителя');
				else if (!empty($parameters['market_id']) && model::read('d.id == "' . $parameters['market_id'] . '"', errors: $this->errors['account']) instanceof _document) throw new exception('Уже существует магазин с данным идентификатором');

				// Универсализация
				/* $parameters['market_number'] = (int) $parameters['market_number']; */
				/* $parameters['account_number'] = (int) $parameters['account_number']; */

				// Создание аккаунта
				$account = account::create(
					data: [
						'type' => 'market',
						'name' => [
							'first' => $parameters['account_name_first'],
							'second' => $parameters['account_name_second'],
							'last' => $parameters['account_name_last']
						],
						'number' => $parameters['account_number'] === 0 ? '' : $parameters['account_number'],
						'mail' => $parameters['account_mail'],
						'password' => sodium_crypto_pwhash_str(
							$parameters['account_password'],
							SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
							SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
						),
						'commentary' => $parameters['account_commentary']
					],
					errors: $this->errors['account']
				);

				// Проверка существования созданного аккаунта
				if (empty($account)) throw new exception('Не удалось создать аккаунт');
			} catch (exception $e) {
				// Write to the errors registry
				$this->errors['account'][] = [
					'text' => $e->getMessage(),
					'file' => $e->getFile(),
					'line' => $e->getLine(),
					'stack' => $e->getTrace()
				];
			}

			// Инициализация идентификатора магазина
			$id = empty($parameters['market_id']) ? model::id() : $parameters['market_id'];

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

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

			// Генерация ответа
			echo json_encode(
				[
					'clipboard' => empty($this->errors['account']) ? <<<TEXT
					Идентификатор: $id
					Пароль: {$parameters['account_password']}
					TEXT : '',
					'errors' => self::parse_only_text($this->errors['account'])
				]
			);

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

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

			try {
				if (isset($account)) {
					// Инициализирован аккаунт

					// Создание магазина
					$market = model::create(
						data: [
							'id' => (string) $id,
							'name' => [
								'first' => $parameters['market_name_first'],
								'second' => $parameters['market_name_second'],
								'last' => $parameters['market_name_last']
							],
							'number' => $parameters['market_number'] === 0 ? '' : $parameters['market_number'],
							'mail' => $parameters['market_mail'],
							'type' => $parameters['market_type'],
							'city' => $parameters['market_city'],
							'district' => $parameters['market_district'],
							'address' => $parameters['market_address'],
						],
						errors: $this->errors['account']
					);

					// Проверка существования созданного магазина
					if (empty($market)) throw new exception('Не удалось создать магазин');

					// Создание ребра: account -> market
					account::connect($account, $market, 'market', $this->errors['account']);
				}
				throw new exception('Не инициализирован аккаунт');
			} catch (exception $e) {
				// Write to the errors registry
				$this->errors['account'][] = [
					'text' => $e->getMessage(),
					'file' => $e->getFile(),
					'line' => $e->getLine(),
					'stack' => $e->getTrace()
				];
			}
		}
	}

	/**
	 * Прочитать данные
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function fields(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт администратора или оператора

			// Инициализация данных магазина 
			$market = model::read('d.id == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, type: d.type, city: d.city, district: d.district, address: d.address}')->getAll();

			if (!empty($market)) {
				// Найдены данные магазина

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

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

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

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

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

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

	/**
	 * Обновить данные
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function update(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт администратора или оператора

			// Инициализация данных магазина
			$market = model::read('d.id == "' . $parameters['id'] . '"');

			if (!empty($market)) {
				// Найден магазин

				// Инициализация параметров (перезапись переданными значениями)
				if ($parameters['name_first'] !== $market->name['first']) $market->name = ['first' => $parameters['name_first']] + $market->name;
				if ($parameters['name_second'] !== $market->name['second']) $market->name = ['second' => $parameters['name_second']] + $market->name;
				if ($parameters['name_last'] !== $market->name['last']) $market->name = ['last' => $parameters['name_last']] + $market->name;
				if ($parameters['number'] !== $market->number)
					if (mb_strlen($parameters['number']) === 11) $market->number = $parameters['number'];
					else throw new exception('Номер должен состоять из 11 символов');
				if ($parameters['mail'] !== $market->mail) $market->mail = $parameters['mail'];
				if ($parameters['type'] !== $market->type) $market->type = $parameters['type'];
				if ($parameters['city'] !== $market->city) $market->city = $parameters['city'];
				if ($parameters['district'] !== $market->district) $market->district = $parameters['district'];
				if ($parameters['address'] !== $market->address) $market->address = $parameters['address'];

				if (_core::update($market)) {
					// Записаны данные магазина

					// Инициализация строки в глобальную переменную шаблонизатора
					$this->view->rows = registry::markets(
						before: sprintf(
							"FILTER a._id == '%s' && a.deleted != true",
							$market->getId()
						),
						after: <<<AQL
							let account = (IS_SAME_COLLECTION('account', a) ? a : b)
							let market = (IS_SAME_COLLECTION('market', a) ? a : b)
							FILTER account.type == 'market' && b.deleted != true
						AQL,
						amount: 1,
						target: model::COLLECTION
					);

					// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
					$this->view->page = null;

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

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

					// Инициализация буфера ответа
					$return = [
						'updated' => true,
						'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'),
						'errors' => self::parse_only_text($this->errors)
					];

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

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

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не удалось записать изменения в базу данных');
			} else throw new exception('Не удалось найти аккаунт');
		}

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

	/**
	 * Прочитать данные магазинов для <datalist>
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function datalist(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
			// Авторизован аккаунт оператора или магазина

			// Инициализация данных магазинов
			$this->view->markets = model::read(filter: 'd.active == true', amount: 10000, return: '{ id: d.id, name: d.name }');

			// Универсализация
			if ($this->view->markets instanceof _document) $this->view->markets = [$this->view->markets];

			// Возврат (успех)
			return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'markets.html');
		}

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