<?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\ArticleAdapterInterface;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\Category;
use Newland\Toubiz\Sync\Neos\Domain\Model\Medium;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;

/**
 * Article importer.
 *
 * @Flow\Scope("singleton")
 */
class ArticleImporter extends AbstractImporter
{
    /**
     * @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->mapAddresses($data, $article);
        $this->mapCategories($data, $article);
        $this->mapFiles($data, $article);
        $this->mapMedia($data, $article);
        $this->mapAttributes($data, $article);
        $article->setUpdatedAt(new \DateTime);

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

        return $article;
    }


    private function findArticle(ArticleAdapterInterface $data): ?Article
    {
        $language = $data->getLanguage();
        if ($language) {
            $this->articleRepository->setLanguage($language);
        }

        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->setFacebookUri($data->getFacebookUri());
        $article->setTwitterUri($data->getTwitterUri());
        $article->setInstagramUri($data->getInstagramUri());
        $article->setYoutubeUri($data->getYoutubeUri());
        $article->setWikipediaUri($data->getWikipediaUri());
        $article->setFlickrUri($data->getFlickrUri());
        $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 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);
            }
        }
        $article->setMedia($media);

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

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

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

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