<?php

namespace StevenBuehner\sbChurchtoolsGrouphomepage\Service;

use DateTimeZone;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\JsonLd\Context;
use StevenBuehner\ChurchTools\ApiException;
use StevenBuehner\ChurchTools\Model\GetCalendars200ResponseDataInner;
use StevenBuehner\sbChurchtoolsGrouphomepage\Always\MyContainer;
use StevenBuehner\sbChurchtoolsGrouphomepage\Helper\CacheHelper;
use StevenBuehner\sbChurchtoolsGrouphomepage\Helper\iCalHelper;
use StevenBuehner\sbChurchtoolsGrouphomepage\Service\Exceptions\InvalidArgumentException;
use StevenBuehner\sbChurchtoolsGrouphomepage\Service\Models\Event;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Symfony\Contracts\Cache\ItemInterface;
use DateTime;
use DateTimeInterface;

class CtCalendarService {

	/**
	 * @var array|bool
	 */
	protected mixed $calendarsCached = FALSE;

	public function __construct() {
	}

	/**
	 * @param $urlHashOrId
	 * @param bool $flushCache
	 * @return iCalHelper
	 * @throws InvalidArgumentException
	 */
	public function getICalHelperOrFail($urlHashOrId, bool $flushCache = FALSE): iCalHelper {

		$calendar        = $this->getCalendar($urlHashOrId, $flushCache);
		$calId           = $calendar->getId();
		$calSecurityHash = $calendar->getRandomUrl();

		// URL-Generation
		$host    = $this->getChurchToolsHost();
		$icalUrl = "{$host}/?q=churchcal/ical&security={$calSecurityHash}&id={$calId}";

		$cacheHelper = CacheHelper::getInstance();
		$cacheKey    = md5($icalUrl);

		if ($flushCache === TRUE) {
			try {
				$cacheHelper->delete($cacheKey);
			} catch (\StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Psr\Cache\InvalidArgumentException $e) {
			}
		}

		try {
			$iCalHelper = $cacheHelper->get($cacheKey, function (ItemInterface $item) use ($icalUrl) {
				$item->expiresAfter(60 * 5);
				return new iCalHelper($icalUrl);
			});
		} catch (\StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Psr\Cache\InvalidArgumentException $e) {
		}

		return $iCalHelper;

	}

	/**
	 * @param $urlHashOrId
	 * @param bool $flushCache
	 * @return ?GetCalendars200ResponseDataInner
	 * @throws InvalidArgumentException
	 */
	public function getCalendar($urlHashOrId, bool $flushCache = FALSE): ?GetCalendars200ResponseDataInner {

		$cacheHelper = CacheHelper::getInstance();
		$cacheKey    = md5('getCalendar_' . $urlHashOrId);

		if ($flushCache === TRUE) {
			try {
				$cacheHelper->delete($cacheKey);
			} catch (\StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Psr\Cache\InvalidArgumentException) {
			}
		}

		try {
			$calendar = $cacheHelper->get($cacheKey, function (ItemInterface $item) use ($urlHashOrId) {

				$item->expiresAfter(60 * 5);

				/** @var GetCalendars200ResponseDataInner[] $calendars */
				$calendars     = $this->getChurchToolsCalendarsCached();
				$foundCalendar = NULL;

				if (is_numeric($urlHashOrId) && ((int)$urlHashOrId) == $urlHashOrId) {

					// Parse Input
					$urlHashOrId = (int)$urlHashOrId;

					// Check if the CalendarId is a valid ChurchTools Calendar with the user has access to
					foreach ($calendars as $cal) {
						if ($cal->getId() === $urlHashOrId) {
							$foundCalendar = $cal;
							break;
						}
					}

				} else if (iCalHelper::isValidUrl($urlHashOrId)) {

					// Check whether the $urlOrId is a valid ChurchTools-Public ics URL the user has access to
					// $icalUrl = "{$host}/?q=churchcal/ical&security={$security}&id={$calId}";
					if (preg_match('~security=(?<secret>[0-9A-Za-z]+)&~', $urlHashOrId, $match) !== 1) {
						throw new InvalidArgumentException('Invalid Calendar-Url - Security Error');
					}

					foreach ($calendars as $cal) {
						if ($cal->getRandomUrl() === $match['secret']) {
							$foundCalendar = $cal;
							break;
						}
					}

				} else {

					// Check wether the string MIGHT BE valid hash
					if (preg_match('~^(?<secret>[0-9A-Za-u]+)$~', $urlHashOrId, $match) !== 1) {
						throw new InvalidArgumentException('Invalid Calendar-Hash - Security Error');
					}

					foreach ($calendars as $cal) {
						if ($cal->getRandomUrl() === $match['secret']) {
							$foundCalendar = $cal;
							break;
						}
					}

				}

				return $foundCalendar;

			});
		} catch (\StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Psr\Cache\InvalidArgumentException) {
		}


		if (is_null($calendar)) {
			throw new InvalidArgumentException('Unknown or Invalid Calendar-Id or Hash - Security Error');
		}

		return $calendar;

	}

