<?php

namespace App\Tests\Security;

use App\Entity\User;
use App\Repository\UserRepository;
use App\Security\KeycloakAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\OAuth2ClientInterface;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class KeycloakAuthenticatorTest extends TestCase
{
    private ClientRegistry $clientRegistry;
    private EntityManagerInterface $em;
    private UserRepository $userRepository;
    private RouterInterface $router;
    private KeycloakAuthenticator $authenticator;

    protected function setUp(): void
    {
        $this->clientRegistry = $this->createStub(ClientRegistry::class);
        $this->em = $this->createStub(EntityManagerInterface::class);
        $this->userRepository = $this->createStub(UserRepository::class);
        $this->router = $this->createStub(RouterInterface::class);

        $this->authenticator = new KeycloakAuthenticator(
            $this->clientRegistry,
            $this->em,
            $this->userRepository,
            $this->router,
        );
    }

    public function testSupports(): void
    {
        $request = new Request();
        $request->attributes->set('_route', 'connect_keycloak_check');
        $this->assertTrue($this->authenticator->supports($request));

        $request->attributes->set('_route', 'other_route');
        $this->assertFalse($this->authenticator->supports($request));
    }

    public function testAuthenticate(): void
    {
        $request = new Request();
        $client = $this->createStub(OAuth2ClientInterface::class);

        $accessToken = new AccessToken(['access_token' => 'fake-token']);

        $this->clientRegistry->method('getClient')->willReturn($client);
        $client->method('getAccessToken')->willReturn($accessToken);

        $passport = $this->authenticator->authenticate($request);
        $this->assertInstanceOf(SelfValidatingPassport::class, $passport);

        $userBadge = $passport->getBadge(UserBadge::class);
        $this->assertNotNull($userBadge);

        $keycloakUser = $this->createStub(ResourceOwnerInterface::class);
        $keycloakUser->method('toArray')->willReturn([
            'sub' => '123',
            'email' => 'test@e-cosplay.fr',
            'given_name' => 'John',
            'family_name' => 'Doe',
            'groups' => ['superadmin'],
        ]);

        $client->method('fetchUserFromToken')->willReturn($keycloakUser);
        $this->userRepository->method('findOneBy')->willReturn(null);

        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('persist');
        $em->expects($this->once())->method('flush');

        // Recreate authenticator with mock em for expectations
        $authenticator = new KeycloakAuthenticator(
            $this->clientRegistry,
            $em,
            $this->userRepository,
            $this->router,
        );
        $passport = $authenticator->authenticate($request);
        $userBadge = $passport->getBadge(UserBadge::class);

        $user = $userBadge->getUser();
        $this->assertInstanceOf(User::class, $user);
        $this->assertEquals('123', $user->getKeycloakId());
        $this->assertEquals('test@e-cosplay.fr', $user->getEmail());
        $this->assertContains('ROLE_ROOT', $user->getRoles());
    }

    public function testAuthenticateExistingUserByEmail(): void
    {
        $request = new Request();
        $client = $this->createStub(OAuth2ClientInterface::class);
        $accessToken = new AccessToken(['access_token' => 'fake-token']);
        $this->clientRegistry->method('getClient')->willReturn($client);
        $client->method('getAccessToken')->willReturn($accessToken);

        $passport = $this->authenticator->authenticate($request);
        $userBadge = $passport->getBadge(UserBadge::class);

        $keycloakUser = $this->createStub(ResourceOwnerInterface::class);
        $keycloakUser->method('toArray')->willReturn([
            'sub' => '123',
            'email' => 'existing@e-cosplay.fr',
            'groups' => ['gp_member'],
        ]);
        $client->method('fetchUserFromToken')->willReturn($keycloakUser);

        $existingUser = new User();
        $this->userRepository->method('findOneBy')->willReturnCallback(function ($criteria) use ($existingUser) {
            if (isset($criteria['email']) && 'existing@e-cosplay.fr' === $criteria['email']) {
                return $existingUser;
            }

            return null;
        });

        $user = $userBadge->getUser();
        $this->assertSame($existingUser, $user);
        $this->assertEquals('123', $user->getKeycloakId());
        $this->assertContains('ROLE_EMPLOYE', $user->getRoles());
    }

    public function testOnAuthenticationSuccess(): void
    {
        $request = new Request();
        $token = $this->createStub(TokenInterface::class);
        $this->router->method('generate')->willReturn('/home');

        $response = $this->authenticator->onAuthenticationSuccess($request, $token, 'main');
        $this->assertInstanceOf(RedirectResponse::class, $response);
        $this->assertEquals('/home', $response->getTargetUrl());
    }

    public function testOnAuthenticationFailure(): void
    {
        $request = $this->createStub(Request::class);

        $flashBag = $this->createMock(FlashBagInterface::class);
        $flashBag->expects($this->once())->method('add')->with('error', $this->anything());

        $session = $this->createStub(FlashBagAwareSessionInterface::class);
        $session->method('getFlashBag')->willReturn($flashBag);

        $request->method('getSession')->willReturn($session);
        $this->router->method('generate')->willReturn('/home');

        $response = $this->authenticator->onAuthenticationFailure($request, new AuthenticationException());
        $this->assertInstanceOf(RedirectResponse::class, $response);
    }

    public function testStart(): void
    {
        $request = new Request();
        $this->router->method('generate')->willReturn('/home');

        $response = $this->authenticator->start($request);
        $this->assertInstanceOf(RedirectResponse::class, $response);
        $this->assertEquals('/home', $response->getTargetUrl());
    }

    public function testAuthenticateSuperAdminAssoGroup(): void
    {
        $request = new Request();
        $client = $this->createStub(OAuth2ClientInterface::class);
        $accessToken = new AccessToken(['access_token' => 'fake-token']);
        $this->clientRegistry->method('getClient')->willReturn($client);
        $client->method('getAccessToken')->willReturn($accessToken);

        $keycloakUser = $this->createStub(ResourceOwnerInterface::class);
        $keycloakUser->method('toArray')->willReturn([
            'sub' => '456',
            'email' => 'asso@e-cosplay.fr',
            'given_name' => 'Asso',
            'family_name' => 'Admin',
            'groups' => ['super_admin_asso'],
        ]);
        $client->method('fetchUserFromToken')->willReturn($keycloakUser);
        $this->userRepository->method('findOneBy')->willReturn(null);

        $em = $this->createStub(EntityManagerInterface::class);
        $authenticator = new KeycloakAuthenticator(
            $this->clientRegistry,
            $em,
            $this->userRepository,
            $this->router,
        );

        $passport = $authenticator->authenticate($request);
        $userBadge = $passport->getBadge(UserBadge::class);
        $user = $userBadge->getUser();

        $this->assertContains('ROLE_ROOT', $user->getRoles());
    }

    public function testAuthenticateUnknownGroupGetsRoleUser(): void
    {
        $request = new Request();
        $client = $this->createStub(OAuth2ClientInterface::class);
        $accessToken = new AccessToken(['access_token' => 'fake-token']);
        $this->clientRegistry->method('getClient')->willReturn($client);
        $client->method('getAccessToken')->willReturn($accessToken);

        $keycloakUser = $this->createStub(ResourceOwnerInterface::class);
        $keycloakUser->method('toArray')->willReturn([
            'sub' => '789',
            'email' => 'user@e-cosplay.fr',
            'given_name' => 'Regular',
            'family_name' => 'User',
            'groups' => ['some_other_group'],
        ]);
        $client->method('fetchUserFromToken')->willReturn($keycloakUser);
        $this->userRepository->method('findOneBy')->willReturn(null);

        $passport = $this->authenticator->authenticate($request);
        $userBadge = $passport->getBadge(UserBadge::class);
        $user = $userBadge->getUser();

        $this->assertContains('ROLE_USER', $user->getRoles());
    }

    public function testAuthenticateNonEcosplayEmailThrows(): void
    {
        $request = new Request();
        $client = $this->createStub(OAuth2ClientInterface::class);
        $accessToken = new AccessToken(['access_token' => 'fake-token']);
        $this->clientRegistry->method('getClient')->willReturn($client);
        $client->method('getAccessToken')->willReturn($accessToken);

        $keycloakUser = $this->createStub(ResourceOwnerInterface::class);
        $keycloakUser->method('toArray')->willReturn([
            'sub' => '000',
            'email' => 'hacker@example.com',
            'groups' => [],
        ]);
        $client->method('fetchUserFromToken')->willReturn($keycloakUser);

        $passport = $this->authenticator->authenticate($request);
        $userBadge = $passport->getBadge(UserBadge::class);

        $this->expectException(AuthenticationException::class);
        $userBadge->getUser();
    }

    public function testOnAuthenticationFailureWithoutFlashBagSession(): void
    {
        $request = new Request();
        // Session that does NOT implement FlashBagAwareSessionInterface
        $session = $this->createStub(\Symfony\Component\HttpFoundation\Session\SessionInterface::class);
        $request->setSession($session);

        $this->router->method('generate')->willReturn('/home');

        $response = $this->authenticator->onAuthenticationFailure($request, new AuthenticationException('test'));
        $this->assertInstanceOf(RedirectResponse::class, $response);
    }
}
