<?php

namespace Planaday\Database\SQLDatabaseControllers\DayPart;

use ArrayAccess;
use Countable;
use Planaday\Database\Abstraction\SQLQuery\ColumnTypeMap;
use Planaday\Database\Abstraction\SQLQuery\SQLFactory;
use Planaday\Database\SQLDatabaseControllers\Table;
use Planaday\Entities\DayPart\DTO\DayPart;
use Planaday\Platform\PlatformAbstraction\DatabaseInterface;
use Planaday\Utility\DataGrouping\ArrayFunctions;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\Map;
use Planaday\Utility\DependencyInjection\Container;
use Traversable;

class DayPartTable extends Table {
	public const DAY_PART_TABLE = 'day_part';

	public function __construct(
		protected Container $registry,
		protected DayPartInstructorLink $dayPartInstructorLink,
		protected DayPartLocationLink $dayPartLocationLink,
		DatabaseInterface $querySender,
		SQLFactory $sql,
		ColumnTypeMap $columnTypeMap
	) {
		$columns = [];

		$columns[DayPartColumnEnum::ID] = implode(" ", [
			$columnTypeMap->ID(),
			$columnTypeMap->NOT_NULL(),
			$columnTypeMap->PRIMARY_KEY()
		]);

		$columns[DayPartColumnEnum::COURSE_ID] = implode(" ", [
			$columnTypeMap->ID(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::NAME] = implode(" ", [
			$columnTypeMap->NAME(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::DESCRIPTION] = implode(" ", [
			$columnTypeMap->DESCRIPTION(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::STATUS]   =
		$columns[DayPartColumnEnum::VAT_CODE] = implode(" ", [
			$columnTypeMap->CODE(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::USERS_SCHEDULED]    =
		$columns[DayPartColumnEnum::USERS_AVAILABLE]    =
		$columns[DayPartColumnEnum::USERS_OPTIONS]      =
		$columns[DayPartColumnEnum::USERS_MIN]          =
		$columns[DayPartColumnEnum::USERS_MAX]          =
		$columns[DayPartColumnEnum::USERS_WAITING_LIST] = implode(" ", [
			$columnTypeMap->AVAILABILITY_COUNT(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::COST] = implode(" ", [
			$columnTypeMap->PRICE(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::VAT] = implode(" ", [
			$columnTypeMap->TAX(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::IS_E_LEARNING] =
		$columns[DayPartColumnEnum::IS_VISIBLE]    =
		$columns[DayPartColumnEnum::HAS_CODE_95]   = implode(" ", [
			$columnTypeMap->BOOLEAN(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::LABELS] = implode(" ", [
			$columnTypeMap->LABELS(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::START_TIME] =
		$columns[DayPartColumnEnum::END_TIME] = implode(" ", [
			$columnTypeMap->DATETIME(),
			$columnTypeMap->DEFAULT_NULL()
		]);

		$columns[DayPartColumnEnum::LAST_UPDATE] = $columnTypeMap->LAST_UPDATED_TIMESTAMP();

		parent::__construct( $querySender, $sql, self::DAY_PART_TABLE, $columns, [] );
	}

	public function setDayPart( DayPart $dayPart ): void {
		$databaseArray = DayPartDatabaseObject::getDatabaseArrayFromDayPart($dayPart);

		$table = $this->getSqlTable();
		$this->dayPartLocationLink->setLocationsForDayPart((string)$dayPart->getId(), Collection::fromArray($dayPart->getLocationIds()));
		$this->dayPartInstructorLink->setInstructorsForDayPart((string)$dayPart->getId(), Collection::fromArray($dayPart->getInstructors()));

		$query = $this->sql->insert($table, $databaseArray);
		$this->querySender->runPreparedQuery($query->toQuery(true), $databaseArray);
	}

	/**
	 * @param int $courseId
	 *
	 * @return ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable
	 */
	public function getDayPartsByCourseId( int $courseId ): ArrayAccess&Traversable&Countable {
		$query = "SELECT * FROM {pad-table} WHERE " . DayPartColumnEnum::COURSE_ID . "=%d ORDER BY " . DayPartColumnEnum::START_TIME;

		$results = $this->querySender->getQueryResults($this, $query, array($courseId));

		return $this->getDayPartsFromResult($results);
	}

	public function getDayPartById( int $id ): ?DayPart {
		$table = $this->getSqlTable();
		$query = $this->sql->query($table, $table);
		$query->setWhere($this->sql->compare($table->getColumn(DayPartColumnEnum::ID), $id, true));
		$query->setLimit(1);

		$result = $this->querySender->sendQuery($query->toQuery());

		if (!is_array($result) || count($result) == 0){
			return null;
		}

		return DayPartDatabaseObject::getDayPartObject(
			$result[0],
			[...$this->dayPartLocationLink->getLocationsForDayPart((string) $id)],
			[...$this->dayPartInstructorLink->getInstructorsForDayPart((string) $id)]
		);
	}

	/**
	 * @return ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable
	 */
	public function getAll(): ArrayAccess&Traversable&Countable {
		return $this->getDayPartsFromResult($this->getAllRaw());
	}


	/**
	 * @return ArrayAccess&Traversable&Countable
	 */
	public function getAllRaw():ArrayAccess&Traversable&Countable{
		$query = "SELECT * FROM {pad-table}";

		$results = $this->querySender->getQueryResults($this, $query);

		if (!is_array($results) || count($results) == 0){
			return Collection::fromArray([]);
		}

		return Map::fromArray($results);
	}

	/**
	 * @param mixed $results
	 *
	 * @return ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable
	 */
	public function getDayPartsFromResult(mixed $results): ArrayAccess&Traversable&Countable{
		if (!ArrayFunctions::is_arrayType($results) || count($results) == 0){
			return Collection::fromArray([]);
		}

		$returnArray = [];

		foreach ($results as $result){
			$returnArray[] = DayPartDatabaseObject::getDayPartObject(
				$result,
				[...$this->dayPartLocationLink->getLocationsForDayPart($result[DayPartColumnEnum::ID])],
				[...$this->dayPartInstructorLink->getInstructorsForDayPart($result[DayPartColumnEnum::ID])]
			);
		}

		return Collection::fromArray($returnArray);
	}

	/**
	 * @param ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable $dayParts
	 * @param bool $wipeDB
	 *
	 * @return void
	 */
	public function setAll(ArrayAccess&Traversable&Countable $dayParts, bool $wipeDB = false ): void {
		if ($wipeDB){
			$sql = "TRUNCATE TABLE " . $this->getSqlTable()->getFromQuery();
			$this->querySender->sendQuery($sql);

			$sql = "TRUNCATE TABLE " . $this->dayPartLocationLink->getSqlTable()->getFromQuery();
			$this->querySender->sendQuery($sql);

			$sql = "TRUNCATE TABLE " . $this->dayPartInstructorLink->getSqlTable()->getFromQuery();
			$this->querySender->sendQuery($sql);
		}

		foreach ($dayParts as $dayPart){
			$this->setDayPart($dayPart);
		}
	}

	public function getCountAll(): int {
		$table = $this->getSqlTable();
		$count = $this->sql->count($table->getColumn(DayPartColumnEnum::ID));
		$count->setAlias('Count');
		$query = $this->sql->query( $table, $count );

		$result = $this->querySender->sendQuery($query->toQuery());

		if (!is_array($result) || count($result) == 0){
			return 0;
		}

		return $result[0]['Count'] ?? 0;
	}
}