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

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\Tenant;
use Ferienpass\CoreBundle\Entity\TenantDomain;
use Ferienpass\CoreBundle\EventListener\HttpCache\CacheInvalidationListener;
use Ferienpass\CoreBundle\Tenant\Surface;
use Ferienpass\CoreBundle\Tenant\TenantContext;
use FOS\HttpCacheBundle\CacheManager;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouterInterface;

/**
 * A BAN has to reach the URL space each of a tenant's domains actually caches
 * under: `/admin/saisons` where the admin shares the public site's domain,
 * `/saisons` where it has one of its own.
 */
class CacheInvalidationListenerSurfaceTest extends TestCase
{
    public function testBansTheAdminUrlSpaceOfADedicatedAdminDomain(): void
    {
        $tenant = self::tenant([
            'ferienpass-nordhorn.de' => Surface::Frontend,
            'nordhorn.my-ferienpass.de' => Surface::Admin,
        ]);

        $bans = $this->persistAnEdition($tenant);

        self::assertSame([
            [
                'pattern' => '^(/[a-z]{2,3})?/saisons(/[a-zA-Z0-9_\-+]+)?(\?|$)',
                'hosts' => ['nordhorn.my-ferienpass.de'],
            ],
        ], $bans);
    }

    public function testBansUnderTheAdminSegmentWhileThePublicSiteServesTheAdmin(): void
    {
        $tenant = self::tenant(['ferienpass-nordhorn.de' => Surface::Frontend]);

        $bans = $this->persistAnEdition($tenant);

        self::assertSame([
            [
                'pattern' => '^(/[a-z]{2,3})?/admin/saisons(/[a-zA-Z0-9_\-+]+)?(\?|$)',
                'hosts' => ['ferienpass-nordhorn.de'],
            ],
        ], $bans);
    }

    /**
     * With no tenant active — a single-tenant build — nothing changes: the
     * request host, the pattern as written.
     */
    public function testFallsBackToTheRequestHostWithoutATenant(): void
    {
        $bans = $this->persistAnEdition(null);

        self::assertSame([
            [
                'pattern' => '^(/[a-z]{2,3})?/admin/saisons(/[a-zA-Z0-9_\-+]+)?(\?|$)',
                'hosts' => ['ferienpass-nordhorn.de'],
            ],
        ], $bans);
    }

    /**
     * @return list<array{pattern: string, hosts: ?array}>
     */
    private function persistAnEdition(?Tenant $tenant): array
    {
        $bans = [];

        $manager = $this->createMock(CacheManager::class);
        $manager->method('invalidateTags')->willReturnSelf();
        $manager->method('invalidatePath')->willReturnSelf();
        $manager->method('invalidateRegex')->willReturnCallback(
            static function (string $pattern, ?string $contentType = null, $hosts = null) use (&$bans, $manager) {
                $bans[] = ['pattern' => $pattern, 'hosts' => $hosts];

                return $manager;
            }
        );

        $context = new RequestContext();
        $context->setHost('ferienpass-nordhorn.de');
        $context->setScheme('https');

        $router = $this->createMock(RouterInterface::class);
        $router->method('getContext')->willReturn($context);

        $tenantContext = $this->createMock(TenantContext::class);
        $tenantContext->method('isEnabled')->willReturn(null !== $tenant);
        $tenantContext->method('getTenant')->willReturn($tenant);

        $listener = new CacheInvalidationListener($manager, $router, 'test-app', $tenantContext);
        $listener->postPersist(new PostPersistEventArgs(new Edition(), $this->createMock(EntityManagerInterface::class)));

        return $bans;
    }

    /**
     * @param array<string, Surface> $domains
     */
    private static function tenant(array $domains): Tenant
    {
        $tenant = new Tenant();

        foreach ($domains as $host => $surface) {
            $tenant->addDomain(new TenantDomain($tenant, $host, $surface));
        }

        return $tenant;
    }
}
