<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CmsBundle\Tests\Page;

use Contao\CoreBundle\Exception\NoRootPageFoundException;
use Contao\CoreBundle\Routing\PageFinder;
use Contao\Model\Collection;
use Contao\PageModel;
use Contao\TestCase\ContaoTestCase;
use Ferienpass\CmsBundle\Page\PageBuilder;
use Ferienpass\CmsBundle\Page\PageBuilderFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Requests that are not Contao pages (the security handlers, the error
 * screens, /robots.txt, the account and registration routes) call
 * `create()` without a page model, so the factory has to find a root page
 * itself.
 *
 * It used to do that with `PageModel::findPublishedRootPages()[0]`, which
 * warns ("Trying to access array offset on null") and then hands `null` to
 * PageBuilder as soon as an installation has no published root page — a
 * fresh tenant, an unpublished root page, or a host nobody mapped. In
 * production that warning is turned into an ErrorException, so a plain 404
 * (e.g. /robots.txt on a domain without a root page) came back as a 500.
 */
final class PageBuilderFactoryTest extends ContaoTestCase
{
    public function testThrowsWhenTheInstallationHasNoPublishedRootPage(): void
    {
        $factory = $this->factory(hostRootPage: null, publishedRootPages: null);

        $this->expectException(NoRootPageFoundException::class);

        $factory->create();
    }

    public function testUsesTheRootPageOfTheCurrentHost(): void
    {
        $hostRootPage = $this->stubPageModel();

        $factory = $this->factory(hostRootPage: $hostRootPage, publishedRootPages: [$this->stubPageModel()]);

        self::assertSame(
            $hostRootPage,
            $this->pageModelOf($factory->create()),
            'A multi-domain installation must not render another domain\'s layout.'
        );
    }

    public function testFallsBackToTheFirstPublishedRootPageForAnUnmappedHost(): void
    {
        $firstRootPage = $this->stubPageModel();

        $factory = $this->factory(hostRootPage: null, publishedRootPages: [$firstRootPage, $this->stubPageModel()]);

        self::assertSame($firstRootPage, $this->pageModelOf($factory->create()));
    }

    public function testKeepsTheGivenPageModel(): void
    {
        $pageModel = $this->stubPageModel();

        $pageFinder = $this->createMock(PageFinder::class);
        $pageFinder->expects(self::never())->method('findRootPageForHostAndLanguage');

        $factory = $this->factory(hostRootPage: null, publishedRootPages: null, pageFinder: $pageFinder);

        self::assertSame($pageModel, $this->pageModelOf($factory->create($pageModel)));
    }

    /**
     * @param list<PageModel>|null $publishedRootPages
     */
    private function factory(?PageModel $hostRootPage, ?array $publishedRootPages, ?PageFinder $pageFinder = null): PageBuilderFactory
    {
        if (null === $pageFinder) {
            $pageFinder = $this->createStub(PageFinder::class);
            $pageFinder->method('findRootPageForHostAndLanguage')->willReturn($hostRootPage);
        }

        $collection = null;

        if (null !== $publishedRootPages) {
            $collection = $this->createStub(Collection::class);
            $collection->method('getModels')->willReturn($publishedRootPages);
        }

        $framework = $this->createContaoFrameworkStub([
            PageModel::class => $this->createConfiguredAdapterStub(['findPublishedRootPages' => $collection]),
        ]);

        $requestStack = new RequestStack();
        $requestStack->push(Request::create('https://cbb-grafschaft.de/robots.txt'));

        return new PageBuilderFactory(
            $this->createStub(FragmentHandler::class),
            $this->createStub(TranslatorInterface::class),
            $framework,
            $requestStack,
            $pageFinder,
        );
    }

    private function stubPageModel(): PageModel
    {
        $pageModel = $this->createStub(PageModel::class);
        $pageModel->method('loadDetails')->willReturnSelf();

        return $pageModel;
    }

    private function pageModelOf(PageBuilder $pageBuilder): PageModel
    {
        $property = new \ReflectionProperty(PageBuilder::class, 'pageModel');

        return $property->getValue($pageBuilder);
    }
}
