<?php

namespace Planaday\Entities\DayPart;

use ArrayAccess;
use Countable;
use Planaday\Api\ApiException\InvalidResponseException;
use Planaday\Config\General\Api\ApiSettings;
use Planaday\Entities\Course\DTO\Course;
use Planaday\Entities\DayPart\DTO\DayPart;
use Planaday\Entities\DayPart\Exception\DayPartNotFoundException;
use Planaday\Entities\Location\LocationController;
use Planaday\Utility\DataGrouping\Collection;
use Planaday\Utility\DataGrouping\LazyCollection;
use Planaday\Utility\DataGrouping\LazyFetchModifiable;
use Planaday\Utility\DateAndTimeUtility;
use Planaday\Utility\Debug\Log\Logging;
use Traversable;

class DayPartController {
	use Logging;

	public function __construct(
		protected DayPartApi $api,
		protected DayPartDatabase $database,
		protected ApiSettings $apiSettings,
		protected DayPartCache $cache,
		protected LocationController $locationController
	) {}

	/**
	 * @param int $dayPartID
	 * @param bool $forceCall
	 * @param bool $allowCache
	 *
	 * @return DayPart
	 * @throws DayPartNotFoundException
	 * @throws InvalidResponseException
	 */
	public function getDayPart(int $dayPartID, bool $forceCall = false, bool $allowCache = true):DayPart {
		if ($allowCache && $this->cache->contains($dayPartID)) return $this->cache->get($dayPartID);

		if (!$forceCall){
			$dayPartDatabase = $this->database->getDayPartById($dayPartID);
			$maxAllowedAge = $this->apiSettings->getRefreshIntervalDayParts() * DateAndTimeUtility::SECONDS_IN_HOUR;

			if (isset($dayPartDatabase) && $dayPartDatabase->getAge() < $maxAllowedAge){
				$this->addLocationsToDayPart($dayPartDatabase);
				return $dayPartDatabase;
			}
		}

		$apiDayPart = $this->api->getDayPart($dayPartID);

		$this->addLocationsToDayPart($apiDayPart, $forceCall);
		$this->cache->set($apiDayPart);
		$this->database->setDayPart($apiDayPart);

		return $apiDayPart;
	}

	/**
	 * @param Course $course
	 * @param bool $forceCall
	 *
	 * @return ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable
	 * @throws InvalidResponseException
	 */
	public function getDayPartsCourse(Course $course, bool $forceCall = false):ArrayAccess&Traversable&Countable {
		if (!$forceCall){
			$dayPartsDatabase = $this->database->getDayPartsByCourseId($course->getId());
			$valid = $this->checkDayPartListValid($dayPartsDatabase, $course->getDayPartAmount());

			if ($valid){
				foreach ($dayPartsDatabase as $dayPart){
					$this->addLocationsToDayPart($dayPart);
				}
				return $dayPartsDatabase;
			}
		}

		$dayPartsApi = $this->api->getDayPartsCourse($course->getId());

		if ($dayPartsApi instanceof LazyFetchModifiable){
			$dayPartsApi->addFetchAction(function (DayPart $dayPart) use ($forceCall){
				$this->addLocationsToDayPart($dayPart, $forceCall);
				$this->database->setDayPart($dayPart);
				$this->cache->set($dayPart);
				return $dayPart;
			}, true);
		} else {
			foreach ($dayPartsApi as $dayPart){
				$this->addLocationsToDayPart($dayPart, $forceCall);
				$this->database->setDayPart($dayPart);
				$this->cache->set($dayPart);
			}
		}

		return $dayPartsApi;
	}

	/**
	 * @param ArrayAccess<int|string, DayPart>&Traversable<int|string, DayPart>&Countable $dayParts
	 * @param int $dayPartCount
	 *
	 * @return bool
	 */
	protected function checkDayPartListValid(ArrayAccess&Traversable&Countable $dayParts, int $dayPartCount): bool {
		if ($dayPartCount != count($dayParts)){
			return false;
		}

		$maxAllowedAge = $this->apiSettings->getRefreshIntervalDayParts() * DateAndTimeUtility::SECONDS_IN_HOUR;

		foreach ($dayParts as $dayPart){
			if ($dayPart->getAge() > $maxAllowedAge){
				return false;
			}
		}

		return true;
	}

	protected function addLocationsToDayPart(DayPart $dayPart, bool $forceCall = false): void {
		$dayPart->setLocations(
			new LazyCollection(
				function () use ($dayPart, $forceCall) {
					try {
						return $this->locationController->getLocations( Collection::fromArray($dayPart->getLocationIds()), $forceCall );
					} catch (InvalidResponseException $e) {
						$this->logError("Failed to fetch locations for day part with id {$dayPart->getId()}", $e);
						return Collection::fromArray([]);
					}
				}
			)
		);
	}
}