<?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 Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Api\ObjectAdapter\EventAdapterInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
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
{
    /**
     * @var EventAdapterInterface
     */
    protected $data;

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

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

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

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

    /**
     * Post-import.
     *
     * Should be executed after importing multiple records.
     *
     * @return void
     */
    public function postImport()
    {
        $endsAt = new \DateTime();
        $endsAt->setTime(0, 0, 0);

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

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

        $this->eventDateRepository->deletePastRecords($endsAt);
    }

    /**
     * Import method.
     *
     * Persist given data by creating new objects or updating existing ones.
     *
     * @param EventAdapterInterface $data
     * @return void
     */
    public function import($data)
    {
        $this->initialize($data);

        $persisted = $this->fetchEventOrCreateNew();

        // Map simple values to have a valid event.
        $this->mapSimpleValues();

        // The event is being persisted in order to create relations to it.
        if ($persisted) {
            $this->eventRepository->update($this->event);
        } else {
            $this->eventRepository->add($this->event);
        }

        $this->mapAddresses();
        $this->mapCategories();
        $this->mapEventDates();
        $this->mapEventTags();
        $this->mapMedia();

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

        // Persisting is required due to the API returning the same object multiple times!
        $this->persistenceManager->persistAll();
    }

    protected function mapSimpleValues(): void
    {
        $this->event->setLanguage($this->language);

        $this->event->setOriginalId($this->data->getExternalId());
        $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());
    }

    /**
     * Imports and maps addresses.
     *
     * @return void
     */
    protected function mapAddresses()
    {
        $organizerItem = $this->data->getOrganizer();
        $organizer = null;
        if ($organizerItem) {
            $importer = new AddressImporter();
            $organizer = $importer->import($organizerItem);
        }
        $this->event->setOrganizer($organizer);

        $locationItem = $this->data->getLocation();
        $location = null;
        if ($locationItem) {
            $importer = new AddressImporter();
            $location = $importer->import($locationItem);
        }
        $this->event->setLocation($location);
    }

    /**
     * Maps categories onto the event.
     *
     * @return void
     */
    protected function mapCategories()
    {
        $categories = $this->event->getCategories();
        $categories->clear();
        foreach ($this->data->getCategories() as $data) {
            $importer = new CategoryImporter();
            $importer->setLanguage($this->language);
            $categories->add($importer->import($data));
        }
        $this->event->setCategories($categories);
    }

    /**
     * Maps event dates onto the event.
     *
     * @todo removal procedure is generic, refactor
     * @return void
     */
    public function mapEventDates()
    {
        $eventDates = $this->event->getEventDates();
        $removalIds = [];
        foreach ($eventDates->toArray() as $date) {
            $removalIds[] = $date->getPersistenceObjectIdentifier();
        }

        $eventDates->clear();
        foreach ($this->data->getEventDates() as $data) {
            $importer = new EventDateImporter();
            $importer->setLanguage($this->language);
            $importer->setEvent($this->event);
            $event = $importer->import($data);
            $eventDates->add($event);

            $indexKey = array_search($event->getPersistenceObjectIdentifier(), $removalIds, true);
            unset($removalIds[$indexKey]);
        }
        $this->event->setEventDates($eventDates);

        $this->eventDateRepository->removeByIds($removalIds);
    }

    /**
     * Maps event tags onto the event.
     *
     * @return void
     */
    public function mapEventTags()
    {
        $eventTags = $this->event->getEventTags();
        if ($eventTags->isEmpty() === false) {
            $eventTags->clear();
            foreach ($this->data->getEventTags() as $data) {
                $importer = new EventTagImporter;
                $importer->setLanguage($this->language);
                $eventTags->add($importer->import($data));
            }
            $this->event->setEventTags($eventTags);
        }
    }

    /**
     * Maps multimedia events onto the event.
     *
     * @return void
     */
    public function mapMedia()
    {
        $media = $this->event->getMedia();
        $media->clear();
        foreach ($this->data->getMedia() as $data) {
            $importer = new MediumImporter;
            $importer->setEvent($this->event);
            $media->add($importer->import($data));
        }
        $this->event->setMedia($media);
    }

    private function fetchEventOrCreateNew(): bool
    {
        $event = $this->eventRepository->findOneByOriginalId($this->data->getExternalId());
        $persisted = (bool) $event;
        $this->event = $event ?? new Event();
        return $persisted;
    }

    private function initialize($data): void
    {
        $this->data = $data;
        $this->eventRepository->setLanguage($this->language);
        $this->eventDateRepository->setLanguage($this->language);
    }

    private function generateUrlIdentifierFromData(): string
    {
        return $this->event->getOriginalId() . '-' . $this->event->getLanguage();
    }
}
