<?php

declare(strict_types=1);

namespace mirzaev\huesos\controllers;

// Files of the project
use mirzaev\huesos\controllers\core,
	mirzaev\huesos\models\account;

// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
	mirzaev\minimal\http\enumerations\status;

// Framework for ArangoDB
use mirzaev\arangodb\document;

/**
 * Controller of session
 *
 * @package mirzaev\huesos\controllers
 *
 * @param array $errors Registry of errors
 *
 * @method null write(string ...$parameters) Write to the session buffer
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class session extends core
{
	/**
	 * Errors
	 *
	 * @var array $errors Registry of errors
	 */
	protected array $errors = [
		'session' => [],
		'account' => [],
		'buffer' => []
	];

	/**
	 * Connect session to the telegram account
	 *
	 * @see https://core.telegram.org/bots/webapps#initializing-mini-apps
	 * @see https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
	 * @see https://core.telegram.org/bots/webapps#webappinitdata
	 *
	 * @param ?string $user JSON
	 * @param ?string $chat_instance
	 * @param ?string $chat_type
	 * @param ?string $auth_date
	 * @param ?string $hash
	 * @param ?string $query_id
	 *
	 * @return null
	 */
	public function telegram(
		?string $user = null,
		?string $chat_instance = null,
		?string $chat_type = null,
		?string $auth_date = null,
		?string $hash = null,
		?string $query_id = null
	): null {
		if (str_contains($this->request->headers['accept'], content::json->value)) {
			// Request for JSON response

			// Declaring variables in the correct scope
			$identifier = $domain = $language = null;

			if ($connected = isset($this->account)) {
				// Found the account

				// Initializing identifier of the account
				$identifier = $this->account->identifier;

				// Initializing language of the account
				$language = $this->account->language;

				// Initializing domain of the account
				$domain = $this->account->domain;
			} else {
				// Not found the account

				if (isset($user, $chat_instance, $chat_type, $auth_date, $hash)) {
					// Received required parameters

					$buffer = ['user' => $user, 'chat_instance' => $chat_instance, 'chat_type' => $chat_type, 'auth_date' => $auth_date];

					ksort($buffer);

					$prepared = [];
					foreach ($buffer as $key => $value) {
						if (is_array($value)) {
							$prepared[] = $key . '=' . json_encode($value, JSON_UNESCAPED_UNICODE);
						} else {
							$prepared[] = $key . '=' . $value;
						}
					}

					$key = hash_hmac('sha256', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'), 'WebAppData', true);
					$_hash = bin2hex(hash_hmac('sha256', implode(PHP_EOL, $prepared), $key, true));

					if (hash_equals($hash, $_hash)) {
						// Data confirmed (according to telegram documentation)

						if (time() - $auth_date < 86400) {
							// Authorization date less than 1 day ago

							// Initializing data of the account
							$data = json_decode($user);

							// Initializing of the account
							$account = account::initialize(
								$data->id,
								[
									'identifier' => $data->id,
									'name' => [
										'first' => $data->first_name,
										'last' => $data->last_name
									],
									'domain' => $data->username,
									'language' => $data->language_code,
									'messages' => $data->allows_write_to_pm,
									'chat' => [
										'type' => $chat_type,
										'instance' => $chat_instance
									]
								],
								$this->errors['account']
							);

							if ($account instanceof account) {
								// Initialized the account

								// Connecting the account to the session
								$connected = $this->session->connect($account, $this->errors['session']);

								// Initializing identifier of the account
								$identifier = $account->identifier;

								// Initializing language of the account
								$language = $account->language;

								// Initializing domain of the account
								$domain = $account->domain;
							}
						}
					}
				}
			}

			// Sending response
			$this->response
				->start()
				->clean()
				->sse()
				->json([
					'connected' => (bool) $connected,
					'identifier' => $identifier ?? null,
					'domain' => $domain ?? null,
					'language' => $language?->name ?? null,
					'errors' => $this->errors
				])
				->validate($this->request)
				?->body()
				->end();
		}

		// Exit (success/fail)
		return null;
	}

	/**
	 * Write 
	 *
	 * Write to the buffer
	 *
	 * @param mixed ...$parameters Parameters for writing to the buffer
	 *
	 * @return null
	 *
	 * @todo переделать под trait buffer
	 */
	public function write(mixed ...$parameters): null
	{
		if (!empty($parameters) && isset($this->session)) {
			// Received parameters and initialized model with buffer trait

			// Declaring the buffer of deserialized parameters
			$deserialized = [];

			foreach ($parameters as $name => $value) {
				// Iterate over parameters

				// Validation of the parameter value
				if (mb_strlen(serialize($value)) > 4096) continue;

				// Declaring the buffer of deserialized parameter
				$parameter = null;

				// Deserializing name to multidimensional array
				foreach (array_reverse(explode('_', (string) $name)) as $key)
					$parameter = [$key => $parameter ?? (is_string($value) && json_validate($value) ? json_decode($value, true, 10) : $value)];

				// Writing into the buffer of deserialized parameters
				$deserialized = array_merge_recursive($parameter, $deserialized);
			}

			// Write to the document from ArangoDB
			if (!empty($deserialized)) $this->session->write($deserialized, $this->errors['buffer']);

			// Writing status of response
			$this->response->status = status::created;
		}

		// Exit (success)
		return null;
	}
}
