<?php
namespace Newland\Toubiz\Sync\Neos\Command;

/*
 * This file is part of the "toubiz-sync-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Doctrine\ORM\AbstractQuery;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Api\Constants\Language;
use Newland\Toubiz\Api\ObjectAdapter\EventAdapterInterface;
use Newland\Toubiz\Api\Service\ServiceFactory;
use Newland\Toubiz\Api\Service\Toubiz\Legacy\EventApiService;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventRepository;
use Newland\Toubiz\Sync\Neos\Importer\EventImporter;

/**
 * Events command controller.
 *
 * Provides commands to manipulate events data.
 *
 * @Flow\Scope("singleton")
 */
class EventsCommandController extends AbstractCommandController
{
    protected const TYPE_LEGACY_TOUBIZ = 'LegacyToubiz';
    public const TYPE_FLUSH = 'EVENTS';

    /**
     * @var EventRepository
     * @Flow\Inject
     */
    protected $eventRepository;

    /**
     * Synchronize command.
     *
     * Updates local events database from API data source.
     *
     * @todo delete persisted events not being in the API response
     * @param bool $quiet
     * @param int|null $limit
     * @return void
     */
    public function synchronizeCommand($quiet = false, int $limit = null)
    {
        if (!$quiet) {
            $this->showProgressOnCommandLine();
        }

        // There is currently only one API providing events.
        $configuration = $this->getConfigurationForService('Toubiz/Legacy/EventApi');
        if (!$configuration) {
            throw new \Exception('Toubiz/Legacy/EventApi service not configured!');
        }
        foreach ($configuration['languages'] as $language) {
            $this->synchronizeEventsFromLegacyToubiz($configuration, $language, $limit);
        }
        (new EventImporter())->postImport();

        $this->flushObjectPathMappings();
        $this->emitFinish();
    }

    public function synchronizeOneCommand(string $eventId, $quiet = false)
    {
        if (!$quiet) {
            $this->showProgressOnCommandLine();
        }

        // There is currently only one API providing events.
        $configuration = $this->getConfigurationForService('Toubiz/Legacy/EventApi');
        if (!$configuration) {
            throw new \Exception('Toubiz/Legacy/EventApi service not configured!');
        }
        $this->synchronizeOneFromLegacyToubiz($eventId, $configuration);

        $this->objectPathMappingService->flushMappings(Event::class);
    }

    /**
     * Removes events from the system according to the given clause.
     * If no WHERE clause is given then all events will be deleted.
     *
     * # Remove all events
     * $ php flow events:remove
     *
     * # Remove events according to WHERE clause
     * $ php flow events:remove --where='event.sourcename="REGIO Konstanz-Bodensee-Hegau e.V."'
     *
     * # Remove single event
     * $ php flow events:remove \
     *      --where="event.Persistence_Object_Identifier='a4625eb4-6e84-4834-969d-c9f1d447408b'"
     *
     *
     * @param string|null $where DQL WHERE clause selecting the event to delete.
     * @param string|null $language Language to delete events in. Deletes in all languages if not specified.
     */
    public function removeCommand(string $where = null, string $language = null)
    {
        if ($where) {
            $this->outputLine('Deleting events WHERE ' . $where);
        } else {
            $this->outputLine('Deleting all events');
        }

        $this->showProgressOnCommandLine();

        if ($language === null) {
            $this->eventRepository->withoutLanguageHandling(function () use ($where) {
                $this->remove($where, null);
            });
        } else {
            $this->remove($where, $language);
        }
    }

    public function remove(?string $where, ?string $language): void
    {
        $this->eventRepository->setLanguage($language);
        $query = $this->eventRepository->createQueryBuilder('event');
        if ($where) {
            $query->where($where);
        }
        $count = (clone $query)->select('COUNT(event) AS count')
                ->getQuery()
                ->execute([], AbstractQuery::HYDRATE_ARRAY)[0]['count'] ?? 0;
        $this->askForConfirmationAndAbortIfNoneGiven(
            sprintf('Do you really want to remove %d events (%s)?', $count, $language)
        );

        $this->emitStart(sprintf('Remove %d events', $count), compact('language'));

        $processed = 0;
        foreach ($query->getQuery()->iterate() as $events) {
            foreach ($events as $event) {
                $this->eventRepository->remove($event);
                $this->emitProgress(sprintf('Remove %d events', $count), ++$processed, compact('language'));
                if ($processed % 250 === 0) {
                    $this->persistenceManager->persistAll();
                    $this->persistenceManager->clearState();
                    $this->validatorResolver->reset();
                }
            }
        }
        $this->emitEnd(sprintf('Remove %d events', $count), compact('language'));
    }

    private function synchronizeEventsFromLegacyToubiz(array $configuration, string $language, int $limit = null): void
    {
        $this->emitStart(static::TYPE_LEGACY_TOUBIZ, compact('language'));

        /** @var EventApiService $toubizService */
        $toubizService = ServiceFactory::get('Toubiz/Legacy/EventApi', $configuration['baseUri'] ?? null);
        $toubizService->setClientName($configuration['client']);
        $toubizService->setApiKey($configuration['apiKey']);
        $toubizService->setLanguage($language);
        $toubizService->setLimit($limit);
        $toubizService->setDetailUriTemplates($configuration['detailUriTemplates'] ?? []);

        if (array_key_exists('organizer', $configuration)) {
            $toubizService->setOrganizerId($configuration['organizer']);
        }

        $processed = 0;
        $toubizService->fetchActiveEvents(
            $this->wrapImportClosure(
                function (EventAdapterInterface $adapter) use (&$processed, $language) {
                    $this->emitProgress(static::TYPE_LEGACY_TOUBIZ, ++$processed, compact('language'));
                    $importer = new EventImporter();
                    $importer->setLanguage($language);
                    $importer->import($adapter);
                }
            )
        );

        $this->emitEnd(static::TYPE_LEGACY_TOUBIZ, compact('language'));
    }

    private function synchronizeOneFromLegacyToubiz(string $eventId, array $configuration): void
    {
        $this->emitStart(static::TYPE_LEGACY_TOUBIZ);

        /** @var EventApiService $toubizService */
        $toubizService = ServiceFactory::get('Toubiz/Legacy/EventApi', $configuration['baseUri'] ?? null);
        $toubizService->setClientName($configuration['client']);
        $toubizService->setApiKey($configuration['apiKey']);

        if (array_key_exists('organizer', $configuration)) {
            $toubizService->setOrganizerId($configuration['organizer']);
        }

        $adapter = $toubizService->fetchEventDetails($eventId);
        if ($adapter) {
            (new EventImporter())->import($adapter);
            $this->emitProgress(static::TYPE_LEGACY_TOUBIZ, 1);
        }

        $this->emitEnd(static::TYPE_LEGACY_TOUBIZ);
    }

    private function flushObjectPathMappings(): void
    {
        $this->objectPathMappingService->flushMappings(Event::class);
        $this->emitFlush(static::TYPE_FLUSH);
    }
}
