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

use Ferienpass\AdminBundle\Security\AuthenticationSuccessHandler;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Tenant\SurfaceContext;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;

final class AuthenticationSuccessHandlerTest extends TestCase
{
    public function testReturnsNullWhenUserIsNotFerienpassUser(): void
    {
        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->expects(self::never())->method('generate');

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($this->createMock(UserInterface::class));

        $handler = new AuthenticationSuccessHandler($urlGenerator, $this->surfaces());
        $result = $handler->onAuthenticationSuccess(new Request(), $token);

        self::assertNull($result);
    }

    public function testReturnsRedirectForFerienpassUser(): void
    {
        $user = new User();
        $user->setLocale('de');

        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->expects(self::once())
            ->method('generate')
            ->with('admin_index', ['_locale' => 'de'])
            ->willReturn('/de/admin');

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $handler = new AuthenticationSuccessHandler($urlGenerator, $this->surfaces());
        $result = $handler->onAuthenticationSuccess(new Request(), $token);

        self::assertInstanceOf(RedirectResponse::class, $result);
        self::assertSame('/de/admin', $result->getTargetUrl());
    }

    public function testUserWithCustomLocaleGeneratesCorrectUrl(): void
    {
        $user = new User();
        $user->setLocale('fr');

        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->expects(self::once())
            ->method('generate')
            ->with('admin_index', ['_locale' => 'fr'])
            ->willReturn('/fr/admin');

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $handler = new AuthenticationSuccessHandler($urlGenerator, $this->surfaces());
        $result = $handler->onAuthenticationSuccess(new Request(), $token);

        self::assertInstanceOf(RedirectResponse::class, $result);
        self::assertSame('/fr/admin', $result->getTargetUrl());
    }

    public function testHonoursADeepLinkIntoTheAdmin(): void
    {
        self::assertSame('/admin/angebote', $this->redirectFor('/admin/angebote')?->getTargetUrl());
        self::assertSame('/nl/admin/angebote', $this->redirectFor('/nl/admin/angebote')?->getTargetUrl());
    }

    public function testRefusesATargetOutsideTheAdmin(): void
    {
        self::assertSame('/de/admin', $this->redirectFor('https://evil.example.com/')?->getTargetUrl());
        self::assertSame('/de/admin', $this->redirectFor('//evil.example.com/')?->getTargetUrl());
        self::assertSame('/de/admin', $this->redirectFor('/angebote')?->getTargetUrl());
    }

    /**
     * On a domain that serves the admin at its root, every path on it is
     * inside the admin — there is no segment left to recognise it by.
     */
    public function testHonoursADeepLinkOnAnAdminDomain(): void
    {
        self::assertSame('/angebote', $this->redirectFor('/angebote', adminAtRoot: true)?->getTargetUrl());
        self::assertSame('/de/admin', $this->redirectFor('//evil.example.com/', adminAtRoot: true)?->getTargetUrl());
    }

    private function redirectFor(string $target, bool $adminAtRoot = false): ?RedirectResponse
    {
        $user = new User();
        $user->setLocale('de');

        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->method('generate')->willReturn('/de/admin');

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $request = new Request(request: ['_target_path' => base64_encode($target)]);

        $result = new AuthenticationSuccessHandler($urlGenerator, $this->surfaces($adminAtRoot))
            ->onAuthenticationSuccess($request, $token);

        self::assertInstanceOf(RedirectResponse::class, $result);

        return $result;
    }

    private function surfaces(bool $adminAtRoot = false): SurfaceContext
    {
        $surfaces = $this->createMock(SurfaceContext::class);
        $surfaces->method('adminAtRoot')->willReturn($adminAtRoot);

        return $surfaces;
    }
}
