<?php

namespace Rapsys\AirBundle\Command;

use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Twig\Extra\Markdown\DefaultMarkdown;

use Rapsys\AirBundle\Entity\Session;

class CalendarCommand extends Command {
	//Set failure constant
	const FAILURE = 1;

	///Set success constant
	const SUCCESS = 0;

	///Config array
	protected $config;

	/**
	 * Doctrine instance
	 *
	 * @var ManagerRegistry
	 */
	protected $doctrine;

	///Locale
	protected $locale;

	///Translator instance
	protected $translator;

	/**
	 * Inject doctrine, container and translator interface
	 *
	 * @param ContainerInterface $container The container instance
	 * @param ManagerRegistry $doctrine The doctrine instance
	 * @param RouterInterface $router The router instance
	 * @param TranslatorInterface $translator The translator instance
	 */
    public function __construct(ContainerInterface $container, ManagerRegistry $doctrine, RouterInterface $router, TranslatorInterface $translator) {
		//Call parent constructor
		parent::__construct();

		//Retrieve config
		$this->config = $container->getParameter($this->getAlias());

		//Retrieve locale
		$this->locale = $container->getParameter('kernel.default_locale');

		//Store doctrine
        $this->doctrine = $doctrine;

		//Store router
        $this->router = $router;

		//Get router context
		$context = $this->router->getContext();

		//Set host
		$context->setHost('airlibre.eu');

		//Set scheme
		$context->setScheme('https');

		//Set the translator
		$this->translator = $translator;
	}

	///Configure attribute command
	protected function configure() {
		//Configure the class
		$this
			//Set name
			->setName('rapsysair:calendar')
			//Set description shown with bin/console list
			->setDescription('Synchronize sessions in calendar')
			//Set description shown with bin/console --help airlibre:attribute
			->setHelp('This command synchronize sessions in google calendar');
	}

	///Process the attribution
	protected function execute(InputInterface $input, OutputInterface $output) {
		//Compute period
		$period = new \DatePeriod(
			//Start from last week
			new \DateTime('-1 week'),
			//Iterate on each day
			new \DateInterval('P1D'),
			//End with next 2 week
			new \DateTime('+2 week')
		);

		//Retrieve events to update
		$sessions = $this->doctrine->getRepository(Session::class)->fetchAllByDatePeriod($period, $this->locale);

		//Markdown converted instance
		$markdown = new DefaultMarkdown;

		//Retrieve cache object
		//XXX: by default stored in /tmp/symfony-cache/@/W/3/6SEhFfeIW4UMDlAII+Dg
		//XXX: stored in %kernel.project_dir%/var/cache/airlibre/0/P/IA20X0K4dkMd9-+Ohp9Q
		$cache = new FilesystemAdapter($this->config['cache']['namespace'], $this->config['cache']['lifetime'], $this->config['cache']['directory']);

		//Retrieve calendars
		$cacheCalendars = $cache->getItem('calendars');

		//Without calendars
		if (!$cacheCalendars->isHit()) {
			//Return failure
			return self::FAILURE;
		}

		//Retrieve calendars
		$calendars = $cacheCalendars->get();

		//XXX: calendars content
		#var_export($calendars);

		//Check expired token
		foreach($calendars as $clientId => $client) {
			//Get google client
			$googleClient = new \Google\Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]);

