<?php
/**
 * Common data storage for Deployer.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Utility;

use Dakwamine\Component\ComponentBasedObject;
use Exception;

/**
 * Common data storage for Deployer.
 *
 * This class is used to store common data that is used by the plugin
 * and that should not be stored in the options table.
 */
class CommonDataStorage extends ComponentBasedObject {

	const TABLE_NAME = 'dploy_common_data';

	/**
	 * Loads the item(s) with the given ID.
	 *
	 * @param string              $data_id The ID of the item to load.
	 * @param string|null         $tag The optional tag.
	 * @param array<string,mixed> $options Options:
	 *     - "limit" (int): Limit to the results.
	 *
	 * @return array<array<string,mixed>> The stored results:
	 *     - "id" (int): The auto-incremented ID.
	 *     - "data_id" (string): The data_id.
	 *     - "data" (string): The real payload data.
	 *     - "date_created" (string: The date when this item has been created.
	 *     - "tag" (string): Data tag, for grouping, namespacing, etc.
	 */
	public function load( string $data_id, ?string $tag = null, ?array $options = null ): array {
		if ( empty( $data_id ) ) {
			return array();
		}

		// Use dbDelta to create the table if it doesn't exist.
		$database = Database::getDatabaseHandle();

		$sql       = 'SELECT * FROM %i WHERE `data_id` = %s';
		$arguments = array(
			Database::prependDatabasePrefix( static::TABLE_NAME ),
			$data_id,
		);

		if ( ! empty( $tag ) ) {
			$sql .= ' AND `tag` = %s';

			$arguments[] = $tag;
		}

		$sql .= ' ORDER BY `date_created` ASC';

		if ( is_int( $options['limit'] ?? null ) ) {
			$sql .= ' LIMIT %d';

			$arguments[] = $options['limit'];
		}

		$results = $database->get_results( $database->prepare( $sql, $arguments ), ARRAY_A );

		return empty( $results ) ? array() : $results;
	}

	/**
	 * Loads a single item value.
	 *
	 * If multiple values with the same ID, this will return the first found.
	 *
	 * @param string      $data_id The data ID.
	 * @param string|null $tag The optional tag.
	 *
	 * @return string|null The value. Null value if not found.
	 */
	public function loadSingleValue( string $data_id, ?string $tag = null ): ?string {
		$results = $this->load( $data_id, $tag, array( 'limit' => 1 ) );

		return reset( $results )['data'] ?? null;
	}

	/**
	 * Prepares the table.
	 */
	public function prepareTable(): void {
		// Use dbDelta to create the table if it doesn't exist.
		$database = Database::getDatabaseHandle();

		Database::doDbDelta(
			$database->prepare(
				<<<'SQL'
CREATE TABLE %i (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
	`data_id` varchar(128) NOT NULL,
	`data` longtext NOT NULL,
	`date_created` datetime(6),
	`tag` varchar(128),
	PRIMARY KEY (`id`),
	KEY `data_id` (`data_id`)
)
SQL,
				Database::prependDatabasePrefix( static::TABLE_NAME )
			)
		);
	}

	/**
	 * Removes the table.
	 */
	public function removeTable(): void {
		$database = Database::getDatabaseHandle();

		$database->query(
			$database->prepare(
				'DROP TABLE IF EXISTS %i;',
				Database::prependDatabasePrefix( static::TABLE_NAME )
			)
		);
	}

	/**
	 * Stores the data.
	 *
	 * @param string    $data_id The data ID.
	 * @param string    $data The data to store.
	 * @param string    $tag Optional. The tag to associate with the data.
	 * @param bool|null $replace Set to true to replace instead of adding. False if unset.
	 *     Will replace the first occurrence found of rows identified by data ID.
	 *
	 * @return int The inserted or updated ID.
	 *
	 * @throws Exception Failed to generate date.
	 */
	public function store( string $data_id, string $data, string $tag = '', ?bool $replace = null ): int {
		$database = Database::getDatabaseHandle();

		$replace = $replace ?? false;

		if ( $replace ) {
			// Check that it exists.
			$maybe_existing_id = $database->get_var(
				$database->prepare( 'SELECT `id` FROM %i WHERE `data_id` = %s;', Database::prependDatabasePrefix( static::TABLE_NAME ), $data_id )
			);

			if ( ! empty( $maybe_existing_id ) ) {
				$prepared = $database->prepare(
					<<<'SQL'
					UPDATE %i SET `data` = %s, `tag` = %s WHERE `data_id` = %s AND `id` = %s;
					SQL,
					Database::prependDatabasePrefix( static::TABLE_NAME ),
					$data,
					$tag,
					$data_id,
					$maybe_existing_id
				);

				$database->query( $prepared );
				return $maybe_existing_id;
			}
		}

		// Insert.
		$prepared = $database->prepare(
			<<<'SQL'
INSERT INTO %i (`data_id`, `data`, `date_created`, `tag`) VALUES (%s, %s, %s, %s);
SQL,
			Database::prependDatabasePrefix( static::TABLE_NAME ),
			$data_id,
			$data,
			Date::getCurrentDateTime()->format( Date::DB_DATETIME_FORMAT_MILLISECONDS ),
			$tag
		);

		$database->query( $prepared );

		return $database->insert_id;
	}
}