	/**
	 * @throws \StevenBuehner\ChurchTools\ApiException
	 */
	public function getChurchToolsCalendarsCached() {

		if ($this->calendarsCached === FALSE) {
			$cacheAdapter = MyContainer::getCacheAdapter();

			$this->calendarsCached = $cacheAdapter->get('churchtools_api_calendars', function (ItemInterface $item) {

				$item->expiresAfter(60 * 60 * 24); // 24h

				MyContainer::churchToolsLogin();
				$calApi = MyContainer::getCalendarApi();
				return $calApi->getCalendars()->getData();

			});

		}

		return $this->calendarsCached;
	}

	/**
	 * @param string|int $urlHashOrId
	 * @param int $maxEvents
	 * @param string $titleRegExp
	 * @param string $descriptionRegExp
	 * @param DateTime|null $maxDate
	 * @param bool $clearCache
	 * @return Event[]
	 * @throws InvalidArgumentException
	 */
	public function getFilteredEventsFromCalendar($urlHashOrId, int $maxEvents = 5, string $titleRegExp = '', string $descriptionRegExp = '', ?DateTime $maxDate = NULL, bool $clearCache = FALSE) {

		// Parse Input
		$maxEvents = max(1, min(50, $maxEvents));
		$iCal      = $this->getICalHelperOrFail($urlHashOrId, $clearCache);
		$calendar  = $this->getCalendar($urlHashOrId);

		if (!iCalHelper::isValidRegExp($titleRegExp)) {
			throw new InvalidArgumentException('Invalid RegExp vor Title!');
		}

		if (!iCalHelper::isValidRegExp($descriptionRegExp)) {
			throw new InvalidArgumentException('Invalid RegExp vor Description!');
		}


		// Retrieve Events from ChurchTools
		$events        = $iCal->getFilteredEvents($titleRegExp, $descriptionRegExp, $maxDate, $maxEvents);
		$responsEvents = [];

		for ($i = 0; $i < count($events); $i++) {
			$eventEntity = $this->transformToEvent($events[$i], $iCal);
			$eventEntity->setColor($calendar->getColor());
			$responsEvents[] = $eventEntity;
		}

		return $responsEvents;

	}

	/**
	 * Wrapper Function um mehrere Kalender auf einmal abzufragen, die events zu sortieren und die maximale Anzahl Events zurückzugeben
	 * @param array $urlHashOrId
	 * @param int $maxEvents
	 * @param string $titleRegExp
	 * @param string $descriptionRegExp
	 * @param DateTime|null $maxDate
	 * @param bool $clearCache
	 * @return array|Event[]
	 */
	public function getFilteredEventsFromMultipleCalendars(array $urlHashOrId, int $maxEvents = 5, string $titleRegExp = '', string $descriptionRegExp = '', ?DateTime $maxDate = NULL, bool $clearCache = FALSE) {

		$events = [];

		try {
			foreach ($urlHashOrId as $id) {
				$events = array_merge($events, $this->getFilteredEventsFromCalendar($id, $maxEvents, $titleRegExp, $descriptionRegExp, $maxDate, $clearCache));
			}

			// Order by Start-Date
			usort($events, function (Event $a, Event $b) {
				return $a->getStart() <=> $b->getStart();
			});

			// Limit by $maxCount
			if ($maxEvents > 0 && $maxEvents < count($events)) {
				$events = array_slice($events, 0, $maxEvents);
			}

		} catch (InvalidArgumentException $e) {
			// throw $e;
		}


		return $events;

	}

