<?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\Geonames\Connector;
use Alchemy\Geonames\Exception\ExceptionInterface as GeonamesExceptionInterface;
use Alchemy\Phrasea\Account\Command\UpdateAccountCommand;
use Alchemy\Phrasea\Account\Command\UpdateFtpCredentialsCommand;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Authentication\Phrasea\PasswordEncoder;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\ControllerProvider\Root\Login;
use Alchemy\Phrasea\Core\Configuration\RegistrationManager;
use Alchemy\Phrasea\Core\Event\RegistrationEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Form\Login\PhraseaRenewPasswordForm;
use Alchemy\Phrasea\Model\Entities\ApiApplication;
use Alchemy\Phrasea\Model\Entities\FtpCredential;
use Alchemy\Phrasea\Model\Entities\Session;
use Alchemy\Phrasea\Model\Manipulator\ApiAccountManipulator;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
use Alchemy\Phrasea\Model\Repositories\ApiAccountRepository;
use Alchemy\Phrasea\Model\Repositories\ApiApplicationRepository;
use Alchemy\Phrasea\Model\Repositories\TokenRepository;
use Alchemy\Phrasea\Notification\Mail\MailRequestEmailUpdate;
use Alchemy\Phrasea\Notification\Receiver;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;



class AccountController extends Controller
{
    use EntityManagerAware;
    use NotifierAware;

