<?php

/*
 * This file is part of Sulu.
 *
 * (c) Sulu GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\WebsiteBundle\Routing;

use PHPCR\RepositoryException;
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
use Sulu\Bundle\PageBundle\Document\PageDocument;
use Sulu\Component\Content\Compat\Structure\PageBridge;
use Sulu\Component\Content\Compat\StructureManagerInterface;
use Sulu\Component\Content\Document\Behavior\ExtensionBehavior;
use Sulu\Component\Content\Document\RedirectType;
use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
use Sulu\Component\DocumentManager\DocumentManagerInterface;
use Sulu\Component\Security\Authorization\PermissionTypes;
use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
use Sulu\Component\Security\Authorization\SecurityCondition;
use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
 * The PortalRouteProvider should load the dynamic routes created by Sulu.
 */
class ContentRouteProvider implements RouteProviderInterface
{
    /**
     * @var DocumentManagerInterface
     */
    private $documentManager;

    /**
     * @var DocumentInspector
     */
    private $documentInspector;

    /**
     * @var ResourceLocatorStrategyPoolInterface
     */
    private $resourceLocatorStrategyPool;

    /**
     * @var StructureManagerInterface
     */
    private $structureManager;

    /**
     * @var WebspaceManagerInterface
     */
    private $webspaceManager;

    /**
     * @var RequestAnalyzerInterface
     */
    private $requestAnalyzer;

    /**
     * @var SecurityCheckerInterface|null
     */
    private $securityChecker;

    /**
     * @var array
     */
    private $defaultOptions;

    public function __construct(
        DocumentManagerInterface $documentManager,
        DocumentInspector $documentInspector,
        ResourceLocatorStrategyPoolInterface $resourceLocatorStrategyPool,
        StructureManagerInterface $structureManager,
        WebspaceManagerInterface $webspaceManager,
        RequestAnalyzerInterface $requestAnalyzer,
        SecurityCheckerInterface $securityChecker = null,
        array $defaultOptions = []
    ) {
        $this->documentManager = $documentManager;
        $this->documentInspector = $documentInspector;
        $this->resourceLocatorStrategyPool = $resourceLocatorStrategyPool;
        $this->structureManager = $structureManager;
        $this->webspaceManager = $webspaceManager;
        $this->requestAnalyzer = $requestAnalyzer;
        $this->securityChecker = $securityChecker;
        $this->defaultOptions = $defaultOptions;
    }