			//Iterate on each tokens
			foreach($client['tokens'] as $tokenId => $token) {
				//Set token
				$googleClient->setAccessToken(
					[
						'access_token' => $tokenId,
						'refresh_token' => $token['refresh'],
						'expires_in' => $token['expire'],
						'scope' => $token['scope'],
						'token_type' => $token['type'],
						'created' => $token['created']
					]
				);

				//With expired token
				if ($exp = $googleClient->isAccessTokenExpired()) {
					//Refresh token
					if ($googleClient->getRefreshToken()) {
						//Retrieve refreshed token
						$googleToken = $googleClient->fetchAccessTokenWithRefreshToken($googleClient->getRefreshToken());

						//Add refreshed token
						$calendars[$clientId]['tokens'][$googleToken['access_token']] = [
							'calendar' => $token['calendar'],
							'prefix' => $token['prefix'],
							'refresh' => $googleToken['refresh_token'],
							'expire' => $googleToken['expires_in'],
							'scope' => $googleToken['scope'],
							'type' => $googleToken['token_type'],
							'created' => $googleToken['created']
						];

						//Remove old token
						unset($calendars[$clientId]['tokens'][$tokenId]);
					} else {
						//Drop token
						unset($calendars[$clientId]['tokens'][$tokenId]);

						//Without tokens
						if (empty($calendars[$clientId]['tokens'])) {
							//Drop client
							unset($calendars[$clientId]);
						}

						//Drop token and report
						echo 'Token '.$tokenId.' for calendar '.$token['calendar'].' has expired and is not refreshable'."\n";

						//Return failure
						//XXX: we want that mail and stop here
						return self::FAILURE;
					}
				}
			}
		}

		//Save calendars
		$cacheCalendars->set($calendars);

		//Save calendar
		$cache->save($cacheCalendars);

		//Iterate on each calendar client
		foreach($calendars as $clientId => $client) {
			//Get google client
			$googleClient = new \Google\Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]);

			//Iterate on each tokens
			foreach($client['tokens'] as $tokenId => $token) {
				//Set token
				$googleClient->setAccessToken(
					[
						'access_token' => $tokenId,
						'refresh_token' => $token['refresh'],
						'expires_in' => $token['expire'],
						'scope' => $token['scope'],
						'token_type' => $token['type'],
						'created' => $token['created']
					]
				);

				//With expired token
				if ($exp = $googleClient->isAccessTokenExpired()) {
					//Last chance to skip this run
					continue;
				}

				//Get google calendar
				$googleCalendar = new \Google\Service\Calendar($googleClient);

				//Retrieve calendar
				try {
					$calendar = $googleCalendar->calendars->get($token['calendar']);
				//Catch exception
				} catch(\Google\Service\Exception $e) {
					//Display exception
					//TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
					echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
					echo $e->getTraceAsString()."\n";

					//Return failure
					return self::FAILURE;
				}

				//Init events
				$events = [];

				//Set filters
				$filters = [
					//XXX: show even deleted event to be able to update them
					'showDeleted' => true,
					//TODO: fetch events one day before and one day after to avoid triggering double insert duplicate key 409 errors :=) on google
					'timeMin' => $period->getStartDate()->format(\DateTime::ISO8601),
					'timeMax' => $period->getEndDate()->format(\DateTime::ISO8601)
					/*, 'iCalUID' => 'airlibre/?????'*//*'orderBy' => 'startTime', */
				];

				//Retrieve event collection
				$googleEvents = $googleCalendar->events->listEvents($token['calendar'], $filters);

				//Iterate until reached end
				while (true) {
					//Iterate on each event
					foreach ($googleEvents->getItems() as $event) {
						//Store event by id
						if (preg_match('/^'.$token['prefix'].'([0-9]+)$/', $id = $event->getId(), $matches)) {
							$events[$matches[1]] = $event;
						//XXX: 3rd party events with id not matching prefix are skipped
						#} else {
						#	echo 'Skipping '.$event->getId().':'.$event->getSummary()."\n";*/
						}
					}

					//Get page token
					$pageToken = $googleEvents->getNextPageToken();

					//Handle next page
					if ($pageToken) {
						//Replace collection with next one
						$googleEvents = $service->events->listEvents($token['calendar'], $filters+['pageToken' => $pageToken]);
					} else {
						break;
					}
				}

				//Iterate on each session to sync
				foreach($sessions as $sessionId => $session) {
					//Init shared properties
					//TODO: validate for constraints here ??? https://developers.google.com/calendar/api/guides/extended-properties
					$shared = [
						'gps' => $session['l_latitude'].','.$session['l_longitude']
					];

					//Init source
					$source = [
						'title' => $this->translator->trans('Session %id% by %pseudonym%', ['%id%' => $sessionId, '%pseudonym%' => $session['au_pseudonym']]).' '.$this->translator->trans('at '.$session['l_title']),
						'url' => $this->router->generate('rapsys_air_session_view', ['id' => $sessionId], UrlGeneratorInterface::ABSOLUTE_URL)
					];

					//Init description
					#$description = '<dl><dt>Description</dt><dd>'.$markdown->convert(strip_tags(str_replace(["\r", "\n\n"], ['', "\n"], $session['p_description']))).'</dd></dl>';
					$description = '<dl><dt>Description</dt><dd>'.$markdown->convert(strip_tags($session['p_description'])).'</dd></dl>';

					//Add class when available
					if (!empty($session['p_class'])) {
						$shared['class'] = $session['p_class'];
						#$description .= '<dl><dt>Classe</dt><dd>'.$markdown->convert(strip_tags(str_replace(["\r", "\n\n"], ['', "\n"], $session['p_class']))).'</dd></dl>';
						$description .= '<dl><dt>Classe</dt><dd><p>'.$session['p_class'].'</p></dd></dl>';
					}

					//Add contact when available
					if (!empty($session['p_contact'])) {
						$shared['contact'] = $session['p_contact'];
						$description .= '<dl><dt>Contacter</dt><dd><p>'.$session['p_contact'].'</p></dd></dl>';
					}

					//Add donate when available
					if (!empty($session['p_donate'])) {
						$shared['donate'] = $session['p_donate'];
						$description .= '<dl><dt>Contribuer</dt><dd><p>'.$session['p_donate'].'</p></dd></dl>';
					}

					//Add link when available
					if (!empty($session['p_link'])) {
						$shared['link'] = $session['p_link'];
						$description .= '<dl><dt>Site</dt><dd><p>'.$session['p_link'].'</p></dd></dl>';
					}

					//Add profile when available
					if (!empty($session['p_profile'])) {
						$shared['profile'] = $session['p_profile'];
						$description .= '<dl><dt>Réseau social</dt><dd><p>'.$session['p_profile'].'</p></dd></dl>';
					}

					//Locked session
					if (!empty($session['locked']) && $events[$sessionId]) {
						//With events
						if (!empty($event = $events[$sessionId])) {
							try {
								//Delete the event
								$googleCalendar->events->delete($token['calendar'], $event->getId());
							//Catch exception
							} catch(\Google\Service\Exception $e) {
								//Display exception
								//TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
								echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
								echo $e->getTraceAsString()."\n";

								//Return failure
								return self::FAILURE;
							}
						}
					//Without event
					} elseif (empty($events[$sessionId])) {
						//Init event
						$event = new \Google\Service\Calendar\Event(
							[
								//TODO: replace 'airlibre' with $this->config['calendar']['prefix'] when possible with prefix validating [a-v0-9]{5,}
								//XXX: see https://developers.google.com/calendar/api/v3/reference/events/insert#id
								'id' => $token['prefix'].$sessionId,
								'summary' => $session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_short']),
								#'description' => $markdown->convert(strip_tags($session['p_description'])),
								'description' => $description,
								'status' => empty($session['a_canceled'])?'confirmed':'cancelled',
								'location' => implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]),
								'source' => $source,
								'extendedProperties' => [
									'shared' => $shared
								],
								//TODO: colorId ?
								//TODO: attendees[] ?
								'start' => [
									'dateTime' => $session['start']->format(\DateTime::ISO8601)
								],
								'end' => [
									'dateTime' => $session['stop']->format(\DateTime::ISO8601)
								]
							]
						);

						try {
							//Insert the event
							$googleCalendar->events->insert($token['calendar'], $event);
						//Catch exception
						} catch(\Google\Service\Exception $e) {
							//Display exception
							//TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
							echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
							echo $e->getTraceAsString()."\n";

							//Return failure
							return self::FAILURE;
						}
					// With event
					} else {
						//Set event
						$event = $events[$sessionId];

						//With updated event
						#if ($session['updated'] >= (new \DateTime($event->getUpdated()))) {
						{
							//Set summary
							$event->setSummary($session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_short']));

							//Set description
							$event->setDescription($description);

							//Set status
							$event->setStatus(empty($session['a_canceled'])?'confirmed':'cancelled');

							//Set location
							$event->setLocation(implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]));

							//Get source
							$eventSource = $event->getSource();

							//Update source title
							$eventSource->setTitle($source['title']);

							//Update source url
							$eventSource->setUrl($source['url']);

							//Set source
							#$event->setSource($source);

							//Get extended properties
							$extendedProperties = $event->getExtendedProperties();

							//Update shared
							$extendedProperties->setShared($shared);

							//TODO: colorId ?
							//TODO: attendees[] ?

							//Set start
							$start = $event->getStart();

							//Update start datetime
							$start->setDateTime($session['start']->format(\DateTime::ISO8601));

							//Set end
							$end = $event->getEnd();

							//Update stop datetime
							$end->setDateTime($session['stop']->format(\DateTime::ISO8601));

							try {
								//Insert the event
								$updatedEvent = $googleCalendar->events->update($token['calendar'], $event->getId(), $event);
							//Catch exception
							} catch(\Google\Service\Exception $e) {
								//Display exception
								//TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
								echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
								echo $e->getTraceAsString()."\n";

								//Return failure
								return self::FAILURE;
							}
						}

						//Drop from events array
						unset($events[$sessionId]);
					}
				}

				//Remaining events to drop
				foreach($events as $eventId => $event) {
					//Non canceled events
					if ($event->getStatus() == 'confirmed') {
						try {
							//Delete the event
							$googleCalendar->events->delete($token['calendar'], $event->getId());
						//Catch exception
						} catch(\Google\Service\Exception $e) {
							//Display exception
							//TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
							echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
							echo $e->getTraceAsString()."\n";

							//Return failure
							return self::FAILURE;
						}
					}
				}
			}
		}

		//Return success
		return self::SUCCESS;
	}

	/**
	 * Return the bundle alias
	 *
	 * {@inheritdoc}
	 */
	public function getAlias(): string {
		return 'rapsys_air';
	}
}