<?php declare(strict_types=1);


namespace Newland\Toubiz\Poi\Neos\Controller;

use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\ContentRepository\Exception\PageNotFoundException;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Routing\ObjectPathMapping;
use Neos\Flow\Mvc\Routing\ObjectPathMappingRepository;
use Neos\Flow\Utility\Now;
use Neos\Neos\Domain\Service\ContentContext;
use Newland\NeosCommon\Logging\DeprecationLog;
use Newland\Toubiz\Poi\Neos\Service\ArticleUrlService;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Service\UrlIdentifierRedirectService;
use Newland\Toubiz\Sync\Neos\Translation\CurrentLanguageService;

class RedirectController extends AbstractActionController
{

    /**
     * @var ArticleUrlService
     * @Flow\Inject()
     */
    protected $articleUrlService;

    /**
     * @var ObjectPathMappingRepository
     * @Flow\Inject()
     */
    protected $objectPathMappingRepository;

    /**
     * @var ContextFactoryInterface
     * @Flow\Inject()
     */
    protected $contextFactory;

    /**
     * @var DeprecationLog
     * @Flow\Inject()
     */
    protected $deprecationLog;

    /**
     * @var UrlIdentifierRedirectService
     * @Flow\Inject()
     */
    protected $urlIdentifierRedirect;

    /**
     * @var CurrentLanguageService
     * @Flow\Inject()
     */
    protected $currentLanguageService;

    public function articleDetailAction(Node $node, string $uriSegment): void
    {

        $this->currentLanguageService->updateLanguageFromNode($node);
        $article = $this->findPossibleRedirectionTarget($uriSegment);

        $node = $this->getCurrentSiteNode($article->getLanguage());
        if ($node === null) {
            throw new PageNotFoundException(sprintf(
                'No site node can be resolved for article %s',
                $article->getPersistenceObjectIdentifier()
            ));
        }

        $uri = $this->articleUrlService->generateUrlByCurrentNode($article, $node);
        $this->deprecationLog->logDeprecation('Deprecated article URL ' . $uriSegment . ' redirected to ' . $uri);
        $this->redirectToUri($uri, 0, 301);
    }

    private function findPossibleRedirectionTarget(string $uriSegment): Article
    {
        $article = $this->findArticleBasedOnIncorrectPath($uriSegment)
            ?? $this->findArticleBasedOnMissingUrlIdentifier($uriSegment)
            ?? $this->findArticleBasedOnPersistenceObjectIdentifier($uriSegment)
            ?? $this->findArticleBasedOnOriginalId($uriSegment);

        if ($article === null) {
            throw new PageNotFoundException(sprintf('Uri segment %s could not be resolved to article', $uriSegment));
        }

        return $article;
    }

    private function findArticleBasedOnMissingUrlIdentifier(string $uriSegment): ?Article
    {
        $query = $this->objectPathMappingRepository->createQuery();

        $parts = [];
        $parts[] = $query->logicalAnd([
            $query->equals('objectType', Article::class),
            $query->equals('uriPattern', '{name}-{urlIdentifier}'),
            $query->like('pathSegment', $uriSegment . '%'),
        ]);
        $parts[] = $query->logicalAnd([
            $query->equals('objectType', Article::class),
            $query->equals('uriPattern', '{urlIdentifier}-{name}'),
            $query->like('pathSegment', '%' . $uriSegment),
        ]);

        $query->matching($query->logicalOr($parts));
        $query->setLimit(1);

        /** @var ObjectPathMapping|null $mapping */
        $mapping = $query->execute()->getFirst();
        if ($mapping === null) {
            return null;
        }

        return $this->articleRepository->withoutLanguageHandling(function () use ($mapping) {
            return $this->articleRepository->findByIdentifier($mapping->getIdentifier());
        });
    }

    private function findArticleBasedOnIncorrectPath(string $uriSegment): ?Article
    {
        return $this->articleRepository->withoutLanguageHandling(function () use ($uriSegment) {
            $parts = (array) preg_split('/\W/', $uriSegment);
            $potentialUrlIdentifiers = $this->urlIdentifierRedirect->getRedirects($parts[count($parts) - 1]);
            foreach ($potentialUrlIdentifiers as $potentialUrlIdentifier) {
                $article = $this->articleRepository->findOneBy([ 'urlIdentifier' => $potentialUrlIdentifier ]);
                if ($article) {
                    return $article;
                }
            }
            return null;
        });
    }

    private function findArticleBasedOnPersistenceObjectIdentifier(string $uriSegment): ?Article
    {
        return $this->articleRepository->withoutLanguageHandling(function () use ($uriSegment) {
            return $this->articleRepository->findByIdentifier($uriSegment);
        });
    }

    private function findArticleBasedOnOriginalId(string $uriSegment): ?Article
    {
        return $this->articleRepository->findOneByOriginalId($uriSegment);
    }

    protected function getCurrentSiteNode(?string $language): ?Node
    {
        $dimensions = $language ? [ 'language' => [ $language ] ] : [];
        $context = $this->contextFactory->create(
            [
                'workspaceName' => 'live',
                'currentDateTime' => new Now(),
                'dimensions' => $dimensions,
                'invisibleContentShown' => false,
                'removedContentShown' => false,
                'inaccessibleContentShown' => false,
            ]
        );

        if (!($context instanceof ContentContext)) {
            return null;
        }

        $node = $context->getCurrentSiteNode();
        if (!($node instanceof Node)) {
            return null;
        }

        return $node;
    }
}