    public function getRouteCollectionForRequest(Request $request)
    {
        $collection = new RouteCollection();

        if ('' === $request->getRequestFormat()) {
            return $collection;
        }

        /** @var RequestAttributes $attributes */
        $attributes = $request->attributes->get('_sulu');
        if (!$attributes) {
            return $collection;
        }

        $matchType = $attributes->getAttribute('matchType');

        // no portal information without localization supported
        if (null === $attributes->getAttribute('localization')
            && RequestAnalyzerInterface::MATCH_TYPE_PARTIAL !== $matchType
            && RequestAnalyzerInterface::MATCH_TYPE_REDIRECT !== $matchType
        ) {
            return $collection;
        }

        $resourceLocator = $this->decodePathInfo($attributes->getAttribute('resourceLocator'));
        $prefix = $attributes->getAttribute('resourceLocatorPrefix');

        $pathInfo = $this->decodePathInfo($request->getPathInfo());
        $htmlRedirect = $pathInfo !== $prefix . $resourceLocator
                        && \in_array($request->getRequestFormat(), ['htm', 'html']);

        if ($htmlRedirect
            || RequestAnalyzerInterface::MATCH_TYPE_REDIRECT == $matchType
            || RequestAnalyzerInterface::MATCH_TYPE_PARTIAL == $matchType
        ) {
            return $collection;
        }

        // just show the page
        $portal = $attributes->getAttribute('portal');
        $locale = $attributes->getAttribute('localization')->getLocale();
        $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey(
            $portal->getWebspace()->getKey()
        );

        try {
            // load content by url ignore ending trailing slash
            /** @var PageDocument $document */
            $document = $this->documentManager->find(
                $resourceLocatorStrategy->loadByResourceLocator(
                    \rtrim($resourceLocator, '/'),
                    $portal->getWebspace()->getKey(),
                    $locale
                ),
                $locale,
                [
                    'load_ghost_content' => false,
                ]
            );

            if (!$document->getTitle()) {
                // If the title is empty the document does not exist in this locale
                // Necessary because of https://github.com/sulu/sulu/issues/2724, otherwise locale could be checked
                return $collection;
            }

            if ($this->securityChecker && $portal->getWebspace()->hasWebsiteSecurity()) {
                $this->securityChecker->checkPermission(
                    new SecurityCondition(
                        'sulu.webspaces.' . $document->getWebspaceName(),
                        $document->getLocale(),
                        \get_class($document),
                        $document->getUuid()
                    ),
                    PermissionTypes::VIEW
                );
            }

            if (\preg_match('/\/$/', $resourceLocator) && ('/' !== $resourceLocator || $prefix)) {
                // redirect page to page without slash at the end
                $url = $prefix . \rtrim($resourceLocator, '/');
                if ($request->getQueryString()) {
                    $url .= '?' . $request->getQueryString();
                }
                $collection->add('redirect_' . \uniqid(), $this->getRedirectRoute($request, $url));
            } elseif (RedirectType::INTERNAL === $document->getRedirectType()) {
                $redirectUrl = $this->webspaceManager->findUrlByResourceLocator(
                    $document->getRedirectTarget()->getResourceSegment(),
                    null,
                    $document->getLocale(),
                    $document->getRedirectTarget()->getWebspaceName()
                );

                if ($request->getQueryString()) {
                    $redirectUrl .= '?' . $request->getQueryString();
                }

                $collection->add(
                    $document->getStructureType() . '_' . $document->getUuid(),
                    $this->getRedirectRoute($request, $redirectUrl)
                );
            } elseif (RedirectType::EXTERNAL === $document->getRedirectType()) {
                $collection->add(
                    $document->getStructureType() . '_' . $document->getUuid(),
                    $this->getRedirectRoute($request, $document->getRedirectExternal())
                );
            } elseif (!$this->checkResourceLocator($resourceLocator, $prefix)) {
                return $collection;
            } else {
                if ($document instanceof ExtensionBehavior) {
                    $documentSegments = $document->getExtensionsData()['excerpt']['segments'];
                    $documentSegmentKey = $documentSegments[$portal->getWebspace()->getKey()] ?? null;
                    $segment = $this->requestAnalyzer->getSegment();

                    if ($segment && $documentSegmentKey && $segment->getKey() !== $documentSegmentKey) {
                        $this->requestAnalyzer->changeSegment($documentSegmentKey);
                    }
                }

                // convert the page to a StructureBridge because of BC
                $metadata = $this->documentInspector->getStructureMetadata($document);
                if (!$metadata) {
                    return $collection;
                }

                /** @var PageBridge $structure */
                $structure = $this->structureManager->wrapStructure(
                    $this->documentInspector->getMetadata($document)->getAlias(),
                    $metadata
                );
                $structure->setDocument($document);

                // show the page
                $collection->add(
                    $document->getStructureType() . '_' . $document->getUuid(),
                    $this->getStructureRoute($request, $structure)
                );
            }
        } catch (ResourceLocatorNotFoundException $exc) {
            // just do not add any routes to the collection
        } catch (ResourceLocatorMovedException $exc) {
            // old url resource was moved
            $collection->add(
                $exc->getNewResourceLocatorUuid() . '_' . \uniqid(),
                $this->getRedirectRoute($request, $prefix . $exc->getNewResourceLocator())
            );
        } catch (RepositoryException $exc) {
            // just do not add any routes to the collection
        }

        return $collection;
    }

    public function getRouteByName($name, $parameters = [])
    {
        // TODO: Implement getRouteByName() method.
    }

    public function getRoutesByNames($names, $parameters = [])
    {
        // TODO
        return [];
    }

    /**
     * Checks if the resource locator is valid.
     * A resource locator with a slash only is not allowed, the only exception is when it is a single language
     * website, where the browser automatically adds the slash.
     *
     * @param string $resourceLocator
     * @param string $resourceLocatorPrefix
     *
     * @return bool
     */
    private function checkResourceLocator($resourceLocator, $resourceLocatorPrefix)
    {
        return !('/' === $resourceLocator && $resourceLocatorPrefix);
    }

    /**
     * @param string $url
     *
     * @return Route
     */
    protected function getRedirectRoute(Request $request, $url)
    {
        $requestFormat = $request->getRequestFormat(null);
        $formatSuffix = $requestFormat ? '.' . $requestFormat : '';

        // redirect to linked page
        return new Route(
            $this->decodePathInfo($request->getPathInfo()),
            [
                '_controller' => 'sulu_website.redirect_controller:redirectAction',
                'url' => $url . $formatSuffix,
            ],
            [],
            $this->defaultOptions
        );
    }

    /**
     * @return Route
     */
    protected function getStructureRoute(Request $request, PageBridge $content)
    {
        return new Route(
            $this->decodePathInfo($request->getPathInfo()),
            [
                '_controller' => $content->getController(),
                'structure' => $content,
                'partial' => 'true' === $request->get('partial', 'false'),
            ],
            [],
            $this->defaultOptions
        );
    }

    /**
     * Server encodes the url and symfony does not encode it
     * Symfony decodes this data here https://github.com/symfony/symfony/blob/3.3/src/Symfony/Component/Routing/Matcher/UrlMatcher.php#L91.
     *
     * @param string $pathInfo
     *
     * @return string
     */
    private function decodePathInfo($pathInfo)
    {
        if (null === $pathInfo || '' === $pathInfo) {
            return '';
        }

        return '/' . \ltrim(\rawurldecode($pathInfo), '/');
    }
}