    public function resetPassword(Request $request)
    {
        $form = $this->app->form(new PhraseaRenewPasswordForm());

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

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

                if ($this->getPasswordEncoder()->isPasswordValid($user->getPassword(), $data['oldPassword'], $user->getNonce())) {
                    $this->getUserManipulator()->setPassword($user, $data['password']);
                    $this->app->addFlash('success', $this->app->trans('login::notification: Mise a jour du mot de passe avec succes'));

                    return $this->app->redirectPath('account');
                } else {
                    $this->app->addFlash('error', $this->app->trans('Invalid password provided'));
                }
            }
        }

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

    /**
     * Reset Email
     *
     * @param  Request $request
     * @return RedirectResponse
     */
    public function resetEmail(Request $request)
    {
        if (null === ($password = $request->request->get('form_password'))
            || null === ($email = $request->request->get('form_email'))
            || null === ($emailConfirm = $request->request->get('form_email_confirm'))
        ) {
            throw new BadRequestHttpException($this->app->trans('Could not perform request, please contact an administrator.'));
        }

        $user = $this->getAuthenticatedUser();

        if (!$this->getPasswordEncoder()->isPasswordValid($user->getPassword(), $password, $user->getNonce())) {
            $this->app->addFlash('error', $this->app->trans('admin::compte-utilisateur:ftp: Le mot de passe est errone'));

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

        if (!\Swift_Validate::email($email)) {
            $this->app->addFlash('error', $this->app->trans('forms::l\'email semble invalide'));

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

        if ($email !== $emailConfirm) {
            $this->app->addFlash('error', $this->app->trans('forms::les emails ne correspondent pas'));

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

        $token = $this->getTokenManipulator()->createResetEmailToken($user, $email);
        $url = $this->app->url('account_reset_email', ['token' => $token->getValue()]);

        try {
            $receiver = Receiver::fromUser($user);
        } catch (InvalidArgumentException $e) {
            $this->app->addFlash('error', $this->app->trans('phraseanet::erreur: echec du serveur de mail'));

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

        $mail = MailRequestEmailUpdate::create($this->app, $receiver, null);
        $mail->setButtonUrl($url);
        $mail->setExpiration($token->getExpiration());

        $this->deliver($mail);

        $this->app->addFlash('info', $this->app->trans('admin::compte-utilisateur un email de confirmation vient de vous etre envoye. Veuillez suivre les instructions contenue pour continuer'));

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

    /**
     * Display reset email form
     *
     * @param  Request $request
     * @return Response
     */
    public function displayResetEmailForm(Request $request)
    {
        if (null !== $tokenValue = $request->query->get('token')) {
            if (null === $token = $this->getTokenRepository()->findValidToken($tokenValue)) {
                $this->app->addFlash('error', $this->app->trans('admin::compte-utilisateur: erreur lors de la mise a jour'));

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

            $user = $token->getUser();
            $user->setEmail($token->getData());
            $this->getTokenManipulator()->delete($token);

            $this->app->addFlash('success', $this->app->trans('admin::compte-utilisateur: L\'email a correctement ete mis a jour'));

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

        $context = $this->getLoginController()->getDefaultTemplateVariables($request);

        return $this->render('account/reset-email.html.twig', $context);
    }

    /**
     * @return LoginController
     */
    private function getLoginController()
    {
        return $this->app['login.controller'];
    }

    /**
     * Display authorized applications that can access user information
     *
     * @param Request        $request
     * @param ApiApplication $application
     *
     * @return JsonResponse
     */
    public function grantAccess(Request $request, ApiApplication $application)
    {
        if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) {
            $this->app->abort(400, $this->app->trans('Bad request format, only JSON is allowed'));
        }

        if (null === $account = $this->getApiAccountRepository()->findByUserAndApplication($this->getAuthenticatedUser(), $application)) {
            return $this->app->json(['success' => false]);
        }

        $manipulator = $this->getApiAccountManipulator();
        if (false === (Boolean) $request->query->get('revoke')) {
            $manipulator->authorizeAccess($account);
        } else {
            $manipulator->revokeAccess($account);
        }

        return $this->app->json(['success' => true]);
    }

    /**
     * Display account base access
     *
     * @return Response
     */
    public function accountAccess()
    {
        return $this->render('account/access.html.twig', [
            'inscriptions' => $this->getRegistrationManager()->getRegistrationSummary($this->getAuthenticatedUser())
        ]);
    }

    /**
     * Display authorized applications that can access user information
     *
     * @return Response
     */
    public function accountAuthorizedApps()
    {
        $data = [];

        $user = $this->getAuthenticatedUser();
        foreach (
            $this->getApiApplicationRepository()->findByUser($user) as $application) {
            $account = $this->getApiAccountRepository()->findByUserAndApplication($user, $application);

            $data[$application->getId()] = [
                'application' => $application,
                'user-account' => $account,
            ];
        }

        return $this->render('account/authorized_apps.html.twig', [
            "applications" => $data,
        ]);
    }

    /**
     * Display account session accesses
     *
     * @return Response
     */
    public function accountSessionsAccess()
    {
        $dql = 'SELECT s FROM Phraseanet:Session s WHERE s.user = :usr_id ORDER BY s.created DESC';

        $query = $this->getEntityManager()->createQuery($dql);
        $query->setMaxResults(100);
        $query->setParameters(['usr_id' => $this->getSession()->get('usr_id')]);
        /** @var Session[] $sessions */
        $sessions = $query->getResult();

        $result = [];
        foreach ($sessions as $session) {
            $info = '';
            try {
                $geoname = $this->getGeonameConnector()->ip($session->getIpAddress());
                $country = $geoname->get('country');
                $city = $geoname->get('city');
                $region = $geoname->get('region');

                $countryName = isset($country['name']) ? $country['name'] : null;
                $regionName = isset($region['name']) ? $region['name'] : null;

                if (null !== $city) {
                    $info = $city . ($countryName ? ' (' . $countryName . ')' : null);
                } elseif (null !== $regionName) {
                    $info = $regionName . ($countryName ? ' (' . $countryName . ')' : null);
                } elseif (null !== $countryName) {
                    $info = $countryName;
                } else {
                    $info = '';
                }
            } catch (GeonamesExceptionInterface $e) {

            }

            $result[] = [
                'session' => $session,
                'info'    => $info,
            ];
        }

        return $this->render('account/sessions.html.twig', ['sessions' => $result]);
    }

    /**
     * Display account form
     *
     * @return Response
     */
    public function displayAccount()
    {
        $manager = $this->getEventManager();
        $user = $this->getAuthenticatedUser();

        return $this->render('account/account.html.twig', [
            'user'          => $user,
            'evt_mngr'      => $manager,
            'notifications' => $manager->list_notifications_available($user),
        ]);
    }

    /**
     * Update account information
     *
     * @param  Request $request The current request
     * @return Response
     */
    public function updateAccount(Request $request)
    {
        $registrations = $request->request->get('registrations', []);

        if (false === is_array($registrations)) {
            $this->app->abort(400, '"registrations" parameter must be an array of base ids.');
        }

        $successfulRegistrations = [];
        $user = $this->getAuthenticatedUser();

        if (0 !== count($registrations)) {
            foreach ($registrations as $baseId) {
                $this->getRegistrationManipulator()
                    ->createRegistration($user, \collection::getByBaseId($this->app, $baseId));
                $successfulRegistrations[$baseId] = \collection::getByBaseId($this->app, $baseId);;
            }

            $this->getEventDispatcher()
                ->dispatch(PhraseaEvents::REGISTRATION_CREATE, new RegistrationEvent($user, $successfulRegistrations));

            $this->app->addFlash('success', $this->app->trans('Your registration requests have been taken into account.'));
        }

        $accountFields = [
            'form_gender',
            'form_firstname',
            'form_lastname',
            'form_address',
            'form_zip',
            'form_phone',
            'form_fax',
            'form_function',
            'form_company',
            'form_activity',
            'form_geonameid',
            'form_addressFTP',
            'form_loginFTP',
            'form_pwdFTP',
            'form_destFTP',
            'form_prefixFTPfolder',
            'form_retryFTP'
        ];

        $service = $this->app['accounts.service'];

        if (0 === count(array_diff($accountFields, array_keys($request->request->all())))) {
            $command = new UpdateAccountCommand();
            $command
                ->setGender((int) $request->request->get("form_gender"))
                ->setFirstName($request->request->get("form_firstname"))
                ->setLastName($request->request->get("form_lastname"))
                ->setAddress($request->request->get("form_address"))
                ->setZipCode($request->request->get("form_zip"))
                ->setPhone($request->request->get("form_phone"))
                ->setFax($request->request->get("form_fax"))
                ->setJob($request->request->get("form_activity"))
                ->setCompany($request->request->get("form_company"))
                ->setPosition($request->request->get("form_function"))
                ->setNotifications((Boolean) $request->request->get("mail_notifications"));

            $service->updateAccount($command);

            $this->getUserManipulator()->setGeonameId($user, $request->request->get("form_geonameid"));

            $ftpCredential = $user->getFtpCredential();

            if (null === $ftpCredential) {
                $ftpCredential = new FtpCredential();
                $ftpCredential->setUser($user);
            }

            $command = new UpdateFtpCredentialsCommand();

            $command->setEnabled($request->request->get("form_activeFTP"));
            $command->setAddress($request->request->get("form_addressFTP"));
            $command->setLogin($request->request->get("form_loginFTP"));
            $command->setPassword($request->request->get("form_pwdFTP"));
            $command->setPassiveMode($request->request->get("form_passifFTP"));
            $command->setFolder($request->request->get("form_destFTP"));
            $command->setFolderPrefix($request->request->get("form_prefixFTPfolder"));
            $command->setRetries($request->request->get("form_retryFTP"));

            $service->updateFtpSettings($command);

            $this->app->addFlash('success', $this->app->trans('login::notification: Changements enregistres'));
        }

        $requestedNotifications = (array) $request->request->get('notifications', []);

        $manipulator = $this->getUserManipulator();
        foreach ($this->getEventManager()->list_notifications_available($user) as $notifications) {
            foreach ($notifications as $notification) {
                $manipulator->setNotificationSetting($user, $notification['id'], isset($requestedNotifications[$notification['id']]));
            }
        }

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

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

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

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

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

    /**
     * @return ApiAccountRepository
     */
    private function getApiAccountRepository()
    {
        return $this->app['repo.api-accounts'];
    }

    /**
     * @return ApiAccountManipulator
     */
    private function getApiAccountManipulator()
    {
        return $this->app['manipulator.api-account'];
    }

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

    /**
     * @return ApiApplicationRepository
     */
    private function getApiApplicationRepository()
    {
        return $this->app['repo.api-applications'];
    }

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

    /**
     * @return Connector
     */
    private function getGeonameConnector()
    {
        return $this->app['geonames.connector'];
    }

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

    /**
     * @return \eventsmanager_broker
     */
    private function getEventManager()
    {
        return $this->app['events-manager'];
    }

    /**
     * @return EventDispatcherInterface
     */
    private function getEventDispatcher()
    {
        return $this->app['dispatcher'];
    }
}
