<?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\Collection;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Api\ObjectAdapter\HasLocationDataInterface;
use Newland\Toubiz\Api\ObjectAdapter\EventAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\EventDateAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\HasLanguageGroupingSeparateFromOriginalId;
use Newland\Toubiz\Api\ObjectAdapter\HasMarketingPriceInterface;
use Newland\Toubiz\Api\ObjectAdapter\HasSeoInformationInterface;
use Newland\Toubiz\Api\ObjectAdapter\MediumAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\ProvidesRawJson;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
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;

class EventImporter extends AbstractImporter
{
    use ClientAware;
    use MapsMarketingPrice;
    use MapsSeoInformation;

    /**
     * @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
     */
    public function import($data): ?Event
    {
        $this->initializeLogger(
            $data,
            [
                'event' => [
                    'name' => $data->getName(),
                    'externalId' => $data->getExternalId(),
                ],
            ]
        );

        $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->mapAreaName();
        $this->mapCategories();
        $this->mapEventDates();
        $this->mapEventTags();
        $this->mapMedia();

        if ($data instanceof HasMarketingPriceInterface) {
            $this->event->setMarketingPrice($this->mapMarketingPrice($data));
        }

        if ($data instanceof HasLocationDataInterface) {
            $this->mapLocationData($data);
        }

        if ($data instanceof HasLanguageGroupingSeparateFromOriginalId) {
            $this->event->setLanguageGrouping($data->getLanguageGrouping());
        } else {
            $this->event->setLanguageGrouping($data->getExternalId());
        }

        if ($data instanceof ProvidesRawJson) {
            $this->event->setRawJson($data->getRawJson());
        }

        if ($data instanceof HasSeoInformationInterface) {
            $this->mapSeoInformation($data->getSeoInformation(), $this->event);
        }

        $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->setEventType($this->data->getEventType());
        $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->setLastModifiedAt($this->data->getLastModifiedAt());
        $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());
        $this->event->setAdditionalSearchString((string) $this->data->getAdditionalSearchString());
        $this->event->setDataSource($this->data->getDataSource());
        $this->event->setCanceled($this->data->getIsCanceled());
        $this->event->setCanonicalUrl($this->data->getCanonicalUrl());
    }

    protected function mapCategories(): void
    {
        $this->updateCollection(
            $this->event->getCategories(),
            $this->data->getCategories(),
            function ($data) {
                $importer = new CategoryImporter();
                $importer->loggingContext($this->context);
                $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->loggingContext($this->context);
                $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->loggingContext($this->context);
                $importer->setLanguage($this->data->getLanguage());
                return $importer->import($data);
            }
        );
    }

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

        $importer = new MediumImporter();
        $importer->setDownload($this->packageConfig['downloadImages']['events'] ?? false);
        $importer->loggingContext($this->context);

        /** @var Collection<int, Medium> $media */
        [ $media ] = $this->updateCollection(
            $this->event->getMedia(),
            $this->data->getMedia(),
            function (MediumAdapterInterface $data) use (&$existing, $importer) {
                return $importer->import($data, $existing[$data->getExternalId()] ?? null);
            }
        );

        $mainMediumEntry = $this->data->getMainMedium();
        $mainMedium = null;
        if ($mainMediumEntry) {
            $existingMainMedium = null;
            foreach ($media as $medium) {
                if ($medium->getOriginalId() === $mainMediumEntry->getExternalId()) {
                    $existingMainMedium = $medium;
                    break;
                }
            }

            $mainMedium = $importer->import($mainMediumEntry, $existingMainMedium);
        }
        $this->event->setMainMedium($mainMedium);
    }

    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);
    }

    public function mapAreaName(): void
    {
        $this->event->setAreaName($this->data->getAreaName());
    }

    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(HasLocationDataInterface $data): void
    {
        $cities = array_values(
            array_filter(
                array_map(
                    function ($selector) {
                        return $this->articleRepository->withLanguage(
                            $this->event->getLanguage(),
                            function () use ($selector) {
                                return $this->articleRepository->findOneByExternalIdSelector($selector, $this->client);
                            }
                        );
                    },
                    $data->getCitySelectors()
                )
            )
        );
        [ $collection, $added, $removed ] = $this->setCollectionValues(
            $this->event->getCities(),
            $cities
        );

        $this->event->setCities($collection);
    }
}
