<?php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class KeycloakAuthenticator extends OAuth2Authenticator
{
    public function __construct(
        private ClientRegistry $clientRegistry,
        private EntityManagerInterface $em,
        private RouterInterface $router,
    ) {
    }

    public function supports(Request $request): ?bool
    {
        return 'app_oauth_keycloak_check' === $request->attributes->get('_route');
    }

    private const GROUP_ROLE_MAP = [
        '/superadmin' => 'ROLE_ROOT',
    ];

    public function authenticate(Request $request): Passport
    {
        $client = $this->clientRegistry->getClient('keycloak');
        $accessToken = $this->fetchAccessToken($client);

        return new SelfValidatingPassport(
            new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
                $keycloakUser = $client->fetchUserFromToken($accessToken);
                $data = $keycloakUser->toArray();
                $keycloakId = $keycloakUser->getId();
                $email = $data['email'] ?? '';
                $roles = $this->mapGroupsToRoles($data['groups'] ?? []);

                $user = $this->em->getRepository(User::class)->findOneBy(['keycloakId' => $keycloakId]);

                if ($user) {
                    $user->setEmail($email);
                    $user->setFirstName($data['given_name'] ?? $user->getFirstName());
                    $user->setLastName($data['family_name'] ?? $user->getLastName());
                    $user->setRoles($roles);
                    $this->em->flush();

                    return $user;
                }

                $existingUser = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);

                if ($existingUser) {
                    $existingUser->setKeycloakId($keycloakId);
                    $existingUser->setRoles($roles);
                    $existingUser->setIsVerified(true);
                    $existingUser->setIsApproved(true);
                    $existingUser->setOffer('custom');
                    $existingUser->setEmailVerifiedAt($existingUser->getEmailVerifiedAt() ?? new \DateTimeImmutable());
                    $this->em->flush();

                    return $existingUser;
                }

                $newUser = new User();
                $newUser->setKeycloakId($keycloakId);
                $newUser->setEmail($email);
                $newUser->setFirstName($data['given_name'] ?? '');
                $newUser->setLastName($data['family_name'] ?? '');
                $newUser->setPassword('');
                $newUser->setRoles($roles);
                $newUser->setIsVerified(true);
                $newUser->setIsApproved(true);
                $newUser->setOffer('custom');
                $newUser->setEmailVerifiedAt(new \DateTimeImmutable());
                $newUser->setIsBilling(false);
                $newUser->setBillingAmount(0);
                $newUser->setBillingState('good');

                $this->em->persist($newUser);
                $this->em->flush();

                return $newUser;
            }),
        );
    }

    /**
     * @param list<string> $groups
     *
     * @return list<string>
     */
    private function mapGroupsToRoles(array $groups): array
    {
        $roles = [];

        foreach ($groups as $group) {
            if (isset(self::GROUP_ROLE_MAP[$group])) {
                $roles[] = self::GROUP_ROLE_MAP[$group];
            }
        }

        return $roles;
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return new RedirectResponse($this->router->generate('app_account'));
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        /** @var \Symfony\Component\HttpFoundation\Session\Session $session */
        $session = $request->getSession();
        $session->getFlashBag()->add('error', 'Echec de la connexion SSO E-Cosplay.');

        return new RedirectResponse($this->router->generate('app_login'));
    }
}
