<?php
namespace Newland\Toubiz\Sync\Neos\Domain\Repository;

/*
 * 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 Doctrine\ORM\QueryBuilder;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Utility\Now;
use Newland\Toubiz\Api\Constants\EventType;
use Newland\Toubiz\Sync\Neos\Command\Task\SynchronizationResult;
use Newland\Toubiz\Sync\Neos\Domain\Filter\EventFilter;
use Newland\Toubiz\Sync\Neos\Domain\Filter\FilterInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
use Newland\Toubiz\Sync\Neos\Translation\TranslatableRepository;

/**
 * Event repository.
 *
 * @Flow\Scope("singleton")
 *
 * @method Event|null findOneByOriginalId(string $originalId)
 * @method Event|null findByIdentifier(string $identifier)
 * @method Event|null findOneBy(array $criteria, array $orderBy = null)
 */
class EventRepository extends AbstractRepository
{
    use TranslatableRepository {
        createQueryBuilder as private createTranslatableQueryBuilder;
    }

    /** @var string */
    protected $alias = 'event';

    public function createQueryBuilder($alias, $indexBy = null, bool $withHidden = false): QueryBuilder
    {
        $query = $this->createTranslatableQueryBuilder($alias, $indexBy);

        if (!$withHidden) {
            $query->addFixedExpression(
                $query->expr()->eq(sprintf('%s.hidden', $alias), 'false')
            );
        }

        return $query;
    }

    /**
     * Specific method for fetching all data for a data source
     * (e.g. multi-select in a neos node type property) in the
     * most performant way.
     *
     * @param \DateTimeInterface|null $now
     * @return array
     */
    public function findAllForDataSource(\DateTimeInterface $now = null): array
    {
        $now = $now ?? new Now();
        $query = $this->createQueryBuilder('event');

        $query->select(
            [
                'event.title AS event_title',
                'event.Persistence_Object_Identifier AS event_identifier',
                'event.originalId AS event_originalId',
                'event.beginsAt AS event_start',
                'event.endsAt AS event_end',
            ]
        )->join('event.eventDates', 'eventDate');

        $query->where(
            $query->expr()->orX(
                $query->expr()->gte('DATE(eventDate.beginsAt)', ':date'),
                $query->expr()->gte('DATE(eventDate.endsAt)', ':date'),
                $query->expr()->isNull('eventDate.beginsAt')
            )
        )
            ->setParameters(
                [
                    'date' => $now,
                ]
            )
            ->addOrderBy('eventDate.beginsAt', 'asc')
            ->groupBy('event_identifier');

        $events = $query->getQuery()->getScalarResult();
        $data = [];
        foreach ($events as $event) {
            $data[] = [
                'label' => $event['event_title'],
                'value' => $event['event_identifier'],
                'group' => $this->getDateRange($event['event_start'], $event['event_end']),
            ];
        }

        return $data;
    }

    /**
     * @param EventFilter $filter
     * @param QueryBuilder $query
     * @return QueryBuilder
     */
    protected function applyFilter(FilterInterface $filter, QueryBuilder $query): QueryBuilder
    {
        $this->applyBasicFilter($filter, $query);
        $query->innerJoin('event.eventDates', 'eventDate');

        $fromDate = $filter->getFromDate();
        $toDate = $filter->getToDate();

        $dateConstraints = [];
        if ($fromDate !== null) {
            $dateConstraints[] = $query->expr()->gte('DATE(eventDate.beginsAt)', ':fromDate');
            $query->setParameter('fromDate', $fromDate->format('Y-m-d'));
        }

        if ($toDate !== null) {
            $dateConstraints[] = $query->expr()->lte('DATE(eventDate.endsAt)', ':toDate');
            $query->setParameter('toDate', $toDate->format('Y-m-d'));
        }

        if (count($dateConstraints)) {
            if ($filter->getIncludeOnDemand()) {
                $query->andWhere(
                    $query->expr()->orX(
                        $query->expr()->andX(...$dateConstraints),
                        $query->expr()->isNull('eventDate.beginsAt')
                    )
                );
            } else {
                $query->andWhere(
                    $query->expr()->andX(...$dateConstraints)
                );
            }
        }

        $category = $filter->getCategory();
        if ($category !== null) {
            $query->innerJoin('event.categories', 'category')
                ->andWhere('category = :category')
                ->setParameter('category', $category);
        }

        $highlight = $filter->getHighlight();
        if ($highlight !== null) {
            $query->andWhere('event.isHighlight = :highlight')
                ->setParameter('highlight', $highlight);
        }

        return $query;
    }

    protected function queryBuilderForOriginalIdQuery(string $alias): QueryBuilder
    {
        return $this->createQueryBuilder($alias, null, true);
    }


    public function findUuidsToDeleteBasedOnSynchronizationResult(
        SynchronizationResult $result,
        string $client
    ): array {
        $query = $this->createQueryBuilder('event');
        $query
            ->select([ 'event.Persistence_Object_Identifier', 'event.originalId' ])
            ->where($query->expr()->eq('event.client', ':client'))
            ->setParameters([ 'client' => $client ]);

        $iterator = $query->getQuery()->iterate(null, AbstractQuery::HYDRATE_ARRAY);

        $originalIdsToRetain = (array) $result->getOriginalIdsToRetain();
        $originalIdsToDelete = (array) $result->getOriginalIdsToDelete();

        $uuidsToDelete = [];
        foreach ($iterator as $chunk) {
            foreach ($chunk as $item) {
                $shouldBeDeleted = \in_array($item['originalId'], $originalIdsToDelete, false);
                if (!$shouldBeDeleted && \count($originalIdsToRetain) > 0) {
                    $shouldBeDeleted = !\in_array($item['originalId'], $originalIdsToRetain, false);
                }

                if ($shouldBeDeleted) {
                    $uuidsToDelete[] = $item['Persistence_Object_Identifier'];
                }
            }
        }

        return $uuidsToDelete;
    }

    public function removeBasedOnSynchronizationResult(
        SynchronizationResult $result,
        string $client,
        string $language,
        callable $onProgress
    ): void {
        $this->withLanguage(
            $language,
            function () use ($result, $client, $onProgress) {
                $uuidsToDelete = $this->findUuidsToDeleteBasedOnSynchronizationResult($result, $client);

                $total = \count($uuidsToDelete);
                $deleted = 0;
                $onProgress(0, $total);

                $this->removeByIds(
                    $uuidsToDelete,
                    function () use ($onProgress, $total, &$deleted) {
                        $onProgress(++$deleted, $total);
                    }
                );
            }
        );
    }

    protected function getDateRange(?string $eventStart, ?string $eventEnd): string
    {
        if ($eventStart === null || $eventEnd === null) {
            return '';
        }

        $dateFrom = new \DateTime($eventStart);
        $dateTo = new \DateTime($eventEnd);

        return sprintf('%s - %s', $dateFrom->format('Y-m-d'), $dateTo->format('Y-m-d'));
    }
}
