<?php

/**
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected 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\ChangePassword;

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\Entity\Webservice\Type\User as ModelUser;
use Sso\WebserviceBundle\Services\User\ChangePassword\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
 * @package Sso\WebserviceBundle\Services\User\ChangePassword
 */
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 UserAuthorization
     */
    private $authorization;

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

    /**
     * @param ServiceRequestInterface $request
     * @return ServiceResponseBuilderInterface
     */
    public function handle(ServiceRequestInterface $request)
    {
        $requestDTO = $this->factory->request()->requestParser()->parse($request->getRequestBody());
        $responseBuilder = $this->factory->response()->responseBuilder();
        $userModel = null;

        if (!$this->errorHandler->hasErrors()) {
            //ok get user from db
            $userModel = $this->getUserModel($requestDTO);
            if (null === $userModel) {
                $this->errorHandler->addError(404, 'UserNotFound', 'u2012', '', '');
            }
        }

        // Authorize user update action
        if (!$this->errorHandler->hasErrors() && !$this->authorization->canUpdate($userModel)) {
            $this->errorHandler->addError(401, 'UserNotAuthorized', 'u2014', '', '');
        }

        //update the user from
        if (!$this->errorHandler->hasErrors() && $this->verifyPassword($userModel, $requestDTO)) {
            $this->updateAndSaveUser($userModel, $requestDTO);
        }

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

            //ok next step set the responseDTO
            $responseData = $this->factory->responseData()->changePassword();
            $responseData->setStatus('Success');
            $responseData->setMessage('Password changed');
            $responseBuilder->setResponse($responseData);
        }

        return $responseBuilder;
    }

    /**
     * @param RequestDataUser $requestDTO
     * @return ModelUser|null
     * @throws \Exception
     */
    private function getUserModel(RequestDataUser $requestDTO)
    {
        $userModel = null;
        if ($identifier = $requestDTO->getIdentifier()) {
            $userModel = $this->database->getUserByIdentifier($identifier);
        }
        if (null === $userModel && ($guid = $requestDTO->getGuid())) {
            $userModel = $this->database->getUserByGuid($guid);
        }
        if (null === $userModel && ($username = $requestDTO->getUsername())) {
            $userModel = $this->database->getUserByUsername($username);
        }
        if (null === $userModel && ($email = $requestDTO->getEmail())) {
            $userModel = $this->database->getUserByEmail($email);
        }

        return $userModel;
    }

    /**
     * @param ModelUser $userModel
     * @param RequestDataUser $requestDTO
     * @return bool
     */
    private function verifyPassword(ModelUser $userModel, RequestDataUser $requestDTO)
    {
        if (null !== $requestDTO->getPasswordData()->getCurrentPassword()) {
            if (!$userModel->verifyPassword(
                $requestDTO->getPasswordData()->getCurrentPassword(),
                $this->passwordCryptRepository->getByUser($userModel)
            )) {
                $this->errorHandler->addError(400, 'UserWrongPassword', 'pv01', '', '');
                return false;
            }
        }

        if (null !== $requestDTO->getPasswordData()->getNewPasswordConfirm()) {
            if ($requestDTO->getPasswordData()->getNewPassword() !== $requestDTO->getPasswordData()->getNewPasswordConfirm()) {
                $this->errorHandler->addError(400, 'UserPasswordDoesNotMatch', 'pv02', '', '');
                return false;
            }
        }

        return true;
    }

    /**
     * @param ModelUser $userModel
     * @param RequestDataUser $requestDTO
     */
    private function updateAndSaveUser(ModelUser $userModel, RequestDataUser $requestDTO)
    {
        $updateData = $requestDTO->getPasswordData();

        if ($updateData->getNewPassword()) {
            $userModel->setPassword($updateData->getNewPassword());
            $userModel->setPasswordExpired(false);
        }

        if ($updateData->getCurrentPassword()) {
            $userModel->setPreviousPassword($updateData->getCurrentPassword());
        }

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