<?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\EventListener;

use Contao\CoreBundle\Event\RobotsTxtEvent;
use Contao\PageModel;
use Ferienpass\CmsBundle\EventListener\OfferListRobotsTxtListener;
use Ferienpass\CmsBundle\Filter\OfferListFilter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use webignition\RobotsTxt\Directive\Directive;
use webignition\RobotsTxt\Directive\UserAgentDirective;
use webignition\RobotsTxt\File\File;
use webignition\RobotsTxt\Record\Record;

/**
 * The filter permutations of the offer list are what JS-executing crawlers grind
 * through — each hit a full, uncacheable list render.
 */
final class OfferListRobotsTxtListenerTest extends TestCase
{
    public function testDisallowsEveryFilterParameterInAnyQueryPosition(): void
    {
        $robotsTxt = $this->render();

        // `/*?*x=` rather than `/*?x=`: the UI does not fix the query order, so
        // the parameter has to match in any position.
        self::assertStringContainsString('disallow:/*?*kategorie=', $robotsTxt);
        self::assertStringContainsString('disallow:/*?*alter=', $robotsTxt);
        self::assertStringContainsString('disallow:/*?*rollstuhl=', $robotsTxt);
    }

    public function testDisallowsThePresetParametersThatAreNotFormFields(): void
    {
        $robotsTxt = $this->render();

        // Expanded into von/bis and kostenlos/gebuehr before the form ever sees
        // them, so they are absent from getFilterNames().
        self::assertStringContainsString('disallow:/*?*wann=', $robotsTxt);
        self::assertStringContainsString('disallow:/*?*preis=', $robotsTxt);
        self::assertStringContainsString('disallow:/*?*liste=', $robotsTxt);
    }

    public function testLeavesPaginationCrawlable(): void
    {
        // Offer detail pages are not in the sitemap: the paginated list is the
        // only route by which a crawler reaches anything past page 1. Blocking
        // `page` would de-index the offers themselves.
        self::assertStringNotContainsString('page=', $this->render());
    }

    public function testKeepsTheDirectivesTheCoreListenerAlreadyAdded(): void
    {
        self::assertStringContainsString('disallow:/contao/', $this->render());
    }

    private function render(): string
    {
        $file = new File();

        // The state Contao's own RobotsTxtListener leaves behind for a root page
        // that defines no robots.txt of its own — this listener runs after it.
        $record = new Record();
        $record->getUserAgentDirectiveList()->add(new UserAgentDirective('*'));
        $record->getDirectiveList()->add(new Directive('Disallow', '/contao/'));
        $file->addRecord($record);

        // A stub, not a mock: the listener contributes the same directives to
        // every root page and never reads it.
        $event = new RobotsTxtEvent($file, Request::create('/robots.txt'), $this->createStub(PageModel::class));

        new OfferListRobotsTxtListener($this->filter())($event);

        return (string) $event->getFile();
    }

    private function filter(): OfferListFilter
    {
        // Only the keys are read; the tagged iterator is indexed by
        // FilterType::getName().
        return new OfferListFilter(new \ArrayIterator(array_fill_keys(
            ['alter', 'von', 'kategorie', 'gebuehr', 'varianten', 'bis', 'titel', 'kostenlos', 'sofortbuchung', 'rollstuhl', 'ohne_anmeldung'],
            new \stdClass(),
        )));
    }
}
