<?php

declare(strict_types=1);

namespace mirzaev\huesos\models;

// Files of the project
use mirzaev\huesos\models\core,
	mirzaev\huesos\models\reservation,
	mirzaev\huesos\models\traits\buffer,
	mirzaev\huesos\models\traits\document as document_trait,
	mirzaev\huesos\models\interfaces\document as document_interface,
	mirzaev\huesos\models\interfaces\collection as collection_interface,
	mirzaev\huesos\models\enumerations\language,
	mirzaev\huesos\models\enumerations\currency;

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

// Library for ArangoDB
use ArangoDBClient\Document as _document;

// Built-in libraries
use exception;

/**
 * Model of cart
 *
 * @uses reservation
 * @package mirzaev\huesos\models
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class cart extends core implements document_interface, collection_interface
{
	use document_trait, buffer {
		buffer::write as buffer_write;
	}

	/**
	 * Name of the collection in ArangoDB
	 */
	final public const string COLLECTION = 'cart';

	/**
	 * Search for all products
	 *
	 * Search for all products in the cart
	 *
	 * @param language|null $language Language
	 * @param currency|null $currency Currency
	 * @param array &$errors Registry of errors
	 *
	 * @return array|null Array with implementing objects of documents from ArangoDB, if found
	 */
	public function products(
		?language $language = language::en,
		?currency $currency = currency::usd,
		array &$errors = []
	): ?array {
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
						// Initialized collections

						// Search for all products in the cart
						$result = @collection::execute(
							<<<AQL
								FOR v IN 1..1 INBOUND @cart GRAPH @graph
								FILTER IS_SAME_COLLECTION(@collection, v._id)
								COLLECT d = v WITH COUNT INTO amount
								RETURN {
									[d._id]: {
										amount,
										document: MERGE(d, {
											name: d.name.@language, 
											description: d.description.@language, 
											compatibility: d.compatibility.@language, 
											brand: d.brand.@language, 
											cost: d.cost.@currency
										})		
									}
								}
							AQL,
							[
								'graph' => 'catalog',
								'cart' => $this->getId(),
								'collection' => product::COLLECTION,
								'language' => $language->name,
								'currency' => $currency->name
							],
							flat: true,
							errors: $errors
						);

						/*
						* МеНЯ ЭТО РАЗДРАЖАЕТ
						*/

						$products = [];

						foreach ($result ?? [] as $raw) {
							foreach ($raw as $key => $value) {
								$products[$key] = $value;
							}
						}

						// Exit (success)
						return isset($products['amount']) ? [$products['document']['_id'] => $products] : $products;
					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Search for summary of all products
	 *
	 * Search for summary of all products in the cart
	 *
	 * @param currency|null $currency Currency
	 * @param array &$errors Registry of errors
	 *
	 * @return array|null Array with implementing objects of documents from ArangoDB, if found
	 */
	public function summary(
		?currency $currency = currency::usd,
		array &$errors = []
	): ?array {
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
						// Initialized collections

						// Search for all products in the cart
						$result = @collection::execute(
							<<<AQL
							FOR v IN 1..1 INBOUND @cart GRAPH @graph
								FILTER IS_SAME_COLLECTION(@collection, v._id)
								COLLECT AGGREGATE amount = COUNT(v), cost = SUM(v.cost.@currency)
								RETURN { amount, cost }
							AQL,
							[
								'graph' => 'catalog',
								'cart' => $this->getId(),
								'collection' => product::COLLECTION,
								'currency' => $currency->name
							],
							flat: true,
							errors: $errors
						);

						// Exit (success)
						return $result;
					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}


	/**
	 * Count
	 *
	 * Count of the product in the cart
	 *
	 * @param product $product The product
	 * @param int $limit Limit for counting
	 * @param array &$errors Registry of errors
	 *
	 * @return int|null Amount of the product in the cart, if counted
	 */
	public function count(product $product, int $limit = 100, array &$errors = []): ?int
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
						// Initialized collections

						// Search for the products in the cart and count them
						return (int) collection::execute(
							<<<AQL
							FOR v IN 1..1 INBOUND @cart GRAPH @graph
								FILTER IS_SAME_COLLECTION(@collection, v._id) && v._id == @product
								LIMIT @limit
								COLLECT WITH COUNT INTO length
								RETURN length
							AQL,
							[
								'graph' => 'catalog',
								'cart' => $this->getId(),
								'collection' => product::COLLECTION,
								'product' => $product->getId(),
								'limit' => $limit
							],
							errors: $errors
						);
					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/*
	 * Write
	 *
	 * Write the product in the cart
	 *
	 * @param product $product The product
	 * @param int $amount Amount of writings
	 * @param array &$errors Registry of errors
	 *
	 * @return void
	 */
	public function write(product $product, int $amount = 1, array &$errors = []): void
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
						// Initialized collections

						// Writing the product to the cart
						collection::execute(
							<<<AQL
							FOR i IN 1..@amount
								INSERT { _from: @product, _to: @cart, created: @created, updated: @created, active: true } INTO @@edge
							AQL,
							[
								'cart' => $this->getId(),
								'product' => $product->getId(),
								'@edge' => reservation::COLLECTION,
								'amount' => $amount,
								'created' => time()
							],
							errors: $errors
						);
					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
	}

	/*
	 * Delete
	 *
	 * Delete the product from the cart
	 *
	 * @param product $product The product
	 * @param int $amount Amount of deletings
	 * @param array &$errors Registry of errors
	 *
	 * @return void
	 */
	public function delete(product $product, int $amount = 1, array &$errors = []): void
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
						// Initialized collections

						// Deleting the product from the cart
						collection::execute(
							<<<AQL
							FOR v, e IN 1..1 INBOUND @cart GRAPH @graph
								FILTER IS_SAME_COLLECTION(@collection, v._id) && v._id == @product
								LIMIT @amount
								REMOVE e._key IN @@reservation
							AQL,
							[
								'graph' => 'catalog',
								'cart' => $this->getId(),
								'collection' => product::COLLECTION,
								'product' => $product->getId(),
								'@reservation' => reservation::COLLECTION,
								'amount' => $amount
							],
							errors: $errors
						);
					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
	}

	/*
	 * Share
	 *
	 * Generate hash for sharing the cart
	 *
	 * @param array &$errors Registry of errors
	 *
	 * @return string|false Hash for sharing, if generated and writed to ArangoDB
	 */
	public function share(array &$errors = []): string|false
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				// Initialized collection

				// Generating hash and writing to the cart implement instance
				$this->share = sodium_bin2hex(sodium_crypto_generichash($this->getId()));

				if (document::update($this->__document(), errors: $errors)) {
					// Writed to ArangoDB

					// Exit (success)
					return $this->share;
				} else throw new exception('Failed to write confirmed cart to ArangoDB');
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/*
	 * Unshare
	 *
	 * Deleting hash for sharing the cart
	 *
	 * @param array &$errors Registry of errors
	 *
	 * @return bool Is the cart unshared?
	 */
	public function unshare(array &$errors = []): bool
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				// Initialized collection

				// Deleting hash and writing to the cart implement instance
				$this->share = null;

				if (document::update($this->__document(), errors: $errors)) {
					// Writed to ArangoDB

					// Exit (success)
					return true;
				} else throw new exception('Failed to write confirmed cart to ArangoDB');
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/*
	 * Order
	 *
	 * 
	 *
	 * @param array &$errors Registry of errors
	 *
	 * @return void
	 */
	public function order(array &$errors = []): void
	{
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
					if (collection::initialize(order::COLLECTION, order::TYPE, errors: $errors)) {
						// Initialized collections

					} else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION);
				} else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
	}
}
