<?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\Constants\EntityListType;
use Newland\Toubiz\Api\ObjectAdapter\Article\ArticleTypeCityAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\Article\ArticleWithSocialMediaLinksAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\ArticleAdapterInterface;
use Newland\Toubiz\Api\Service\WithUuidPredictionService;
use Newland\Toubiz\Api\Service\WithUuidPredictionServiceInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Category;
use Newland\Toubiz\Sync\Neos\Domain\Model\CityData;
use Newland\Toubiz\Sync\Neos\Domain\Model\Medium;
use Newland\Toubiz\Sync\Neos\Domain\Model\RelatedLists\EntityList;
use Newland\Toubiz\Sync\Neos\Domain\Model\RelatedLists\EntityListInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\RelatedLists\RelatedLists;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;

/**
 * Article importer.
 *
 * @Flow\Scope("singleton")
 */
class ArticleImporter extends AbstractImporter
{
    use WithUuidPredictionService;

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

    /**
     * @var string
     */
    protected $client = '';

    /**
     * @param ArticleAdapterInterface $data
     * @return void
     */
    public function remove(ArticleAdapterInterface $data): void
    {
        $article = $this->findArticle($data);

        if ($article !== null) {
            $this->articleRepository->remove($article);
            $this->persistenceManager->persistAll();
        }
    }

    /**
     * Import method.
     *
     * Persist given data by creating new objects or updating existing ones.
     *
     * @param ArticleAdapterInterface $data
     * @return Article
     */
    public function import($data): Article
    {
        $article = $this->findArticle($data);
        $persisted = $article !== null;

        if ($article === null) {
            $article = new Article();
        }

        $this->mapSimpleValues($data, $article);
        $this->mapRatingValues($data, $article);
        $article->setClient($this->client);
        $this->mapOpeningTimes($data, $article);

        $this->mapGeoCoordinates($data, $article);
        $this->mapAddresses($data, $article);
        $this->mapCategories($data, $article);
        $this->mapFiles($data, $article);
        $this->mapMedia($data, $article);
        $this->mapAttributes($data, $article);

        if ($data instanceof ArticleWithSocialMediaLinksAdapterInterface) {
            $this->mapSocialMediaLinks($data, $article);
        }

        if ($data instanceof ArticleTypeCityAdapterInterface) {
            $this->mapCityData($data, $article);
        }

        $article->setUpdatedAt(new \DateTime);

        if ($persisted) {
            $this->articleRepository->update($article);
        } else {
            $this->articleRepository->add($article);
        }

        return $article;
    }

    protected function mapSocialMediaLinks(ArticleWithSocialMediaLinksAdapterInterface $data, Article $article): void
    {
        $article->setFacebookUri($data->getFacebookUri());
        $article->setTwitterUri($data->getTwitterUri());
        $article->setInstagramUri($data->getInstagramUri());
        $article->setYoutubeUri($data->getYoutubeUri());
        $article->setWikipediaUri($data->getWikipediaUri());
        $article->setFlickrUri($data->getFlickrUri());
    }


    private function findArticle(ArticleAdapterInterface $data): ?Article
    {
        return $this->articleRepository->withLanguage(
            $data->getLanguage(),
            function () use ($data) {
                if ($this->client) {
                    return $this->articleRepository->findOneByOriginalIdAndClient(
                        $data->getExternalId(),
                        $this->client
                    );
                }

                return $this->articleRepository->findOneByOriginalId($data->getExternalId());
            }
        );
    }

    public function setClient(string $client): void
    {
        $this->client = $client;
    }

    protected function mapSimpleValues(ArticleAdapterInterface $data, Article $article): void
    {
        $article->setOriginalId($data->getExternalId());
        $article->setMainType($data->getMainType());
        $article->setName($data->getName());
        $article->setAbstract($data->getAbstract());
        $article->setDescription($data->getDescription());
        $article->setSourceName($data->getSourceName());
        $article->setAuthorName($data->getAuthorName());
        $article->setBookingUri($data->getBookingUri());
        $article->setDetailUri($data->getDetailUri());
        $article->setLanguage($data->getLanguage());
        $article->setUrlIdentifier($this->generateUrlIdentifierFromData($data));

        $mainAddressAdapter = $data->getMainAddress();
        if ($mainAddressAdapter) {
            $article->setMainAddress(
                (new AddressImporter())->import($mainAddressAdapter, $article->getMainAddress())
            );
        } else {
            $article->setMainAddress(null);
        }
    }

    protected function mapRatingValues(ArticleAdapterInterface $data, Article $article): void
    {
        $article->setStarClassification($data->getStarClassification() ?? 0);
        $article->setAverageRating($data->getAverageRating() ?? 0);
        $article->setNumberOfRatings($data->getNumberOfRatings() ?? 0);
    }

    protected function mapGeoCoordinates(ArticleAdapterInterface $data, Article $article): void
    {
        $article->setLatitude($data->getLatitude());
        $article->setLongitude($data->getLongitude());
    }

    protected function mapAddresses(ArticleAdapterInterface $data, Article $article): void
    {
        $existing = [];
        foreach ($article->getAddresses() as $address) {
            $existing[$address->getOriginalId()] = $address;
        }

        $addresses = $article->getAddresses();
        $addresses->clear();

        foreach ($data->getAddresses() as $adapter) {
            $entity = (new AddressImporter())->import($adapter, $existing[$adapter->getExternalId()] ?? null);
            if (!$addresses->contains($entity)) {
                $addresses->add($entity);
            }
        }

        $article->setAddresses($addresses);
    }

