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

use Ferienpass\AdminBundle\Tenant\TenantSwitcher;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Tenant\SurfaceContext;
use Ferienpass\CoreBundle\Tenant\TenantContext;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Applies a global admin's chosen tenant to the admin area.
 *
 * Priority 6 puts this *after* the firewall (8) on purpose. The user provider
 * refreshes the signed-in account from the database on every request, and that
 * lookup has to happen in the tenant they signed in to — switch before it and a
 * global admin visiting another tenant would refresh into an account that does
 * not exist there and be logged straight out.
 *
 * It is also scoped to /admin: the public site is what a municipality's
 * visitors see, and it must keep answering as the tenant its host names, no
 * matter who is signed in.
 *
 * On the platform console this is not a convenience but the only thing that
 * resolves a tenant at all: that domain names none, so an unswitched session
 * falls back to the signed-in admin's own municipality. Without it the whole
 * admin would run with the filter off, i.e. across every tenant at once.
 */
#[AsEventListener(event: KernelEvents::REQUEST, priority: 6)]
class TenantSwitchListener
{
    private const ADMIN_PATH = '#^(/[a-z]{2,3})?/admin(/|$)#';

    public function __construct(
        private readonly TenantSwitcher $tenantSwitcher,
        private readonly TenantContext $tenantContext,
        private readonly SurfaceContext $surfaces,
        private readonly Security $security,
    ) {
    }

    public function __invoke(RequestEvent $event): void
    {
        if (!$this->tenantContext->isEnabled() || !$event->isMainRequest()) {
            return;
        }

        if (!preg_match(self::ADMIN_PATH, $event->getRequest()->getPathInfo())) {
            return;
        }

        // isAvailable() short-circuits on the security token, so an anonymous
        // visitor never causes a session to be started here.
        $tenant = $this->tenantSwitcher->selected();

        if (null === $tenant && ($this->surfaces->current()?->isPlatform ?? false)) {
            $user = $this->security->getUser();
            $tenant = $user instanceof User ? $user->getTenant() : null;
        }

        if (null === $tenant) {
            return;
        }

        $this->tenantContext->setTenant($tenant);
    }
}
