<?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\RestBundle\Worker\UserWs\Version2\UserSearch\Advanced;

use Sso\RestBundle\Api\ExceptionCollection\MainValidationException;
use Sso\RestBundle\Api\Manager as ApiManager;
use FOS\RestBundle\View\View as View;
use GuzzleHttp\Exception\RequestException;
use LifeStyle\Tools\RestErrorBundle\Api\Manager as ErrorApi;
use LifeStyle\Tools\RestErrorBundle\Api\Error\Errors\Index as Errors;
use LifeStyle\Tools\RestAuthBundle\Security\Authentication\Token\UidentifyToken;
use Symfony\Component\HttpFoundation\Request;
use Sso\RestBundle\ModelRest\Request\UserWs\Version2\UserSearch\Advanced\Request as RequestModel;
use Sso\RestBundle\ModelRest\Response\UserWs\Version2\UserSearch\Advanced\Response as ResponseUserType;
use Sso\RestBundle\ModelRest\Response\UserWs\Version2\UserSearch\Advanced\Response as RestResponseModel;
use Sso\RestBundle\ModelXml\Response\UserWs\Version2\UserSearch\Advanced\Success\Response as SuccessXMLResponse;
use Sso\RestBundle\ModelXml\Response\UserWs\Version2\UserSearch\Advanced\Error\Response as ErrorXMLResponse;
use Sso\RestBundle\ModelRest\Request\UserWs\Version2\UserSearch\Advanced\AdvancedSearch;

/**
 * Class Handler
 * @package Sso\RestBundle\Worker\UserWs\Version2\UserSearch\Advanced
 */
class Handler
{
    const STATUS = "status";
    const ERROR_EXTERNAL = "external";

    /**
     * @var UidentifyToken
     */
    private $userToken;

    /**
     * @var ApiManager
     */
    private $apiM;

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

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

    /**
     * @var ErrorXMLResponse
     */
    private $errorXmlEntity;

    /**
     * @var SuccessXMLResponse
     */
    private $successXmlEntity;

    /**
     * @var RestResponseModel
     */
    private $restResponseModel;

    /**
     * @var bool
     */
    private $oneResponseSuccess = false;

    /**
     * @param ApiManager $apiM
     */
    public function __construct(ApiManager $apiM)
    {
        $this->apiM = $apiM;
        $this->errorApi = $this->apiM->errorManager();
        $this->errors = $this->errorApi->error()->errors();
        $this->restResponseModel = $this->apiM->modelRest()->response()->userWs()->version2()->userSearch()->advanced()->response();
        $this->userToken = $this->apiM->getToken();
    }

    /**
     * @param Request $request
     * @return View
     */
    public function init(Request $request)
    {
        // first: validate main if there is any error it is allowed to use the default error response
        try {
            list($requestContent, $format) = $this->apiM->mainValidator()->validateRestRequest($request);
        } catch (MainValidationException $exc) {
            $this->errorApi->error()->errors()->setScriptSeconds($this->apiM->scriptTimeSeconds());

            return $this->errorApi->error()->view()->getErrorsView($this->errors->getErrors(), 416);
        }

        if ('xml' == $format) {
            // this, rather "not-so-nice", patch is necessary due to the shared models and Serializer's incapability
            // to have separate naming conventions for XML lists, at least as long as we don't implement an own,
            // complete naming strategy handler/class.
            $requestContent = $this->normalizeXmlListsForRequest($requestContent);
        }

        // second: setting the request model
        /** @var RequestModel $requestModelRest */
        $requestModelRest = $this->apiM->serializer(true)->deserialize($requestContent, RequestModel::class, $format);

        if (null === $requestModelRest->getAdvancedSearch()) {
            throw new \Exception('Invalid XML');
        }

        // fire request
        $responseUserType = $this->sendXmlRequest($requestModelRest->getAdvancedSearch());

        // store everything we got safely into our final response object
//        $this->restResponseModel->setUser($responseUserType);
        $this->restResponseModel = $responseUserType;

        // reset the main code status
        $this->restResponseModel->setCode($this->oneResponseSuccess ? 200 : 400);


        // manually serialize the return object into JSON or XML, as the auto-serialisation of the REST view will
        // the default naming strategy (i.e. use names from annotation) but due to the shared models we need it
        // serialized with then "identical property name" strategy
        $resposeOutput = $this->normalizeXmlListsForResponse($this->apiM->serializer(true)->serialize($this->restResponseModel,
            $format));

        // since we already have a serialized JSOM or XML string now, we have to set the output type to the closed we
        // have to plain text, which is HTML, as JSON or XML format will escape the already processed JSON/XML string
        // and encapsule it in another JSON/XML root, rendering it unusable
        //
        // I am sure I will be told fairly soon if there is a better way to do this ;-) but for now, this works.
        return $this->apiM->restView()
            ->setData(['data' => $resposeOutput])
            ->setStatusCode($this->oneResponseSuccess ? 200 : 400)
            ->setFormat('html')
            ->setHeader('content-type', 'application/' . $format);
    }