	/**
	 * @param $e
	 * @param iCalHelper $iCal
	 * @return Event
	 */
	protected function transformToEvent(\StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\ICal\Event $e, $iCal): Event {

		$resEvent = new Event();
		$resEvent->setStart($iCal->getStartDateTime($e));
		$resEvent->setEnd($iCal->getEndDateTime($e));
		$resEvent->setTitle("$e->summary");
		$resEvent->setDescription("$e->description");
		$resEvent->setLocation("$e->location");
		$resEvent->setUrl(iCalHelper::getUrl($e));
		$resEvent->setAllDay(FALSE);
		$resEvent->setCalendarName($iCal->getCalendarName());
		$resEvent->setImageUrl(iCalHelper::getImageUrl($e));

		// Wenn kein Enddatum angegeben ist, handelt es sich um ein ganztägiges Ereignis => Gleiche das Enddatum an
		if ($resEvent->getEnd() === NULL && $resEvent->getStart() instanceof DateTime) {
			$newEnd = clone $resEvent->getStart();
			// $newEnd->setTime(24, 0, 0);
			$resEvent->setEnd($newEnd);

			$resEvent->setAllDay(TRUE);
		} else if (strlen($e->dtstart) === 8 || strlen($e->dtend) === 8) {
			// Nur das Datum wurde in iCal mitgeliefert => Es handelt sich also um ein AllDay Event
			$resEvent->setAllDay(TRUE);
		}

		return $resEvent;

	}


	protected function getChurchToolsHost(): string {
		$host = MyContainer::getChurchToolsConfig()->getHost();
		$host = preg_replace('~\/api\/?$~', '', $host); // Entferne das /api
		return $host;
	}


	public function transformEventToEventArray(Event $e): array {

		$location = trim($e->getLocation());

		if (empty($location)) {
		} else if ($this->isValidUrl($location)) {
			// Url als location erkannt

			$location = [
				'@type' => 'VirtualLocation',
				'url'   => $location
			];

		} else if (preg_match('~^\s*((?<name>[^,]+),)?\s*(?<street>[^,]+),\s*(?<plz>[0-9]{5})\s+(?<ort>[^,]+)$~', $location, $match) === 1) {
			// Deutsche Adresse erkannt
			// z.B. "SV Schönaich, Seestr. 8, 71101 Schönaich"
			// oder "Seestr. 8, 71101 Schönaich"

			$location = [
				'@type'   => 'Place',
				'address' => [
					'@type'           => 'PostalAddress',
					'streetAddress'   => $match['street'],
					'postalCode'      => $match['plz'],
					'addressLocality' => $match['ort'],
					'addressCountry'  => 'DE',
				]
			];

			if (isset($match['name']) && !empty($match['name'])) {
				$location['name'] = $match['name'];
			}

		} else {
			// Backup Location Ausgabe -> aufgrund der Bibliothek fehleraft :/

			$location = [
				'@type'   => 'Place',
				'address' => $location,
			];
		}

		return [
			'name'        => $e->getTitle(),
			'startDate'   => $e->getStart()->format(DateTimeInterface::ATOM),
			'endDate'     => $e->getEnd()->format(DateTimeInterface::ATOM),
			'eventStatus' => "https://schema.org/EventScheduled",
			'description' => $e->getDescription(),
			'location'    => $location,
		];
	}

	public function transformEventToJsonoLd(Event $e, $additionalData = []) {

		// return '<script type="application/ld+json>';

		// https://packagist.org/packages/torann/json-ld
		$data = $this->transformEventToEventArray($e);

		// Parse Organizer
		if (isset($additionalData['organizer'])) {
			$organizer = ['@type' => 'Organization'];

			if (is_array($additionalData['organizer'])) {

				if (isset($additionalData['organizer']['name'])) {
					$organizer['name'] = $additionalData['organizer']['name'];
				}

				if (isset($additionalData['organizer']['url'])) {
					$organizer['url'] = $additionalData['organizer']['url'];
				} else if (!empty($e->getUrl()) && $this->isValidUrl($e->getUrl())) {
					$organizer['url'] = $e->getUrl();
				}

			} else {
				$organizer['name'] = $additionalData['organizer'];

				if (!empty($e->getUrl()) && $this->isValidUrl($e->getUrl())) {
					$organizer['url'] = $e->getUrl();
				}
			}

			$additionalData['organizer'] = $organizer;
		}

		// EventStatus
		if (isset($additionalData['eventStatus'])) {
			switch ($additionalData['eventStatus']) {
				case "EventCancelled":
				case "EventMovedOnline":
				case "EventPostponed":
				case "EventRescheduled":
				case "EventScheduled":
					$additionalData['eventStatus'] = 'https://schema.org/' . $additionalData['eventStatus'];
					break;
				default:
			}
		}

		return array_merge(Context::create('event', $data)->getProperties(), $additionalData);

	}

	protected function isValidUrl($testUrl): bool {
		$resp = preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $testUrl);

		return $resp !== FALSE && $resp > 0;

	}
}
