<?php
/*
 * This file is part of Phraseanet
 *
 * (c) 2005-2016 Alchemy
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Alchemy\Phrasea\Controller\Root;

use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Authentication\AccountCreator;
use Alchemy\Phrasea\Authentication\Context;
use Alchemy\Phrasea\Authentication\Exception\AccountLockedException;
use Alchemy\Phrasea\Authentication\Exception\AuthenticationException;
use Alchemy\Phrasea\Authentication\Exception\NotAuthenticatedException;
use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException;
use Alchemy\Phrasea\Authentication\Phrasea\PasswordAuthenticationInterface;
use Alchemy\Phrasea\Authentication\Phrasea\PasswordEncoder;
use Alchemy\Phrasea\Authentication\Provider\ProviderInterface;
use Alchemy\Phrasea\Authentication\ProvidersCollection;
use Alchemy\Phrasea\Authentication\RecoveryService;
use Alchemy\Phrasea\Authentication\RegistrationService;
use Alchemy\Phrasea\Authentication\SuggestionFinder;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Core\Configuration\ConfigurationInterface;
use Alchemy\Phrasea\Core\Configuration\RegistrationManager;
use Alchemy\Phrasea\Core\Event\LogoutEvent;
use Alchemy\Phrasea\Core\Event\PostAuthenticate;
use Alchemy\Phrasea\Core\Event\PreAuthenticate;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Exception\FormProcessingException;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Form\Login\PhraseaAuthenticationForm;
use Alchemy\Phrasea\Form\Login\PhraseaForgotPasswordForm;
use Alchemy\Phrasea\Form\Login\PhraseaRecoverPasswordForm;
use Alchemy\Phrasea\Form\Login\PhraseaRegisterForm;
use Alchemy\Phrasea\Helper\User\Manage;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\UsrAuthProvider;
use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
use Alchemy\Phrasea\Model\Repositories\BasketParticipantRepository;
use Alchemy\Phrasea\Model\Repositories\FeedItemRepository;
use Alchemy\Phrasea\Model\Repositories\FeedRepository;
use Alchemy\Phrasea\Model\Repositories\TokenRepository;
use Alchemy\Phrasea\Model\Repositories\UserRepository;
use Alchemy\Phrasea\Model\Repositories\UsrAuthProviderRepository;
use Alchemy\Phrasea\Notification\Mail\MailSuccessEmailConfirmationRegistered;
use Alchemy\Phrasea\Notification\Mail\MailSuccessEmailConfirmationUnregistered;
use Alchemy\Phrasea\Notification\Receiver;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use RandomLib\Generator;
use ReCaptcha\ReCaptcha;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class LoginController extends Controller
{
    use DispatcherAware;
    use EntityManagerAware;
    use NotifierAware;

    /**
     * @param Request $request
     * @return array
     */
    public function getDefaultTemplateVariables(Request $request)
    {
        $items = [];

        foreach ($this->getFeedItemRepository()->loadLatest($this->app, 20) as $item) {
            $record = $item->getRecord($this->app);
            $preview = $record->get_subdef('preview');
            $permalink = $preview->get_permalink();

            $items[] = [
                'record' => $record,
                'preview' => $preview,
                'permalink' => $permalink
            ];
        }

        $conf = $this->getConf();
        $browser = $this->getBrowser();

        return [
            'last_publication_items' => $items,
            'instance_title' => $conf->get(['registry', 'general', 'title']),
            'has_terms_of_use' => $this->app->hasTermsOfUse(),
            'meta_description' =>  $conf->get(['registry', 'general', 'description']),
            'meta_keywords' => $conf->get(['registry', 'general', 'keywords']),
            'browser_name' => $browser->getBrowser(),
            'browser_version' => $browser->getVersion(),
            'available_language' => $this->app['locales.available'],
            'locale' => $this->app['locale'],
            'current_url' => $request->getUri(),
            'flash_types' => $this->app->getAvailableFlashTypes(),
            'recaptcha_display' => $this->app->isCaptchaRequired(),
            'recaptcha_enabled' => ($conf->get(['registry', 'webservices', 'captcha-provider']) != 'none') ? true : false,
            'unlock_usr_id' => $this->app->getUnlockAccountData(),
            'guest_allowed' => $this->app->isGuestAllowed(),
            'register_enable' => $this->getRegistrationManager()->isRegistrationEnabled(),
            'display_layout' => $conf->get(['registry', 'general', 'home-presentation-mode']),
            'authentication_providers' => $this->app['authentication.providers'],
            'registration_fields' => $this->getRegistrationFields(),
            'registration_optional_fields' => $this->getOptionalRegistrationFields(),
        ];
    }

    public function getRegistrationFieldsAction()
    {
        return $this->app->json($this->getRegistrationFields());
    }

    public function getCgusAction(Request $request)
    {
        return $this->render('login/cgus.html.twig', array_merge(
            ['cgus' => \databox_cgu::getHome($this->app)],
            $this->getDefaultTemplateVariables($request)
        ));
    }

    public function getLanguage()
    {
        $response =  $this->app->json([
            'validation_blank'          => $this->app->trans('Please provide a value.'),
            'validation_choice_min'     => $this->app->trans('Please select at least %s choice.', ['%s' => 1]),
            'validation_email'          => $this->app->trans('Please provide a valid email address.'),
            'validation_ip'             => $this->app->trans('Please provide a valid IP address.'),
            'validation_length_min'     => $this->app->trans('Please provide a longer value. It should have %s character or more.', ['%s' => 5]),
            'password_match'            => $this->app->trans('Please provide the same passwords.'),
            'email_match'               => $this->app->trans('Please provide the same emails.'),
            'accept_tou'                => $this->app->trans('Please accept the terms of use to register.'),
            'no_collection_selected'    => $this->app->trans('No collection selected'),
            'one_collection_selected'   => $this->app->trans('%d collection selected'),
            'collections_selected'      => $this->app->trans('%d collections selected'),
            'all_collections'           => $this->app->trans('Select all collections'),
            // password strength
            'weak'                      => $this->app->trans('Weak'),
            'ordinary'                  => $this->app->trans('Ordinary'),
            'good'                      => $this->app->trans('Good'),
            'great'                     => $this->app->trans('Great'),
        ]);

        $response->setExpires(new DateTime('+1 day'));

        return $response;
    }

    public function doRegistration(Request $request)
    {
        if (!$this->getRegistrationManager()->isRegistrationEnabled()) {
            $this->app->abort(404, 'Registration is disabled');
        }

        $form = $this->app->form(new PhraseaRegisterForm(
            $this->app, $this->getOptionalRegistrationFields(), $this->getRegistrationFields()
        ));

        if ('POST' === $request->getMethod()) {
            $requestData = $request->request->all();

            // Remove geocompleter field for validation this field is added client side
            // with jquery geonames plugin
            if (isset($requestData['geonameid']) && isset($requestData['geonameid-completer'])) {
                unset($requestData['geonameid-completer']);
            }

            // Remove multiselect field for validation this field is added client side
            // with bootstrap multiselect plugin
            if (isset($requestData['multiselect'])) {
                unset($requestData['multiselect']);
            }

            $form->submit($requestData);
            $data = $form->getData();

            $provider = null;

            if(
                (isset($requestData['g-recaptcha-response']) && $requestData['g-recaptcha-response'] == "") ||
                (isset($requestData['h-captcha-response']) && $requestData['h-captcha-response'] == "")
            ) {
                $this->app->addFlash('error', $this->app->trans('Please fill captcha'));

                $dateError = new FormError("");
                $form->get('captcha')->addError($dateError);
            }

            if ($data['provider-id']) {
                try {
                    $provider = $this->findProvider($data['provider-id']);
                } catch (NotFoundHttpException $e) {
                    $this->app->addFlash('error', $this->app->trans('You tried to register with an unknown provider'));

                    return $this->app->redirectPath('login_register');
                }

                try {
                    $token = $provider->getToken();
                } catch (NotAuthenticatedException $e) {
                    $this->app->addFlash('error', $this->app->trans('You tried to register with an unknown provider'));

                    return $this->app->redirectPath('login_register');
                }

                $userAuthProvider = $this->getUserAuthProviderRepository()
                    ->findWithProviderAndId($token->getProvider()->getId(), $token->getId());

                if (null !== $userAuthProvider) {
                    $this->postAuthProcess($request, $userAuthProvider->getUser());

                    if (null !== $redirect = $request->query->get('redirect')) {
                        $redirection = '../' . $redirect;
                    } else {
                        $redirection = $this->app->path('prod');
                    }

                    return $this->app->redirect($redirection);
                }
            }

            try {
                if ($form->isValid()) {
                    $registrationService = $this->getRegistrationService();
                    $providerId = isset($data['provider-id']) ? $data['provider-id'] : null;
                    $selectedCollections = isset($data['collections']) ? $data['collections'] : null;
                    $data['email'] = trim($data['email']);

                    $user = $registrationService->registerUser($data, $selectedCollections, $providerId);

                    try {
                        $this->sendAccountUnlockEmail($user, $request);
                        $this->app->addFlash('info', $this->app->trans('login::notification: demande de confirmation par mail envoyee'));
                    } catch (InvalidArgumentException $e) {
                        // todo, log this failure
                        $this->app->addFlash('error', $this->app->trans('Unable to send your account unlock email.'));
                    }

                    return $this->app->redirectPath('homepage');
                }
            } catch (FormProcessingException $e) {
                $this->app->addFlash('error', $e->getMessage());
            }
        } elseif (null !== $request->query->get('providerId')) {
            $provider = $this->findProvider($request->query->get('providerId'));
            $identity = $provider->getIdentity();

            $form->setData(array_filter([
                'email'       => $identity->getEmail(),
                'firstname'   => $identity->getFirstname(),
                'lastname'    => $identity->getLastname(),
                'company'     => $identity->getCompany(),
                'provider-id' => $provider->getId(),
            ]));
        }

        $url = $this->app['geonames.server-uri'];
        return $this->render('login/register-classic.html.twig', array_merge(
            $this->getDefaultTemplateVariables($request),
            [
                'geonames_server_uri' => $url,
                'form' => $form->createView()
            ]));
    }

    private function attachProviderToUser(EntityManagerInterface $em, ProviderInterface $provider, User $user)
    {
        $usrAuthProvider = new UsrAuthProvider();
        $usrAuthProvider->setDistantId($provider->getToken()->getId());
        $usrAuthProvider->setProvider($provider->getId());
        $usrAuthProvider->setUser($user);

        try {
            $provider->logout();
        } catch (RuntimeException $e) {
            // log these errors
        }

        $em->persist($usrAuthProvider);
    }

    /**
     * Send a confirmation mail after register
     *
     * @param  Request $request The current request
     * @return RedirectResponse
     */
    public function sendConfirmMail(Request $request)
    {
        if (null === $usrId = $request->query->get('usr_id')) {
            $this->app->abort(400, 'Missing usr_id parameter.');
        }

        $user = $this->getUserRepository()->find((int) $usrId);
        if (!$user instanceof User) {
            $this->app->addFlash('error', $this->app->trans('Invalid link.'));

            return $this->app->redirectPath('homepage');
        }

        try {
            $this->sendAccountUnlockEmail($user, $request);
            $this->app->addFlash('success', $this->app->trans('login::notification: demande de confirmation par mail envoyee'));
        } catch (InvalidArgumentException $e) {
            // todo, log this failure
            $this->app->addFlash('error', $this->app->trans('Unable to send your account unlock email.'));
        }

        return $this->app->redirectPath('homepage');
    }

    /**
     * Validation of email address
     *
     * @param  Request $request The current request
     * @return RedirectResponse
     */
    public function registerConfirm(Request $request)
    {
        if (null === $code = $request->query->get('code')) {
            $this->app->addFlash('error', $this->app->trans('Invalid unlock link.'));

            return $this->app->redirectPath('homepage');
        }

        if (null === $token = $this->getTokenRepository()->findValidToken($code)) {
            $this->app->addFlash('error', $this->app->trans('Invalid unlock link.'));

            return $this->app->redirectPath('homepage');
        }

        $user = $token->getUser();

        if (!$user->isMailLocked()) {
            $this->app->addFlash('info', $this->app->trans('Account is already unlocked, you can login.'));

            return $this->app->redirectPath('homepage');
        }

        $tokenManipulator = $this->getTokenManipulator();
        $tokenManipulator->delete($token);
        $user->setMailLocked(false);

        try {
            $receiver = Receiver::fromUser($user);
        } catch (InvalidArgumentException $e) {
            $this->app->addFlash('success', $this->app->trans('Account has been unlocked, you can now login.'));

            return $this->app->redirectPath('homepage');
        }

        $tokenManipulator->delete($token);

        if (count($this->getAclForUser($user)->get_granted_base()) > 0) {
            $mail = MailSuccessEmailConfirmationRegistered::create($this->app, $receiver);

            if (($locale = $user->getLocale()) != null) {
                $mail->setLocale($locale);
            }

            $this->deliver($mail);

            $this->app->addFlash('success', $this->app->trans('Account has been unlocked, you can now login.'));
        } else {
            $mail = MailSuccessEmailConfirmationUnregistered::create($this->app, $receiver);

            if (($locale = $user->getLocale()) != null) {
                $mail->setLocale($locale);
            }

            $this->deliver($mail);

            $this->app->addFlash('info', $this->app->trans('Account has been unlocked, you still have to wait for admin approval.'));
        }

        if ($request->query->get('send_credentials')) {
            $this->sendPasswordSetupMail($user, $request);
        }

        return $this->app->redirectPath('homepage');
    }

    public function renewPassword(Request $request)
    {
        $service = $this->getRecoveryService();

        $form = $this->app->form(new PhraseaRecoverPasswordForm($this->getTokenRepository()));
        $form->setData(['token' => $request->get('token') ]);

        if ('POST' === $request->getMethod()) {
            $form->handleRequest($request);
            if ($form->isValid()) {
                $data = $form->getData();

                $service->resetPassword($data['token'], $data['password']);
                $this->app->addFlash('success', $this->app->trans('login::notification: Mise a jour du mot de passe avec succes'));

                return $this->app->redirectPath('homepage');
            }
        }

        return $this->render('login/renew-password.html.twig', array_merge(
            $this->getDefaultTemplateVariables($request),
            ['form' => $form->createView() ]
        ));
    }

    /**
     * Submit the new password
     *
     * @param  Request $request The current request
     * @return RedirectResponse
     */
    public function forgotPassword(Request $request)
    {
        $form = $this->app->form(new PhraseaForgotPasswordForm());
        $service = $this->getRecoveryService();
        $flashMessage = $this->app->trans('login:: request password mail sent');

        try {
            if ('POST' === $request->getMethod()) {
                $form->handleRequest($request);
                $requestData = $request->request->all();

                if(
                    (isset($requestData['g-recaptcha-response']) && $requestData['g-recaptcha-response'] == "") ||
                    (isset($requestData['h-captcha-response']) && $requestData['h-captcha-response'] == "")
                ) {
                    $this->app->addFlash('error', $this->app->trans('Please fill captcha'));

                    $dataError = new FormError("");
                    $form->get('captcha')->addError($dataError);
                }

                if ($form->isValid()) {
                    $data = $form->getData();

                    try {
                        $service->requestPasswordResetToken($data['email'], true);
                    }
                    catch (InvalidArgumentException $ex) {
                        /** @Ignore */
                        $message = $this->app->trans($ex->getMessage());
                        throw new FormProcessingException($message, 0, $ex);
                    }

                    $this->app->addFlash('info', $flashMessage);

                    return $this->app->redirectPath('login_forgot_password');
                }
            }
        } catch (FormProcessingException $e) {
            $this->app->addFlash('info', $flashMessage);
        }

        return $this->render('login/forgot-password.html.twig', array_merge(
            $this->getDefaultTemplateVariables($request),
            [ 'form'  => $form->createView() ]
        ));
    }

    /**
     * Get the register form
     *
     * @param  Request $request The current request
     * @return Response
     */
    public function displayRegisterForm(Request $request)
    {
        if (!$this->getRegistrationManager()->isRegistrationEnabled()) {
            $this->app->abort(404, 'Registration is disabled');
        }

        if (0 < count($this->getAuthenticationProviders())) {
            return $this->render('login/register.html.twig', $this->getDefaultTemplateVariables($request));
        } else {
            return $this->app->redirectPath('login_register_classic');
        }
    }

    /**
     * Logout from Phraseanet
     *
     * @param  Request $request The current request
     * @return RedirectResponse
     */
    public function logout(Request $request)
    {
        $providerId = $this->getSession()->get('auth_provider.id', null);

        /** @var RedirectResponse $response */
        $response = null;

        // does the provider provides a logout redirection ?
        if($providerId && ($provider = $this->findProvider($providerId))) {
            if(method_exists($provider, 'logoutAndRedirect')) {
                $redirectToPhr = $this->app->url('logout');
                $response = $provider->logoutAndRedirect($redirectToPhr);
            }
            else {
                $this->dispatch(PhraseaEvents::LOGOUT, new LogoutEvent($this->app));
                $this->getAuthenticator()->closeAccount();

                $response = $provider->logout();
            }
        }

        if($response) {
            $this->getSession()->set('auth_provider.id', null);
        }
        else {
            // no provider logout : use ours
            $this->dispatch(PhraseaEvents::LOGOUT, new LogoutEvent($this->app));
            $this->getAuthenticator()->closeAccount();

            $this->app->addFlash('info', $this->app->trans('Vous etes maintenant deconnecte. A bientot.'));

            $response = $this->app->redirectPath('homepage', [
                'redirect' => $request->query->get("redirect")
            ]);
            $response->headers->clearCookie('persistent');
            $response->headers->clearCookie('last_act');
        }

        return $response;
    }

    /**
     * Login into Phraseanet
     *
     * @param  Request $request The current request
     * @return Response
     */
    public function login(Request $request)
    {
        try {
            $this->getApplicationBox()->get_connection();
        } catch (\Exception $e) {
            $this->app->addFlash('error', $this->app->trans('login::erreur: No available connection - Please contact sys-admin'));
        }

        $feeds = $this->getFeedRepository()->findBy(['public' => true], ['updatedOn' => 'DESC']);

        $form = $this->app->form(new PhraseaAuthenticationForm($this->app));
        $form->setData([
            'redirect' => $request->query->get('redirect')
        ]);

        return $this->render('login/index.html.twig', array_merge(
            $this->getDefaultTemplateVariables($request),
            [
                'feeds' => $feeds,
                'form'  => $form->createView(),
            ]));
    }

    /**
     * Authenticate to phraseanet
     *
     * @param  Request $request The current request
     * @return RedirectResponse
     */
    public function authenticate(Request $request)
    {
        $form = $this->app->form(new PhraseaAuthenticationForm($this->app));
        $redirector = function (array $params = []) {
            return $this->app->redirectPath('homepage', $params);
        };

        try {
            return $this->doAuthentication($request, $form, $redirector);
        } catch (AuthenticationException $e) {
            return $e->getResponse();
        }
    }

    public function authenticateAsGuest(Request $request)
    {
        if (!$this->app->isGuestAllowed()) {
            $this->app->abort(403, $this->app->trans('Phraseanet guest-access is disabled'));
        }

        $context = new Context(Context::CONTEXT_GUEST);
        $this->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context));

        do {
            $login = uniqid('guest');
        } while (null !== $this->getUserRepository()->findOneBy(['login' => $login]));

        $user = $this->getUserManipulator()->createUser($login, $this->getStringGenerator()->generateString(128));
        $user->setGuest(true);
        $invite_user = $this->getUserRepository()->findByLogin(User::USER_GUEST);

        $usr_base_ids = array_keys($this->getAclForUser($user)->get_granted_base());
        $this->getAclForUser($user)->revoke_access_from_bases($usr_base_ids);

        $invite_base_ids = array_keys($this->getAclForUser($invite_user)->get_granted_base());
        $this->getAclForUser($user)->apply_model($invite_user, $invite_base_ids);

        $this->postAuthProcess($request, $user);

        $response = $this->generateAuthResponse($this->getBrowser(), $request->request->get('redirect'));
        $response->headers->setCookie(new Cookie('invite-usr-id', $user->getId()));

        $event = new PostAuthenticate($request, $response, $user, $context);
        $this->dispatch(PhraseaEvents::POST_AUTHENTICATE, $event);

        return $response;
    }

    public function generateAuthResponse(\Browser $browser, $redirect)
    {
        if ($browser->isMobile()) {
            $response = $this->app->redirectPath('lightbox');
        } elseif ($redirect) {
            $response = new RedirectResponse('../' . ltrim($redirect,'/'));
        } elseif (true !== $browser->isNewGeneration()) {
            $response = $this->app->redirectPath('get_client');
        } else {
            $response = $this->app->redirectPath('prod');
        }

        $response->headers->clearCookie('last_act');

        return $response;
    }

    // move this in an event
    public function postAuthProcess(Request $request, User $user)
    {
        $date = new DateTime('+' . (int) $this->getConf()->get(['registry', 'actions', 'validation-reminder-time-left-percent']) . ' days');
        $manager = $this->getEntityManager();

        /*
         * PHRAS-3214_validation-tokens-refacto : This code is moved to console command "SendValidationRemindersCommand.php"
         *
        foreach ($this->getValidationParticipantRepository()->findNotConfirmedAndNotRemindedParticipantsByExpireDate($date) as $participant) {
            $validationSession = $participant->getSession();
            $basket = $validationSession->getBasket();

            // find the token if exists
            // nb : a validation may have not generated tokens if forcing auth was required upon creation
            try {
                $token = $this->getTokenRepository()->findValidationToken($basket, $participant->getUser());
            }
            catch (\Exception $e) {
                // not unique token ? should not happen
                $token = null;
            }

            if(!is_null($token)) {
                $url = $this->app->url('lightbox_validation', ['basket' => $basket->getId(), 'LOG' => $token->getValue()]);
            }
            else {
                $url = $this->app->url('lightbox_validation', ['basket' => $basket->getId()]);
            }

            $this->dispatch(PhraseaEvents::VALIDATION_REMINDER, new ValidationEvent($participant, $basket, $url));

            $participant->setReminded(new DateTime('now'));
            $manager->persist($participant);
        }

        $manager->flush();
        */

        $session = $this->getAuthenticator()->openAccount($user);

        if ($user->getLocale() != $this->app['locale']) {
            $user->setLocale($this->app['locale']);
        }

        $width = $height = null;
        if ($request->cookies->has('screen')) {
            $data = array_filter((explode('x', $request->cookies->get('screen', ''))));
            if (count($data) === 2) {
                $width = $data[0];
                $height = $data[1];
            }
        }
        $session->setIpAddress($request->getClientIp())
            ->setScreenHeight($height)
            ->setScreenWidth($width);

        $manager->persist($session);
        $manager->flush();

        return $session;
    }

    public function authenticateWithProvider(Request $request, $providerId)
    {
        $provider = $this->findProvider($providerId);

        return $provider->authenticate($request->query->all());
    }

    public function authenticationCallback(Request $request, $providerId)
    {
        $this->getSession()->set('auth_provider.id', null);
        $this->getSession()->set('provider.token_info', null);

        $provider = $this->findProvider($providerId);

        // triggers what's necessary
        try {
            $provider->onCallback($request);
            $token = $provider->getToken();
            $this->getSession()->set('auth_provider.id', $providerId);
        } catch (NotAuthenticatedException $e) {
            $this->getSession()->getFlashBag()->add('error', $this->app->trans('Unable to authenticate with %provider_name%', ['%provider_name%' => $provider->getName()]));

            return $this->app->redirectPath('homepage');
        }

        $userAuthProvider = $this->getUserAuthProviderRepository()
            ->findWithProviderAndId($token->getProvider()->getId(), $token->getId());

        if (null !== $userAuthProvider) {
            $this->postAuthProcess($request, $userAuthProvider->getUser());

            if (null !== $redirect = $request->query->get('redirect')) {
                $redirection = '../' . $redirect;
            } else {
                $redirection = $this->app->path('prod');
            }

            return $this->app->redirect($redirection);
        }

        try {
            $user = $this->getAuthenticationSuggestionFinder()->find($token);
        } catch (NotAuthenticatedException $e) {
            $this->app->addFlash('error', $this->app->trans('Unable to retrieve provider identity'));

            return $this->app->redirectPath('homepage');
        }

        $manager = $this->getEntityManager();
        if (null !== $user) {
            $this->attachProviderToUser($manager, $provider, $user);
            $manager->flush();

            $this->postAuthProcess($request, $user);

            if (null !== $redirect = $request->query->get('redirect')) {
                $redirection = '../' . $redirect;
            } else {
                $redirection = $this->app->path('prod');
            }

            return $this->app->redirect($redirection);
        }

        $accountCreatorProvider = $this->getAuthenticationAccountCreatorProvider();
        if ($accountCreatorProvider->isEnabled()) {
            $user = $accountCreatorProvider->create($this->app, $token->getId(), $token->getIdentity()->getEmail(), $token->getTemplates());

            $this->attachProviderToUser($manager, $provider, $user);
            $manager->flush();

            $this->postAuthProcess($request, $user);

            if (null !== $redirect = $request->query->get('redirect')) {
                $redirection = '../' . $redirect;
            } else {
                $redirection = $this->app->path('prod');
            }

            return $this->app->redirect($redirection);
        } elseif ($this->getRegistrationManager()->isRegistrationEnabled()) {
            return $this->app->redirectPath('login_register_classic', ['providerId' => $providerId]);
        }

        $this->app->addFlash('error', $this->app->trans('Your identity is not recognized.'));

        return $this->app->redirectPath('homepage');
    }

    private function sendAccountUnlockEmail(User $user, Request $request)
    {
        $helper = new Manage($this->app, $request);

        $helper->sendAccountUnlockEmail($user);
    }

    private function sendPasswordSetupMail(User $user, Request $request)
    {
        $helper = new Manage($this->app, $request);

        $helper->sendPasswordSetupMail($user);
    }

    /**
     * @param  string $providerId
     * @return ProviderInterface
     */
    private function findProvider($providerId)
    {
        try {
            return $this->getAuthenticationProviders()->get($providerId);
        } catch (InvalidArgumentException $e) {
            throw new NotFoundHttpException('The requested provider does not exist');
        }
    }

    private function doAuthentication(Request $request, FormInterface $form, $redirector)
    {
        if (!is_callable($redirector)) {
            throw new InvalidArgumentException('Redirector should be callable');
        }

        $context = new Context(Context::CONTEXT_NATIVE);
        $this->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context));

        $form->handleRequest($request);
        if (!$form->isValid()) {
            $this->app->addFlash('error', $this->app->trans('An unexpected error occurred during authentication process, please contact an admin'));

            throw new AuthenticationException(call_user_func($redirector));
        }

        $params = [];

        if (null !== $redirect = $request->get('redirect')) {
            $params['redirect'] = ltrim($redirect, '/');
        }

        try {
            $usr_id = $this->getPasswordAuthentication()->getUsrId(
                $request->request->get('login'),
                $request->request->get('password'),
                $request
            );
        } catch (RequireCaptchaException $e) {
            $this->app->requireCaptcha();
            $this->app->addFlash('warning', $e->getMessage());

            throw new AuthenticationException(call_user_func($redirector, $params));
        } catch (AccountLockedException $e) {
            $this->app->addFlash('warning', $this->app->trans('login::erreur: Vous n\'avez pas confirme votre email'));
            $this->app->addUnlockAccountData($e->getUsrId());

            throw new AuthenticationException(call_user_func($redirector, $params));
        }

        if (null === $usr_id) {
            $this->getSession()->getFlashBag()->set('error', $this->app->trans('login::erreur: Erreur d\'authentification'));

            throw new AuthenticationException(call_user_func($redirector, $params));
        }

        $user = $this->getUserRepository()->find($usr_id);
        $session = $this->postAuthProcess($request, $user);

        $response = $this->generateAuthResponse($this->getBrowser(), $request->request->get('redirect'));
        $response->headers->clearCookie('invite-usr-id');

        if ($request->request->get('remember-me') == '1') {
            $nonce = $this->getStringGenerator()->generateString(64);
            $string = $this->getBrowser()->getBrowser() . '_' . $this->getBrowser()->getPlatform();

            $token = $this->getPasswordEncoder()
                ->encodePassword($string, $nonce);

            $session->setToken($token)->setNonce($nonce);

            $response->headers->setCookie(new Cookie('persistent', $token, time() + $this->getConfigurationStore()->offsetGet('session')['lifetime']));

            $manager = $this->getEntityManager();
            $manager->persist($session);
            $manager->flush();
        }

        $event = new PostAuthenticate($request, $response, $user, $context);
        $this->dispatch(PhraseaEvents::POST_AUTHENTICATE, $event);

        return $event->getResponse();
    }

    /**
     * @return FeedItemRepository
     */
    private function getFeedItemRepository()
    {
        return $this->app['repo.feed-items'];
    }

    /**
     * @return \Browser
     */
    private function getBrowser()
    {
        return $this->app['browser'];
    }

    /**
     * @return RegistrationManager
     */
    private function getRegistrationManager()
    {
        return $this->app['registration.manager'];
    }

    /**
     * @return string[]
     */
    private function getRegistrationFields()
    {
        return $this->app['registration.fields'];
    }

    /**
     * @return string[]
     */
    private function getOptionalRegistrationFields()
    {
        return $this->app['registration.optional-fields'];
    }

    /**
     * @return UsrAuthProviderRepository
     */
    private function getUserAuthProviderRepository()
    {
        return $this->app['repo.usr-auth-providers'];
    }

    /**
     * @return ReCaptcha
     */
    private function getRecaptcha()
    {
        return $this->app['recaptcha'];
    }

    /**
     * @return UserManipulator
     */
    private function getUserManipulator()
    {
        return $this->app['manipulator.user'];
    }

    /**
     * @return UserRepository
     */
    private function getUserRepository()
    {
        return $this->app['repo.users'];
    }

    /**
     * @return RegistrationManipulator
     */
    private function getRegistrationManipulator()
    {
        return $this->app['manipulator.registration'];
    }

    /**
     * @return TokenManipulator
     */
    private function getTokenManipulator()
    {
        return $this->app['manipulator.token'];
    }

    /**
     * @return TokenRepository
     */
    private function getTokenRepository()
    {
        return $this->app['repo.tokens'];
    }

    /**
     * @return ProvidersCollection
     */
    private function getAuthenticationProviders()
    {
        return $this->app['authentication.providers'];
    }

    /**
     * @return FeedRepository
     */
    private function getFeedRepository()
    {
        return $this->app['repo.feeds'];
    }

    /**
     * @return Generator
     */
    private function getStringGenerator()
    {
        return $this->app['random.medium'];
    }

    /**
     * @return BasketParticipantRepository
     */
    private function getBasketParticipantRepository()
    {
        return $this->app['repo.basket-participants'];
    }

    /**
     * @return Session
     */
    private function getSession()
    {
        return $this->app['session'];
    }

    /**
     * @return SuggestionFinder
     */
    private function getAuthenticationSuggestionFinder()
    {
        return $this->app['authentication.suggestion-finder'];
    }

    /**
     * @return AccountCreator
     */
    private function getAuthenticationAccountCreatorProvider()
    {
        return $this->app['authentication.providers.account-creator'];
    }

    /**
     * @return PasswordAuthenticationInterface
     */
    private function getPasswordAuthentication()
    {
        return $this->app['auth.native'];
    }

    /**
     * @return PasswordEncoder
     */
    private function getPasswordEncoder()
    {
        return $this->app['auth.password-encoder'];
    }

    /**
     * @return ConfigurationInterface
     */
    private function getConfigurationStore()
    {
        return $this->app['configuration.store'];
    }

    /**
     * @return RecoveryService
     */
    private function getRecoveryService()
    {
        return $this->app['authentication.recovery_service'];
    }

    /**
     * @return RegistrationService
     */
    private function getRegistrationService()
    {
        return $this->app['authentication.registration_service'];
    }
}
