<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Command;

use Doctrine\ORM\AbstractQuery;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ServiceConstants;
use Newland\Toubiz\Api\Utility\ArrayUtility;
use Newland\Toubiz\Sync\Neos\Command\Task\CallsImportTask;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizationTask;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizeGuidesFromTportal;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizeOffersFromTportal;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizePackagesFromTportal;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ServiceRepository;
use Newland\Toubiz\Sync\Neos\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\NullOutput;
use Neos\Flow\Annotations as Flow;

class ServicesCommandController extends AbstractCommandController
{
    use CallsImportTask;

    /**
     * @var ServiceRepository
     * @Flow\Inject()
     */
    protected $serviceRepository;

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

    public function __construct()
    {
        parent::__construct();
        $this->tasks = [
            ServiceConstants::TYPE_OFFER => [ new SynchronizeOffersFromTportal() ],
            ServiceConstants::TYPE_PACKAGE => [ new SynchronizePackagesFromTportal() ],
            ServiceConstants::TYPE_GUIDE => [ new SynchronizeGuidesFromTportal() ],
        ];
    }

    public function synchronizeCommand(string $only = null, bool $quiet = false): void
    {
        if ($quiet) {
            $this->output->setOutput(new NullOutput());
        } else {
            $this->showProgressOnCommandLine();
        }

        foreach ($this->resolveTypes($only) as $type) {
            foreach ($this->tasks[$type] as $task) {
                $this->callSynchronizationTask($task, $this->output);
            }
        }

        $this->emitFinish();
    }


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

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


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

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

        $this->emitEnd(sprintf('Remove %d services', $count), compact('language'));
    }

    private function resolveTypes(?string $only): array
    {
        $types = $only ? ArrayUtility::trimExplode(',', $only) : array_keys($this->tasks);
        foreach ($types as $type) {
            if (!array_key_exists($type, $this->tasks)) {
                throw new InvalidArgumentException(sprintf(
                    'passed service type %s is invalid. Must be one of [%s]',
                    $type,
                    implode(', ', array_keys($this->tasks))
                ));
            }
        }
        return $types;
    }
}