    protected function mapCategories(ArticleAdapterInterface $data, Article $article): void
    {
        $categories = $article->getCategories();
        $categories->clear();

        $importer = new CategoryImporter();
        $importer->setLanguage($data->getLanguage());

        foreach ($data->getCategories() as $entry) {
            $category = $importer->import($entry);

            if (!$categories->contains($category)) {
                $categories->add($category);
            }
        }

        $article->setCategories($categories);

        $sorting = array_map(
            function (Category $category) {
                return $category->getOriginalId();
            },
            $categories->toArray()
        );

        $article->setRelationSortingForField('categories', $sorting);
    }

    protected function mapMedia(ArticleAdapterInterface $data, Article $article): void
    {
        $existing = [];
        foreach ($article->getMedia() as $media) {
            $existing[$media->getOriginalId()] = $media;
        }

        $media = $article->getMedia();
        $media->clear();

        foreach ($data->getMedia() as $entry) {
            $medium = (new MediumImporter)->import($entry, $existing[$entry->getExternalId()] ?? null);
            if (!$media->contains($medium)) {
                $media->add($medium);
                $existing[$entry->getExternalId()] = $medium;
            }
        }
        $article->setMedia($media);

        $sorting = array_map(
            function (Medium $medium) {
                return $medium->getOriginalId();
            },
            $media->toArray()
        );

        $article->setRelationSortingForField('media', $sorting);

        $mainMediumEntry = $data->getMainMedium();
        $mainMedium = null;
        if ($mainMediumEntry) {
            $mainMedium = (new MediumImporter())->import(
                $mainMediumEntry,
                $existing[$mainMediumEntry->getExternalId()] ?? null
            );
        }
        $article->setMainMedium($mainMedium);
    }

    protected function mapFiles(ArticleAdapterInterface $data, Article $article): void
    {
        $existing = [];
        foreach ($article->getFiles() as $file) {
            $existing[$file->getOriginalId()] = $file;
        }

        $files = $article->getFiles();
        $files->clear();

        foreach ($data->getFiles() as $entry) {
            $file = (new FileImporter)->import($entry, $existing[$entry->getExternalId()] ?? null);
            if (!$files->contains($file)) {
                $files->add($file);
            }
        }
        $article->setFiles($files);
    }

    public function mapOpeningTimes(ArticleAdapterInterface $data, Article $article): void
    {
        if ($data->getOpeningTimesFormat()) {
            $article->setOpeningTimesFormat((string) $data->getOpeningTimesFormat());
            $article->setOpeningTimes($data->getOpeningTimes() ?? '');
        }
    }

    public function deleteOldRecords(\DateInterval $timeToLive, int $mainType = null): int
    {
        $i = 0;
        $this->articleRepository->withoutLanguageHandling(
            function () use (&$i, $timeToLive, $mainType) {
                $i = $this->articleRepository->deleteOldRecords($timeToLive, $mainType);
            }
        );
        return $i;
    }

    protected function mapAttributes(ArticleAdapterInterface $data, Article $article): void
    {
        $attributes = $article->getAttributes();
        $attributes->clear();

        $attributesData = $data->getAttributes();
        if ($attributesData) {
            foreach ($attributesData as $attributeData) {
                $importer = new AttributeImporter();
                $importer->setArticle($article);
                $attribute = $importer->import($attributeData);
                if (!$attributes->contains($attribute)) {
                    $attributes->add($attribute);
                }
            }
        }

        $article->setAttributes($attributes);
    }

    protected function mapRelatedLists(ArticleTypeCityAdapterInterface $data, Article $article): void
    {
        if ($data instanceof WithUuidPredictionServiceInterface) {
            $data->setUuidPredictionService($this->uuidPredictionService);
        }

        $relatedListsData = $data->getRelatedLists();

        if ($relatedListsData === null) {
            return;
        }

        $relatedLists = new RelatedLists();
        $poiHighlightsData = $relatedListsData->getHighlightsList();
        if ($poiHighlightsData) {
            $entityList = new EntityList();
            $entityList->setType(EntityListType::ARTICLES);
            $entityList->setTitle($poiHighlightsData->getTitle());
            $entityList->setIdentifiers($poiHighlightsData->getIdentifiers());

            $relatedLists->setPoiHighlights($entityList);
        }

        $entityLists = [];
        foreach ($relatedListsData->getEntityLists() as $entityListData) {
            $entityList = new EntityList();
            $entityList->setType($entityListData->getType());
            $entityList->setTitle($entityListData->getTitle());
            $entityList->setIdentifiers($entityListData->getIdentifiers());

            $entityLists[] = $entityList;
        }

        $relatedLists->setEntityLists($entityLists);

        $article->setRelatedLists($relatedLists);
    }

    private function mapCityData(ArticleTypeCityAdapterInterface $data, Article $article): void
    {
        $this->mapRelatedLists($data, $article);

        $cityData = $article->getCityData();
        if ($cityData === null) {
            $cityData = new CityData();
        }

        $cityData->setCity($article);
        $cityData->setIdToubiz($data->getIdToubiz());
        $cityData->setIdTomas($data->getIdTomas());
        $cityData->setZipCodes($data->getZipCodes());
        $cityData->setClaim($data->getClaim());
        $cityData->setFacts($data->getFacts());
        $cityData->setWebcamUrl($data->getWebcamUrl());
        $cityData->setWebcamDescription($data->getWebcamDescription());
        $cityData->setNews($data->getNews());

        $article->setCityData($cityData);
    }

    private function generateUrlIdentifierFromData(ArticleAdapterInterface $data): string
    {
        return $data->getExternalId() . '-'
            . $this->client . '-'
            . $data->getMainType() . '-'
            . $data->getLanguage();
    }
}
