<?php declare(strict_types=1);

/*
 * This file is part of the Rapsys UserBundle package.
 *
 * (c) Raphaël Gertz <symfony@rapsys.eu>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Rapsys\UserBundle\Listener;

use Rapsys\UserBundle\RapsysUserBundle;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\InvalidParameterException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;

/**
 * {@inheritdoc}
 */
class LogoutListener implements EventSubscriberInterface {
	/**
	 * Config array
	 */
	protected $config;

	/**
	 * {@inheritdoc}
	 *
	 * @xxx Second argument will be replaced by security.firewalls.main.logout.target
	 * @see vendor/symfony/security-bundle/Resources/config/security_listeners.php +79
	 */
	public function __construct(protected ContainerInterface $container, protected string $targetUrl, protected RouterInterface $router) {
		//Set config
		$this->config = $container->getParameter(RapsysUserBundle::getAlias());
	}

	/**
	 * {@inheritdoc}
	 */
	public function onLogout(LogoutEvent $event): void {
		//Get request
		$request = $event->getRequest();

		//Retrieve logout route
		$logout = $request->attributes->get('_route');

		//Extract and process referer
		if (($referer = $request->headers->get('referer'))) {
			//Create referer request instance
			$req = Request::create($referer);

			//Get referer path
			$path = $req->getPathInfo();

			//Get referer query string
			$query = $req->getQueryString();

			//Remove script name
			$path = str_replace($request->getScriptName(), '', $path);

			//Try with referer path
			try {
				//Save old context
				$oldContext = $this->router->getContext();

				//Force clean context
				//XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
				//XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
				$this->router->setContext(new RequestContext());

				//Retrieve route matching path
				$route = $this->router->match($path);

				//Reset context
				$this->router->setContext($oldContext);

				//Clear old context
				unset($oldContext);

				//Without logout route name
				if (($name = $route['_route']) != $logout) {
					//Remove route and controller from route defaults
					unset($route['_route'], $route['_controller'], $route['_canonical_route']);

					//Generate url
					$url = $this->router->generate($name, $route);

					//Set event response
					$event->setResponse(new RedirectResponse($url, 302));

					//Return
					return;
				//With logout route name
				} else {
					//Unset referer and route
					unset($referer, $route);
				}
			//No route matched
			} catch (ResourceNotFoundException $e) {
				//Unset referer and route
				unset($referer, $route);
			}
		}

		//With index route from config
		if (!empty($name = $this->config['route']['index']['name']) && is_array($context = $this->config['route']['index']['context'])) {
			//Without logout route name
			if ($name != $logout) {
				//Try index route
				try {
					//Generate url
					$url = $this->router->generate($name, $context);

					//Set event response
					$event->setResponse(new RedirectResponse($url, 302));

					//Return
					return;
				//No route matched
				} catch (ResourceNotFoundException $e) {
					//Unset name and context
					unset($name, $context);
				}
			//With logout route name
			} else {
				//Unset name and context
				unset($name, $context);
			}
		}

		//Try target url
		try {
			//Save old context
			$oldContext = $this->router->getContext();

			//Force clean context
			//XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
			//XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
			$this->router->setContext(new RequestContext());

			//With logout target path
			if ($this->targetUrl[0] == '/') {
				//Retrieve route matching target url
				$route = $this->router->match($this->targetUrl);

				//Reset context
				$this->router->setContext($oldContext);

				//Clear old context
				unset($oldContext);

				//Without logout route name
				if (($name = $route['_route']) != $logout) {
					//Remove route and controller from route defaults
					unset($route['_route'], $route['_controller'], $route['_canonical_route']);

					//Generate url
					$url = $this->router->generate($name, $route);

					//Set event response
					$event->setResponse(new RedirectResponse($url, 302));

					//Return
					return;
				//With logout route name
				} else {
					//Unset name and route
					unset($name, $route);
				}
			//With route name
			} else {
				//Retrieve route matching path
				$url = $this->router->generate($this->targetUrl);

				//Set event response
				$event->setResponse(new RedirectResponse($url, 302));

				//Return
				return;
			}
		//Get first route from route collection if / path was not matched
		} catch (ResourceNotFoundException|RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
			//Unset name and route
			unset($name, $route);
		}

		//Set event response
		$event->setResponse(new RedirectResponse('/', 302));
	}

	/**
	 * {@inheritdoc}
	 */
	public static function getSubscribedEvents(): array {
		return [
			LogoutEvent::class => ['onLogout', 64],
		];
	}
}
