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

use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\AbstractQuery;
use Newland\Toubiz\Sync\Neos\Domain\Repository\AddressRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\CategoryRepository;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ExternalIdRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\MediumRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\SlopeRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\TransportationFacilityRepository;
use Symfony\Component\Console\Helper\ProgressBar;

/**
 * @Flow\Scope("singleton")
 */
class OrphansCommandController extends AbstractCommandController
{

    /**
     * @var ObjectManager
     * @Flow\Inject()
     */
    protected $entityManager;

    /**
     * @param bool $quiet
     */
    public function removeCommand($quiet = false)
    {
        /** @var OrphanFinder[] $repositories */
        $repositories = [
            'articles' => $this->objectManager->get(ArticleRepository::class),
            'categories' => $this->objectManager->get(CategoryRepository::class),
            'media elements' => $this->objectManager->get(MediumRepository::class),
            'addresses' => $this->objectManager->get(AddressRepository::class),
            'slopes' => $this->objectManager->get(SlopeRepository::class),
            'transportation facilities' => $this->objectManager->get(TransportationFacilityRepository::class),
            'externalids' => $this->objectManager->get(ExternalIdRepository::class),
        ];

        foreach ($repositories as $name => $repository) {
            $this->removeOrphans($repository, $name);
        }
    }

    private function removeOrphans(OrphanFinder $repository, string $name): void
    {
        if (method_exists($repository, 'setLanguage')) {
            $repository->setLanguage(null);
        }

        $count = $this->countOrphans($repository);
        $perPage = 500;

        $progress = new ProgressBar($this->output->getOutput());
        $progress->setMaxSteps($count);
        $progress->setFormat('%current%/%max% [%bar%] ' . $name);
        $progress->start();

        for ($offset = 0; $offset <= $count; $offset += $perPage) {
            foreach ($this->findOrphans($repository, $offset, $perPage) as $orphan) {
                $repository->remove($orphan);
                $progress->advance();
            }

            $this->persistAndClearInMemoryElements();
        }

        $this->persistAndClearInMemoryElements();
        $this->entityManager->flush();

        $progress->finish();
        $this->outputLine();
    }

    private function countOrphans(OrphanFinder $repository): int
    {
        $query = $repository->orphanQuery();
        $alias = $query->getAllAliases()[0];
        return (int) $repository->orphanQuery()
            ->select(sprintf('COUNT(%s.Persistence_Object_Identifier) AS count', $alias))
            ->getQuery()
            ->execute([], AbstractQuery::HYDRATE_ARRAY)[0]['count'];
    }

    private function findOrphans(OrphanFinder $repository, int $offset, int $perPage): iterable
    {
        return  $repository
            ->orphanQuery()
            ->setFirstResult($offset)
            ->setMaxResults($perPage)
            ->getQuery()
            ->execute();
    }

    private function persistAndClearInMemoryElements(): void
    {
        $this->persistenceManager->persistAll();
        $this->persistenceManager->clearState();
        $this->validatorResolver->reset();
    }
}
