<?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 LifeStyle\DH\AccessControlBundle\Authorization;

use LifeStyle\DH\AccessControlBundle\Configuration\IamAccessConfiguration;
use LifeStyle\DH\AccessControlBundle\Configuration\ScopeAccessConfiguration;
use LifeStyle\DH\AccessControlBundle\Exception\InvalidConfigurationException;
use Sso\WebserviceBundle\Database\Webservice\User as UserDatabase;
use Sso\WebserviceBundle\Database\Webservice\Application as ApplicationDatabase;
use Sso\WebserviceBundle\Entity\Webservice\Type\Application;
use Sso\WebserviceBundle\Entity\Webservice\Type\User;
use Sso\WebserviceBundle\Entity\Webservice\Type\UserApplication;
use Sso\WebserviceBundle\Security\Authentication\Token\WsFirewallToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
 * Class UserScopesBuilder
 *
 * @copyright  2019 Lifestyle Webconsulting GmbH
 * @link       https://www.life-style.de
 * @package LifeStyle\DH\AccessControlBundle\Authorization
 */
class UserScopesBuilder
{
    /**
     * @var UserDatabase
     */
    private $userDatabase;

    /**
     * @var ApplicationDatabase
     */
    private $applicationDatabase;

    /**
     * @var TokenStorageInterface
     */
    private $tokenStorageInterface;

    /**
     * @var ScopeAccessConfiguration
     */
    private $scopeConfiguration;

    /**
     * @var IamAccessConfiguration
     */
    private $iamConfiguration;

    /**
     * UserScopesBuilder constructor.
     * @param UserDatabase $userDatabase
     * @param ApplicationDatabase $applicationDatabase
     * @param TokenStorageInterface $tokenStorageInterface
     * @param ScopeAccessConfiguration $scopeConfiguration
     * @param IamAccessConfiguration $iamConfiguration
     */
    public function __construct(
        UserDatabase $userDatabase,
        ApplicationDatabase $applicationDatabase,
        TokenStorageInterface $tokenStorageInterface,
        ScopeAccessConfiguration $scopeConfiguration,
        IamAccessConfiguration $iamConfiguration
    ) {
        $this->userDatabase = $userDatabase;
        $this->applicationDatabase = $applicationDatabase;
        $this->tokenStorageInterface = $tokenStorageInterface;
        $this->scopeConfiguration = $scopeConfiguration;
        $this->iamConfiguration = $iamConfiguration;
    }

    /**
     * @param User|null $user
     * @return UserScopes
     * @throws InvalidConfigurationException
     */
    public function build(?User $user = null): UserScopes
    {
        if (null === $user) {
            $token = $this->tokenStorageInterface->getToken();

            if ($token instanceof WsFirewallToken && !$token->accessRestricted) {
                throw new InvalidConfigurationException(sprintf(
                    'User scopes cannot be build for api tokens (%s...) without restricted-access has been set.',
                    substr($token->serviceToken, 0, 6)
                ));
            }

            $user = $this->userDatabase->getUserByUsername($token->applicationUsername);
        }

        $userScopeApplication = $this->findUserApplication($user, $this->scopeConfiguration->getApplicationName());
        $userIamApplication = $this->findUserApplication($user, $this->iamConfiguration->getApplicationName());

        $isGlobalHrManager = $this->getGlobalHrManager($userIamApplication);
        $scopeIds = $this->getScopeIds($userScopeApplication);
        $application = $this->getApplication($userScopeApplication);
        $attributeIds = $this->getApplicationAttributeIds($application);

        return new UserScopes(
            $isGlobalHrManager,
            $user->getId(),
            $scopeIds,
            $application->getId(),
            $attributeIds
        );
    }

    /**
     * @param User $user
     * @param string $applicationName
     * @return UserApplication|null
     */
    private function findUserApplication(User $user, string $applicationName): ?UserApplication
    {
        foreach ($user->getUserApplications() as $userApplication) {
            if ($userApplication->getActive() && $userApplication->getName() === $applicationName) {
                return $userApplication;
            }
        }

        return null;
    }

    /**
     * @param UserApplication|null $userApplication
     * @return bool
     */
    private function getGlobalHrManager(?UserApplication $userApplication): bool
    {
        if (null === $userApplication) {
            return false;
        }

        $roleName = $this->iamConfiguration->getGlobalHrManagerRole();
        foreach ($userApplication->getRoles() as $role) {
            if ($role->getActive() && $role->getName() === $roleName) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param UserApplication|null $userApplication
     * @return array
     */
    private function getScopeIds(?UserApplication $userApplication): array
    {
        if (null === $userApplication) {
            return [];
        }

        $scopeProperties = [
            strtolower($this->scopeConfiguration->getPrimaryScopeProperty()),
            strtolower($this->scopeConfiguration->getSecondaryScopeProperty()),
        ];

        $scopeIds = [];
        foreach ($userApplication->getAttributes() as $attribute) {
            if (in_array($attribute->getLowername(), $scopeProperties)) {
                $scopeIds[] = (string)$attribute->getValue();
            }
        }

        return $scopeIds;
    }

    /**
     * @param UserApplication|null $userApplication
     * @return Application
     * @throws InvalidConfigurationException
     */
    private function getApplication(?UserApplication $userApplication): Application
    {
        if (null !== $userApplication) {
            return $userApplication->getApplication();
        }

        $application = $this->applicationDatabase->getApplicationByName($this->scopeConfiguration->getApplicationName());
        if (null === $application) {
            throw new InvalidConfigurationException(sprintf(
                'User configuration application "%s "for scopes not found! ' .
                'Please check your configuration: ' .
                'life_style_dh_access_control.scope_configuration.application_name ' .
                'and check if application exists in your database.',
                $this->scopeConfiguration->getApplicationName()
            ));
        }

        return $application;
    }

    /**
     * @param Application $application
     * @return array
     */
    private function getApplicationAttributeIds(Application $application): array
    {
        $scopeProperties = [
            strtolower($this->scopeConfiguration->getPrimaryScopeProperty()),
            strtolower($this->scopeConfiguration->getSecondaryScopeProperty()),
        ];

        $attributeIds = [];
        foreach ($application->getAttributes() as $attribute) {
            if (in_array($attribute->getLowername(), $scopeProperties)) {
                $attributeIds[] = $attribute->getId();
            }
        }

        return $attributeIds;
    }
}
