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

/*
 * 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\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Api\ObjectAdapter\Article\ArticleWithLocationDataInterface;
use Newland\Toubiz\Api\ObjectAdapter\CategoryAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\EventAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\EventDateAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\MediumAdapterInterface;
use Newland\Toubiz\Api\Service\Tportal\ObjectAdapter\MediumAdapter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
use Newland\Toubiz\Sync\Neos\Domain\Model\EventDate;
use Newland\Toubiz\Sync\Neos\Domain\Model\Medium;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventDateRepository;

/**
 * Event importer.
 *
 * @Flow\Scope("singleton")
 */
class EventImporter extends AbstractImporter
{
    use ClientAware;

    /**
     * @var EventAdapterInterface
     */
    protected $data;

    /**
     * @var Event
     */
    protected $event;

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

    /**
     * @Flow\Inject()
     * @var EventDateRepository
     */
    protected $eventDateRepository;

    /**
     * @Flow\Inject()
     * @var ArticleRepository
     */
    protected $articleRepository;

    /**
     * @Flow\InjectConfiguration(package="Newland.Toubiz.Events.Neos", path="expireDays")
     * @var int
     */
    protected $expireDays;

    public function postImport(): void
    {
        $endsAt = new \DateTime();
        $endsAt->setTime(0, 0, 0);

        if ($this->expireDays === null) {
            $this->expireDays = 30;
        }

        $endsAt->modify('-' . $this->expireDays . ' days');

        $this->eventDateRepository->withLanguage(
            $this->language,
            function () use ($endsAt) {
                $this->eventDateRepository->deletePastRecords($endsAt);
            }
        );
    }

    /**
     * @param EventAdapterInterface $data
     * @return Event
     */
    public function import($data): Event
    {
        $this->data = $data;

        $persisted = $this->fetchEventOrCreateNew();

        $this->mapSimpleValues();

        if ($persisted) {
            $this->eventRepository->update($this->event);
        } else {
            $this->eventRepository->add($this->event);
        }

        $this->mapOrganizer();
        $this->mapLocation();
        $this->mapCategories();
        $this->mapEventDates();
        $this->mapEventTags();
        $this->mapMedia();
        if ($data instanceof ArticleWithLocationDataInterface) {
            $this->mapLocationData($data);
        }

        $this->eventRepository->update($this->event);

        $this->persistenceManager->persistAll();

        return $this->event;
    }

    protected function mapSimpleValues(): void
    {
        $this->event->setOriginalId($this->data->getExternalId());

        $this->event->setClient($this->client);
        $this->event->setLanguage($this->data->getLanguage());

        $this->event->setTitle($this->data->getName());
        $this->event->setDescription($this->data->getDescription());
        $this->event->setAdditionalInformation($this->data->getAdditionalInformation());
        $this->event->setAdmission($this->data->getAdmission());
        $this->event->setTicketContact($this->data->getTicketContact());
        $this->event->setIsHighlight($this->data->getIsHighlight());
        $this->event->setBeginsAt($this->data->getBeginsAt());
        $this->event->setEndsAt($this->data->getEndsAt());
        $this->event->setUpdatedAt($this->data->getUpdatedAt());
        $this->event->setTicketUri($this->data->getTicketUri() ?? '');
        $this->event->setLink($this->data->getLink() ?? '');
        $this->event->setSourceName($this->data->getSourceName());
        $this->event->setAttributes($this->data->getAttributes());
        $this->event->setScope($this->data->getScope());
        $this->event->setUrlIdentifier($this->generateUrlIdentifierFromData());
    }

    protected function mapCategories(): void
    {
        $this->updateCollection($this->event->getCategories(), $this->data->getCategories(), function ($data) {
            $importer = new CategoryImporter();
            $importer->setLanguage($this->data->getLanguage());
            return $importer->import($data);
        });
    }

    public function mapEventDates(): void
    {
        $existing = [];
        foreach ($this->event->getEventDates() as $date) {
            $existing[$date->getOriginalId()] = $date;
        }

        $this->updateCollection(
            $this->event->getEventDates(),
            $this->data->getEventDates(),
            function (EventDateAdapterInterface $data) use (&$existing) {
                $importer = new EventDateImporter();
                $importer->setLanguage($this->data->getLanguage());
                $importer->setEvent($this->event);
                return $importer->import($data, $existing[$data->getExternalId()] ?? null);
            }
        );
    }

    public function mapEventTags(): void
    {
        $this->updateCollection($this->event->getEventTags(), $this->data->getEventTags(), function ($data) {
            $importer = new EventTagImporter;
            $importer->setLanguage($this->data->getLanguage());
            return $importer->import($data);
        });
    }

    public function mapMedia(): void
    {
        $existing = [];
        foreach ($this->event->getMedia() as $media) {
            $existing[$media->getOriginalId()] = $media;
        }

        $this->updateCollection(
            $this->event->getMedia(),
            $this->data->getMedia(),
            function (MediumAdapterInterface $data) use (&$existing) {
                $importer = new MediumImporter;
                $importer->setEvent($this->event);
                return $importer->import($data, $existing[$data->getExternalId()] ?? null);
            }
        );
    }

    protected function mapOrganizer(): void
    {
        $organizerItem = $this->data->getOrganizer();
        $organizer = null;
        if ($organizerItem) {
            $addressImporter = new AddressImporter();
            $addressImporter->setLanguage($this->language);
            $organizer = $addressImporter->import($organizerItem);
        }
        $this->event->setOrganizer($organizer);
    }

    protected function mapLocation(): void
    {
        $locationItem = $this->data->getLocation();
        $location = null;
        if ($locationItem) {
            $addressImporter = new AddressImporter();
            $addressImporter->setLanguage($this->language);
            $location = $addressImporter->import($locationItem);
        }
        $this->event->setLocation($location);
    }

    private function fetchEventOrCreateNew(): bool
    {
        $event = $this->eventRepository->withLanguage(
            $this->data->getLanguage(),
            function () {
                return $this->eventRepository->findOneByOriginalIdAndClient(
                    $this->data->getExternalId(),
                    $this->client
                );
            }
        );

        $persisted = (bool) $event;
        $this->event = $event ?: new Event();

        return $persisted;
    }

    private function generateUrlIdentifierFromData(): string
    {
        return implode(
            '-',
            [
                $this->data->getExternalId(),
                $this->client,
                $this->data->getLanguage(),
            ]
        );
    }

    private function mapLocationData(ArticleWithLocationDataInterface $data)
    {
        if ($data->getLocationId()) {
            $city = $this->articleRepository->withLanguage(
                $this->event->getLanguage(),
                function () use ($data) {
                    return $this->articleRepository->findOneByOriginalId($data->getLocationId() ?? '');
                }
            );
            $this->event->setCity($city);
        }
    }
}
