<?php declare(strict_types=1);
namespace Newland\Toubiz\Poi\Neos\ViewHelpers\Widget\Controller;

/*
 * This file is part of the "toubiz-poi-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\FluidAdaptor\Core\Widget\AbstractWidgetController;
use Neos\Neos\Service\LinkingService;
use Newland\Toubiz\Poi\Neos\Domain\Repository\TopicRepository;
use Newland\Toubiz\Sync\Neos\Domain\Filter\ArticleFilter;
use Newland\Toubiz\Sync\Neos\Domain\Filter\EventDateFilter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Model\CityData;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventDateRepository;
use Newland\Toubiz\Sync\Neos\Exception\MissingDataException;
use Psr\Log\LoggerInterface;

/**
 * The widget controller for the city list widget.
 */
class LinkListController extends AbstractWidgetController
{

    /**
     * @var TopicRepository
     * @Flow\Inject()
     */
    protected $topicRepository;

    /**
     * @var array
     * @Flow\InjectConfiguration(package="Newland.Toubiz.Poi.Neos.cityDetails")
     */
    protected $cityDetailsConfiguration;

    /**
     * @var LinkingService
     * @Flow\Inject()
     */
    protected $linkingService;

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

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

    public function indexAction(): void
    {
        $configuration = $this->cityDetailsConfiguration['links'];
        $links = [];
        $article = $this->widgetConfiguration['article'];
        if ($configuration['enabled'] === true) {
            $links = $configuration['data'];
            array_walk(
                $links,
                function (&$linkData) use ($article) {
                    $linkData = $this->prepareTargetUrl($linkData, $article);
                }
            );
        }
        $this->view->assignMultiple(
            [
                'links' => $links,
                'article' => $article,
            ]
        );
    }

    private function prepareTargetUrl(array $linkData, Article $article): ?array
    {
        $linkData['hasResults'] = false;

        if (!$linkData['target']) {
            return $linkData;
        }

        $target = $linkData['target'];
        if (strpos($target, 'node://') === 0) {
            $pluginNamespace = $linkData['pluginNamespace'] ?? '--plugin';
            $linkData['url'] = $this->getUriFromNode($article, $linkData['target'], $pluginNamespace);
            $linkData['hasResults'] = $this->hasResults($linkData);
        } else {
            $linkData['url'] = $this->fillUriPlaceholders($article, $linkData['target']);
            $linkData['hasResults'] = true;
        }

        return $linkData;
    }

    /**
     * @param string $url
     * @param array $replacements
     * @return string
     * @todo Extract to helper package
     *
     */
    private function urlTemplate(string $url, array $replacements): string
    {
        return str_replace(
            array_keys($replacements),
            array_map('urlencode', array_values($replacements)),
            $url
        );
    }

    private function getUriFromNode(Article $article, string $nodeUri, string $pluginNamespace): string
    {
        $baseNode = $this->widgetConfiguration['node'];
        $node = $this->linkingService->convertUriToObject($nodeUri, $baseNode);
        $arguments = [
            $pluginNamespace => [
                'query' => [
                    'city' => $article->getPersistenceObjectIdentifier(),
                ],
            ],
        ];

        try {
            $uri = $this->linkingService->createNodeUri(
                $this->getControllerContext(),
                $node,
                $baseNode,
                null,
                false,
                $arguments
            );
        } catch (\Exception $exception) {
            $this->logger->error($exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__));
            $uri = '';
        }

        return $uri;
    }

    private function fillUriPlaceholders(Article $article, string $targetUri): string
    {
        if ($article->getCityData() === null) {
            throw new MissingDataException('City article has no city data attached.', 1561279197);
        }

        $uri = $this->urlTemplate(
            $targetUri,
            $this->getUrlReplacements($article->getCityData())
        );
        return $uri;
    }

    private function getUrlReplacements(CityData $cityData): array
    {
        return [
            '{idToubiz}' => $cityData->getIdToubiz(),
            '{idTomas}' => $cityData->getIdTomas(),
        ];
    }

    private function hasResults(array $linkData): bool
    {
        $type = $linkData['type'] ?? '';


        if ($type === 'article') {
            return $this->checkArticleResults($linkData);
        }

        if ($type === 'event') {
            return $this->checkEventResults($linkData);
        }

        // If the type is unknown, it might be an external link.
        return true;
    }

    private function checkArticleResults(array $linkData): bool
    {
        $filter = new ArticleFilter();

        /** @var Article $city */
        $city = $this->widgetConfiguration['article'];
        $cityData = $city->getCityData();
        if ($cityData) {
            $filter->setZips($cityData->getZipCodes());
        }

        if ((string) ($linkData['mainType'] ?? '') !== '') {
            $filter->setMainType($linkData['mainType']);
        }

        $language = $this->getLanguageForNodeUri($linkData['target']);
        $result = $this->articleRepository->withLanguage($language, function () use ($filter) {
            return $this->articleRepository->countByFilter($filter, 10);
        });
        return $result['items'] > 0;
    }

    private function checkEventResults(array $linkData): bool
    {

        $filter = new EventDateFilter();

        /** @var Article $city */
        $city = $this->widgetConfiguration['article'];
        $cityData = $city->getCityData();
        if ($cityData) {
            $filter->setLocationZips($cityData->getZipCodes());
        }

        $language = $this->getLanguageForNodeUri($linkData['target']);
        $result = $this->eventDateRepository->withLanguage($language, function () use ($filter) {
            return $this->eventDateRepository->countByFilter($filter, 10);
        });
        return $result['items'] > 0;
    }

    private function getLanguageForNodeUri($uri): ?string
    {
        /** @var NodeInterface|null $node */
        $node = $this->linkingService->convertUriToObject($uri, $this->widgetConfiguration['node']);
        if ($node) {
            return $node->getDimensions()['language'][0] ?? null;
        }
        return null;
    }
}