    /**
     * To whomever stumbles across this: LOOK AWAY! ;-)
     *
     * @param $requestContent
     * @return mixed
     */
    private function normalizeXmlListsForResponse($requestContent)
    {
        $returnData = $requestContent;

        $returnData = str_replace('ApplicationType>', 'applicationType>', $returnData);
        $returnData = str_replace('AttributeType>', 'attributeType>', $returnData);
        $returnData = str_replace('ApplicationRoleType>', 'applicationRoleType>', $returnData);

        return $returnData;
    }

    /**
     * To whomever stumbles across this: LOOK AWAY! ;-)
     *
     * @param $requestContent
     * @return mixed
     */
    private function normalizeXmlListsForRequest($requestContent)
    {
        $returnData = $requestContent;

        $returnData = str_replace('filter>', 'Filter>', $returnData);

        return $returnData;
    }

    /**
     * @param AdvancedSearch $requestModelRest
     * @return ResponseUserType
     */
    private function sendXmlRequest(AdvancedSearch $requestModelRest)
    {
        $requestModel = $this->apiM->modelXml()->request()->userWs()->version2()->userSearch()->advanced()->request();

        // add credentials
        $credentialsModelXml = $this->apiM->modelXml()->request()->userWs()->version2()->userSearch()->advanced()->credentials();
        $credentialsModelXml->setCredentialsData(
            $this->userToken->servicetoken,
            'UserSearch',
            $this->userToken->useridentifier,
            $this->userToken->username,
            'en'
        );
        $requestModel->setCredentials($credentialsModelXml);

        $userSearch = $this->apiM->modelXml()->request()->userWs()->version2()->userSearch()->advanced()->userSearch();
        $searchAdvanced = $this->apiM->modelXml()->request()->userWs()->version2()->userSearch()->advanced()->advanced();

        // set advanced search parameters
        $searchAdvanced
            ->setOffset($requestModelRest->getOffset())
            ->setLimit($requestModelRest->getLimit())
            ->setOrderBy($requestModelRest->getOrderBy())
            ->setOrderDir($requestModelRest->getOrderDir())
            ->setFullResponse($requestModelRest->getFullResponse())
            ->setFilters($requestModelRest->getFilters());
        $userSearch->setAdvanced($searchAdvanced);

        // set advanced search
        $requestModel->setUserSearch($userSearch);

        // send request
        try {
            $clientResponse = $this->apiM->client()->post(
                $this->apiM->configuration()->userWs()->getBaseUrl() . $this->apiM->configuration()->userWs()->getServiceAction(),
                $this->apiM->clientHelper()->options()->getOptions(
                    2,
                    'UserSearch',
                    'Advanced',
                    $this->apiM->serializer()->serialize($requestModel, 'xml')
                )
            );
        } catch (RequestException $ex) {
            $errorResponse = $ex->getResponse()->getBody()->getContents();
            $this->apiM->logger()->error($errorResponse);

            return $this->buildItem([static::STATUS => false, 'xml' => $errorResponse]);
        }

        return $this->buildItem([static::STATUS => true, 'xml' => $clientResponse->getBody()->getContents()]);
    }

