<?php

namespace App\Security;

use App\Entity\User;
use App\Security\Hasher\PasswordEncoder;
use App\Service\SQLLoginService;
use App\SQLLogin\Exception\InvalidSQLPasswordException;
use PDOException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
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 SQLLoginUserAuthenticator extends AbstractAuthenticator
{
    public const LOGIN_ROUTE = 'app_login';
    public const ERROR_LOGIN = 'error_login';
    public const ERROR_PASSWORD = 'error_password';
    public const ERROR_SQL_LOGIN = 'error_sql_login';

    protected string $baseUrl;
    private SQLLoginService $sqlLoginService;
    private PasswordEncoder $passwordHasher;

    public function __construct(string $baseUrl, SQLLoginService $sqlLoginService, PasswordEncoder $passwordHasher)
    {
        $this->baseUrl = $baseUrl;
        $this->sqlLoginService = $sqlLoginService;
        $this->passwordHasher = $passwordHasher;
    }

    /**
     * Called on every request to decide if this authenticator should be
     * used for the request. Returning `false` will cause this authenticator
     * to be skipped.
     */
    public function supports(Request $request): bool
    {
        return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST');
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
    {
        return new RedirectResponse($this->baseUrl.'/connect/login-accept');
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);

        return new RedirectResponse($this->baseUrl.'/connect/login-accept');
    }

    public function authenticate(Request $request): Passport
    {
        $form = $request->request->get('login');
        $login = $form['login'];
        $plaintextPassword = $form['password'];
        $rememberMe = isset($form['_remember_me']) ? true : false;
        try {
            // requête préparée
            list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login);
        } catch (PDOException $e) {
            $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
            throw new AuthenticationException();
        }
        if ($remoteHashedPassword) {
            try {
                // Comparaison remote hash et hash du input password + salt
                $this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt);
                $attributes = $this->sqlLoginService->fetchDatas($login);
                $user = new User($login, $remoteHashedPassword, $attributes, $rememberMe);

                $loader = function (string $userIdentifier) use ($user) {
                    return $user->getLogin() == $userIdentifier ? $user : null;
                };
                $passport = new SelfValidatingPassport(new UserBadge($login, $loader));
                if ($rememberMe) {
                    $passport->addBadge(new RememberMeBadge());
                }
                $passport->setAttribute('attributes', $user->getAttributes());

                return $passport;
            } catch (InvalidSQLPasswordException $e) {
                $request->getSession()->set(self::ERROR_PASSWORD, true);
                throw new AuthenticationException();
            } catch (PDOException $e) {
                $request->getSession()->set(self::ERROR_SQL_LOGIN, true);
                throw new AuthenticationException();
            }
        }
        $request->getSession()->set(self::ERROR_LOGIN, true);
        throw new AuthenticationException();
    }
}