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

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;

final class SwitchUserExitGracefulListener
{
    /**
     * Must run BEFORE Symfony's security
     * {@see \Symfony\Component\Security\Http\Firewall\ExceptionListener}
     * (priority 1): for an "_exit" with no original token that listener would
     * convert the AuthenticationCredentialsNotFoundException into a login
     * redirect (or let it bubble to a 500). We instead strip the stale
     * `_switch_user=_exit` param and redirect back to the same page. The guard
     * below is narrow (that exact exception + query value), so running first is
     * safe and doesn't interfere with real authentication flows.
     *
     * Running first is necessary but not sufficient: the security
     * ExceptionListener does not check whether a response was already set, so it
     * would still handle the AuthenticationException and clobber our redirect —
     * on a firewall with an entry point (the admin firewall's form_login) with a
     * redirect to the login page, on one without by re-throwing an unauthorized
     * error. We therefore stop propagation once the redirect is set: this
     * exception is fully resolved and no lower-priority listener should touch it.
     */
    #[AsEventListener(priority: 4)]
    public function __invoke(ExceptionEvent $event): void
    {
        $throwable = $event->getThrowable();
        if (!$throwable instanceof AuthenticationCredentialsNotFoundException) {
            return;
        }

        $request = $event->getRequest();
        if ('_exit' !== $request->query->get('_switch_user')) {
            return;
        }

        $event->setResponse(new RedirectResponse($this->buildUrlWithoutSwitchUser($request)));
        $event->stopPropagation();
    }

    /**
     * Rebuild the request URL without the `_switch_user` query parameter.
     * Preserves the path, every other query value, and the fragment isn't
     * relevant on a server-side redirect target.
     */
    private function buildUrlWithoutSwitchUser(Request $request): string
    {
        $query = $request->query->all();
        unset($query['_switch_user']);

        $path = $request->getPathInfo();
        if ([] === $query) {
            return $path;
        }

        return $path.'?'.http_build_query($query);
    }
}
