<?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  2019 Lifestyle Webconsulting GmbH
 * @link       https://www.life-style.de
 */

namespace Sso\RestBundle\Worker\User\Show;

use Sso\RestBundle\Api\Database\Factory as DatabaseManager;
use Sso\RestBundle\Api\Manager as RestApiManager;
use Sso\RestBundle\Model\Response\User\PostShow\Response;
use Sso\WebserviceBundle\Entity\Webservice\Type\User;
use FOS\RestBundle\View\View as RestView;
use LifeStyle\Tools\RestErrorBundle\Api\Error\Errors\Index as Errors;
use LifeStyle\Tools\RestErrorBundle\Api as ErrorApi;

/**
 * Class Handler
 *
 * @copyright  2019 Lifestyle Webconsulting GmbH
 * @link       https://www.life-style.de
 * @package Sso\RestBundle\Worker\User\Show
 */
class Handler
{
    /**
     * @var RestApiManager
     */
    private $restApiM;

    /**
     * @var DatabaseManager
     */
    private $dbM;

    /**
     * @var ErrorApi\Manager
     */
    private $errorApi;

    /**
     * @var Errors
     */
    private $errors;

    /**
     * @var integer
     */
    private $timeStart;

    /**
     * Handler constructor.
     * @param RestApiManager $restApiM
     */
    public function __construct(RestApiManager $restApiM)
    {
        $this->restApiM = $restApiM;
        $this->dbM = $restApiM->database();

        if ($this->restApiM->session()->has('ls_start_microtime')) {
            $this->timeStart = $this->restApiM->session()->get('ls_start_microtime');
        } else {
            $this->timeStart = microtime(true);
        }

        $this->errorApi = $this->restApiM->errorManager();
        $this->errors = $this->errorApi->error()->errors();
    }

    /**
     * @param string $encryptedBody
     * @param string $lastDigest
     * @param string $format
     * @return RestView
     * @throws \Exception
     */
    public function init($encryptedBody, $lastDigest, $format = 'json')
    {
        // Validate request
        $requestModel = $this->validateRestRequest($encryptedBody, $lastDigest, $format);
        if ($this->errors->hasErrors()) {
            return $this->errorApi->error()->view()->getErrorsView($this->errors->getErrors());
        }

        // Load user from database
        $ssoUser = $this->dbM->webservice()->data()->user()->findUserByIdentifier(
            $requestModel->getUserIdentifier()
        );

        // User not found
        if (!$ssoUser instanceof User) {
            $this->errors->addError(400, 'user not found in database', 'external', 'InvalidRequest')->setStatus(400);
            return $this->errorApi->error()->view()->getErrorsView($this->errors->getErrors());
        }

        // Map response
        $response = $this->mapResponse($ssoUser);

        // Create response view
        $view = RestView::create();
        $view->setStatusCode(200)->setData($response);

        return $view;
    }

    /**
     * @param string $encyptedBody
     * @param string $lastDigest
     * @return \Sso\RestBundle\Model\Request\User\PostShow\Request|void
     */
    private function validateRestRequest($encyptedBody, $lastDigest, $format)
    {
        //first we have to decipher the request body
        $requestBody = $this->restApiM->getContainer()->get('ciphering_api_manager')->ciphering()->decoder()->decode(
            $encyptedBody,
            $lastDigest
        );

        if (!$requestBody) {
            $this->errors->addError("ci1", 'the body could not be decoded', 'external', 'InvalidCipheringException');
            $this->errors->setStatus(400);

            return;
        }

        //no validate the content
        if ($format == "xml") {
            $errors = $this->restApiM->helper()->string()->validateXML($requestBody);
        } else {
            $errors = $this->restApiM->helper()->string()->validateJson($requestBody);
        }

        // collect errors
        foreach ($errors as $error) {
            $this->errors->addError(400, $error, 'external', 'InvalidRequest')->setStatus(400);
        }
        if ($this->errors->hasErrors()) {
            return;
        }

        //now build request model
        $requestModel = $this->restApiM->serializer()->deserialize(
            $requestBody,
            'Sso\RestBundle\Model\Request\User\PostShow\Request',
            $format
        );

        $validationErrors = $this->restApiM->validator()->validate($requestModel);

        if (count($validationErrors) > 0) {
            foreach ($validationErrors as $error) {
                $message = $error->getPropertyPath() . ": " . $error->getMessage();
                $this->errors->addError(400, $message, 'external')->setStatus(400);
            }
        }

        return $requestModel;
    }

    /**
     * @param User $ssoUser
     * @return Response
     */
    private function mapResponse(User $ssoUser)
    {
        $response = $this->restApiM->model()->response()->user()->postShow();
        $response->setStatus(200);
        $response->setMessage('OK');
        $response->setActive($ssoUser->isActive() && !$ssoUser->isDeleted());
        $response->setUserGuid($ssoUser->getGuid());
        $response->setUsername($ssoUser->getUsername());
        $response->setUserEmail($ssoUser->getEmail());
        $response->setUserFirstname($ssoUser->getFirstname());
        $response->setUserLastname($ssoUser->getLastname());
        $response->setUserIdentifier($ssoUser->getUsername());
        $response->setUserAuthId($ssoUser->getAuthId());
        $response->setUserLdapSearchAttributes($ssoUser->getLdapSearchAttributes());
        $response->setUserLdapSearchValue($ssoUser->getLdapSearchValue());
        $response->setMfaEnabled($ssoUser->isMfaEnabled());
        $response->setLastLogin($ssoUser->getLastLoginAt());
        $response->setLoginFails((int)$ssoUser->getLoginFails());

        // Mfa secret as qr-code should only be added if user has not received the secret yet
        if ($ssoUser->isMfaEnabled() && !$ssoUser->hasReceivedMfaSecret()) {
            $response->setMfaQrCode(
                $this->mfaApiM()->mfa()->image()->generateQr(
                    $ssoUser->getEmail(),
                    $ssoUser->getMfaSecret()
                )
            );
        }

        $response->setDurationSeconds(microtime(true) - $this->timeStart);

        return $response;
    }

    /**
     * @return \LifeStyle\Tools\MfaBundle\Api\Manager
     */
    private function mfaApiM()
    {
        return $this->restApiM->getContainer()->get('mfa_api_manager');
    }
}
