<?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\Api\Utility\TimeDelta;
use Newland\Toubiz\Sync\Neos\Command\Helper\ConfigurationHelper;
use Newland\Toubiz\Sync\Neos\Command\Task\CallsImportTask;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizeEventsFromLegacyToubiz;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizationTask;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventRepository;
use Newland\Toubiz\Sync\Neos\Importer\EventImporter;
use Symfony\Component\Console\Output\NullOutput;

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

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

    /**
     * @var ConfigurationHelper
     * @Flow\Inject()
     */
    protected $configurationHelper;

    /** @var SynchronizationTask[] */
    protected $tasks;

    public function __construct()
    {
        parent::__construct();
        $this->tasks = [ new SynchronizeEventsFromLegacyToubiz() ];
    }

    /**
     * Synchronize command.
     *
     * Updates local events database from API data source.
     *
     * @param bool $quiet
     * @param int $concurrency Number of concurrent requests to execute.
     * @param string $onlyModifiedInLast Only synchronize events that have been modified in the given timeframe.
     *     Examples for valid timeframes: '2 days', '4d', '17h'.
     * @return void
     */
    public function synchronizeCommand(
        $quiet = false,
        int $concurrency = 10,
        string $onlyModifiedInLast = null
    ): void {
        if (!$quiet) {
            $this->showProgressOnCommandLine();
        } else {
            $this->output->setOutput(new NullOutput());
        }
        $this->configurationHelper->setDefaults(
            [
                'delta' => $onlyModifiedInLast ? TimeDelta::create($onlyModifiedInLast) : null,
                'concurrency' => $concurrency,
            ]
        );

        /** @var string[] $ids */
        $ids = [];
        foreach ($this->tasks as $task) {
            $ids = array_merge($ids, $this->callSynchronizationTask($task, $this->output));
        }

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

    /**
     * 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): void
    {
        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'));
        $this->output->progressStart($count);

        $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 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->fetchEventsDetails([ $eventId ])->wait()[0];
        if ($adapter) {
            $importer = new EventImporter();
            $importer->setClient($configuration['client']);
            $importer->import($adapter);
            $this->emitProgress(static::TYPE_LEGACY_TOUBIZ, 1);
        }

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

    /**
     * @param string[]|null $ids
     */
    private function flushObjectPathMappings(array $ids = null): void
    {
        $this->objectPathMappingService->flushMappings(Event::class, $ids);
        $this->emitFlush(static::TYPE_FLUSH, $ids);
    }
}
