<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Services\User\Add;

use Sso\AccessBundle\Authorization\TemporaryTokenAuthorizationInterface;
use Sso\WebserviceBundle\Api\Mapper\UserGroupMapper;
use Sso\WebserviceBundle\Entity\Webservice\Type\User;
use Sso\WebserviceBundle\ErrorHandler\ErrorHandlerInterface;
use Sso\WebserviceBundle\PasswordCrypt\PasswordCryptRepositoryInterface;
use Sso\WebserviceBundle\Security\Authorisation\UserAuthorization;
use Sso\WebserviceBundle\Services\HandlerInterface;
use Sso\WebserviceBundle\Services\RequestInterface as ServiceRequestInterface;
use Sso\WebserviceBundle\Services\ResponseBuilderInterface as ServiceResponseBuilderInterface;
use Sso\WebserviceBundle\Database\Webservice\User as UserRepository;
use Sso\WebserviceBundle\Services\User\Add\RequestData\User as RequestDataUser;
use LifeStyle\Tools\MfaBundle\MfaCreator;
use Sso\WebserviceBundle\Api\PasswordPolicy\History\Service as PasswordHistoryService;
use Sso\WebserviceBundle\Database\Webservice\PasswordPolicy as PasswordPolicyDatabase;

/**
 * Class Handler
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 * @package    Sso\WebserviceBundle\Services\User\Add
 */
final class Handler implements HandlerInterface
{
    /**
     * @var Factory
     */
    private $factory;

    /**
     * @var UserRepository
     */
    private $database;

    /**
     * @var ErrorHandlerInterface
     */
    private $errorHandler;

    /**
     * @var MfaCreator
     */
    private $mfaCreator;

    /**
     * @var PasswordPolicyDatabase
     */
    private $passwordPolicyDatabase;

    /**
     * @var PasswordHistoryService
     */
    private $passwordHistoryService;

    /**
     * @var PasswordCryptRepositoryInterface
     */
    private $passwordCryptRepository;

    /**
     * @var UserGroupMapper
     */
    private $userGroupMapper;

    /**
     * @var TemporaryTokenAuthorizationInterface
     */
    private $temporaryTokenAuthorization;

    /**
     * Handler constructor.
     * @param Factory $factory
     * @param UserRepository $database
     * @param ErrorHandlerInterface $errorHandler
     * @param MfaCreator $mfaCreator
     * @param PasswordPolicyDatabase $passwordPolicyDatabase
     * @param PasswordHistoryService $passwordHistoryService
     * @param PasswordCryptRepositoryInterface $passwordCryptRepository
     * @param UserGroupMapper $userGroupMapper
     * @param TemporaryTokenAuthorizationInterface $temporaryTokenAuthorization
     */
    public function __construct(
        Factory $factory,
        UserRepository $database,
        ErrorHandlerInterface $errorHandler,
        MfaCreator $mfaCreator,
        PasswordPolicyDatabase $passwordPolicyDatabase,
        PasswordHistoryService $passwordHistoryService,
        PasswordCryptRepositoryInterface $passwordCryptRepository,
        UserGroupMapper $userGroupMapper,
        TemporaryTokenAuthorizationInterface $temporaryTokenAuthorization
    ) {
        $this->factory = $factory;
        $this->database = $database;
        $this->errorHandler = $errorHandler;
        $this->mfaCreator = $mfaCreator;
        $this->passwordPolicyDatabase = $passwordPolicyDatabase;
        $this->passwordHistoryService = $passwordHistoryService;
        $this->passwordCryptRepository = $passwordCryptRepository;
        $this->userGroupMapper = $userGroupMapper;
        $this->temporaryTokenAuthorization = $temporaryTokenAuthorization;
    }

    /**
     * @param ServiceRequestInterface $request
     * @return ServiceResponseBuilderInterface
     */
    public function handle(ServiceRequestInterface $request)
    {
        //first get requestModel from request version dependent
        $requestData = $this->factory->request()->requestParser()->parse($request->getRequestBody());

        $responseBuilder = $this->factory->response()->responseBuilder();
        if ($this->errorHandler->hasErrors()) {
            return $responseBuilder;
        }

        //next check if the user already exists
        $this->checkUserAlreadyExists($requestData);

        if ($this->errorHandler->hasErrors()) {
            return $responseBuilder;
        }

        if (!$this->errorHandler->hasErrors() && !empty($requestData->getPasswordPolicy())) {
            // check policy in db
            $passwordPolicy = $this->passwordPolicyDatabase->getOne($requestData->getPasswordPolicy());
            if (null === $passwordPolicy) {
                $this->errorHandler->addError(404, 'PasswordPolicyNotFound', 'u2013', '', '');
            }
        }

        //next save user to db
        /** @var User $userModel */
        $userModel = $this->factory->getApiM()->mapper()->modelInToModelOut($requestData, $this->database->getUserEntity());
        $userModel->generateGuid();
        $userModel->generateId();
        $userModel = $this->userGroupMapper->syncUserGroupsByNames($userModel, $requestData->getUserGroups());

        // Fallback for older versions
        if (!$userModel->getPasswordEncryptType()) {
            $userModel->setPasswordEncryptType($this->passwordCryptRepository->getDefault()->getEncryptType());
        }

        // user never existed until now, so there can't be a prev. pwd change datetime - the magic mapper above
        // in combination with the auto-update of the field by setPassword() cause this to exist though when a value
        // was sent in the request model for it.
        $userModel->setPreviousLastPasswordChange(null);

        //check for user active
        if (!$requestData->isActive()) {
            $userModel->generateActivateRequestToken();
        }
        if ($userModel->isMfaEnabled()) {
            $userModel->setMfaSecret($this->mfaCreator->generateSecret());
        }

        if ((!$this->errorHandler->hasErrors()) && (!$this->database->saveUser($userModel))) {
            $this->errorHandler->addErrors($userModel->errors()->getErrors());
        }

        if (!$this->errorHandler->hasErrors()) {
            $this->passwordHistoryService->addPassword(
                $userModel->getGuid(),
                $userModel->getPassword(),
                $userModel->getPasswordEncryptType()
            );
        }

        //ok next step set the responseData
        if (!$this->errorHandler->hasErrors()) {

            // Add temporary grant access to the current consumer for this new record
            $this->temporaryTokenAuthorization->addAuthorizedUserId($userModel->getId());

            $responseData = $this->factory->responseData()->user()->setFromUserModel($userModel);
            $responseBuilder->setResponse($responseData);
        }

        return $responseBuilder;
    }

    /**
     * @param RequestDataUser $requestModel
     * @return null
     */
    private function checkUserAlreadyExists(RequestDataUser $requestModel)
    {
        $userCheck = $this->database->getUserByUsername($requestModel->getUsername());
        if (null !== $userCheck) {
            $this->errorHandler->addError(400, 'UserAlreadyExists', 'u011', 'Usernmae', '');
        }

        $email = $requestModel->getEmail();
        if (0 < strlen($email)) {
            $userCheck = $this->database->getUserByEmail($email);
            if (null !== $userCheck) {
                $this->errorHandler->addError(400, 'UserAlreadyExists', 'u011', 'Email', '');
            }
        }
        return null;
    }
}