    /**
     *
     * @param string $xmlResponse
     * @return ResponseUserType
     */
    private function buildItem($xmlResponse)
    {
        if (!$xmlResponse[static::STATUS]) {
            // build error response for a userApplication
            $view = $this->buildError($xmlResponse['xml'], $this->restResponseModel);
        } else {
            // fill the success response for a userApplication
            $view = $this->buildSuccess($xmlResponse['xml'], $this->restResponseModel);
        }

        return $view;
    }

    /**
     * @param string $xmlString
     * @param ResponseUserType $responseItem
     * @return ResponseUserType
     */
    private function buildSuccess($xmlString, ResponseUserType $responseItem)
    {
        $this->successXmlEntity = $this->apiM->serializer()->deserialize($xmlString, SuccessXMLResponse::class, 'xml');

        if ($this->successXmlEntity->getCode() != 200) {
            return $this->buildError($xmlString, $responseItem);
        }

        $users = $this->successXmlEntity->getUserSearch()->getAdvanced()->getUsers();

        $this->oneResponseSuccess = true;

        $responseItem->setCount($this->successXmlEntity->getUserSearch()->getAdvanced()->getCount());
        $responseItem->setOffset($this->successXmlEntity->getUserSearch()->getAdvanced()->getOffset());
        $responseItem->setLimit($this->successXmlEntity->getUserSearch()->getAdvanced()->getLimit());
        $responseItem->setFullResponse($this->successXmlEntity->getUserSearch()->getAdvanced()->getFullResponse());
        $responseItem->setOrderBy($this->successXmlEntity->getUserSearch()->getAdvanced()->getOrderBy());
        $responseItem->setOrderDir($this->successXmlEntity->getUserSearch()->getAdvanced()->getOrderDir());

        if (null !== $users) {
            foreach ($users->getUsers() as $user) {
                $responseItem->addUser($user);
            }
        }

        // Add additional fields to response
        $responseItem
            ->setCode(200)
            ->setTrackId($this->successXmlEntity->getTrackId())
            ->setDate($this->successXmlEntity->getDate())
            ->setScriptTimeSec($this->apiM->scriptTimeSeconds());

        return $responseItem;
    }

    /**
     * @param ResponseUserType $responseItem
     * @param string $xmlString
     * @return ResponseUserType
     */
    private function buildError($xmlString, ResponseUserType $responseItem)
    {
        $this->errorXmlEntity = $this->apiM->serializer()->deserialize($xmlString, ErrorXMLResponse::class, 'xml');
        $errors = $this->errorXmlEntity->getErrors();

        foreach ($errors as $error) {
            $errorItem = $this->apiM->modelRest()->response()->userWs()->version2()->userSearch()->advanced()->error();
            $message = 'ErrorCode: ' . $error->getErrorCode() . ' ErrorRef: ' . $error->getErrorRef() . ' ShortMessage: ' . $error->getShortMessage();
            $errorItem->setMessage($message);
            $errorItem->setCode(400);
            $errorItem->setException(static::ERROR_EXTERNAL);
            $responseItem->setError($errorItem);
        }

        // Add additional fields to response
        $responseItem
            ->setCode(400)
            ->setTrackId($this->errorXmlEntity->getTrackId())
            ->setDate($this->errorXmlEntity->getDate())
            ->setScriptTimeSec($this->apiM->scriptTimeSeconds());

        // Add additional fields to error response
        $responseItem->setCode(400);

        return $responseItem;
    }
}
