<?php

namespace Cobblestone\CcLogin\Security\Provider;

use Exception;
use Cobblestone\CcLogin\Security\CCUser;
use Cobblestone\CcLogin\Service\AssetService;
use Cobblestone\CcLogin\Service\RoleService;
use Cobblestone\CcLogin\Service\UserService;
use Symfony\Component\HttpFoundation\Request;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Class CCUserProvider
 *
 * Provider for users that log in through Auth0 and exists in the CC URM (Cobblestone Cloud UserRightsManagement)
 * When users log in through Auth0, se call the /me endpoint in CC-URM to fetch user details.
 *
 * @package Cobblestone\CcLogin\Security\Provider
 */
class CCUserProvider extends OAuthUserProvider implements UserProviderInterface, OAuthAwareUserProviderInterface
{

    /**
     * @var AssetService $assetService
     */
    protected $assetService;

    /**
     * @var string
     */
    protected $applicationId;

    /**
     * @var string
     */
    protected $userCloudDomain;

    /**
     * @var UserService $userService
     */
    protected $userService;

    /**
     * @var RoleService $roleService
     */
    protected $roleService;

    /**
     * @var Request
     */
    protected $request;

    public function __construct
    (
        AssetService $assetService,
        UserService $userService,
        RoleService $roleService,
        $apiApplicationId,
        $userCloudDomain
    )
    {
        $this->assetService = $assetService;
        $this->userService = $userService;
        $this->roleService = $roleService;
        $this->applicationId = $apiApplicationId;
        $this->userCloudDomain = $userCloudDomain;
    }

    public function setRequest(RequestStack $request_stack)
    {
        $this->request = $request_stack->getCurrentRequest();
    }


    /**
     * Loaduser by username is not supported, since part of the User class requires the id_token from Auth0, and we have
     * no way to get that if we only have the username. The method is not called unless we use remember_me or sim d qulate user
     * so no harm done.
     *
     * @see https://symfony.com/doc/current/security/user_provider.html#user-session-refresh
     *
     * @param $username
     * @throws \Exception
     */
    public function loadUserByUsername($username)
    {
        throw new \Exception('Unable to loadUserByUsername. Since we need the Auth0 id_token, we can not support loadUserByUsername. ' . __FILE__);
    }


    /**
     * @param UserResponseInterface $response
     * @return CCUser
     * @throws Exception
     */
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
    {
        $token = $response->getOAuthToken()->getAccessToken();
        $profileData = $this->userService->getProfileData($token);
        if (empty($profileData)) {
            throw new Exception("Unable to login. The user does not exists in the UserMGM API.");
        }

        $responseData = $response->getData();
        if ($responseData['email_verified'] === false) {
            $route = $this->userCloudDomain . '/not-verified?sub=' . $responseData['sub'];
            header("Location: ${route}");
            exit();
        }

        $assets = $this->assetService->getAssets($token, $this->applicationId);
        $assetRoles = $this->roleService->getRoles($token, $this->applicationId);
        $positions = $this->assetService->getAssetPositions($token);
        $userAssetPositions = $this->assetService->getUserAssetPositions($token);

        //The id_token use used in other places for display etc.
        $profileData['id_token'] = $response->getOAuthToken()->getRawToken()['id_token'];
        $user = new CCUser($response->getData()['email']);
        $user->setProfileData($profileData);

        // Set available assets on a user
        if ($assets) {
            $user->setAssets($assets);
        }

        if ($assetRoles) {
            $user->setAssetRoles($assetRoles);
        }

        if ($positions) {
            $user->setPositions($positions);
        }

        if ($userAssetPositions) {
            $user->setUserAssetPositions($userAssetPositions);
        }

        // Determine if the application user is listed as admin for this given application. We might need to add other roles
        // depending on the return data from the profiledata /me endpoint, but this will be implemented later.
        // @todo when the /me endoiun t always return the roles per application, this weird loop can be omitted
        if ($profileData && isset($profileData["application_admin"])) {
            $adminRole = array_filter($profileData["application_admin"], function ($obj) {
                return ($obj['application']["id"] == $this->applicationId);
            });
            if ($adminRole) {
                $user->setIsAdmin(true);
            }
        }

        // Set the locale. We need to set this on the session, since the UserObject is not available in our LocaleSubscriber
        // that sets the locale of the request.
        if ($profileData['locale'] && $this->request) {
            $this->request->getSession()->set('_locale', $profileData["locale"]);
        }

        return $user;
    }

    /**
     * Since we only update userdata on login, the refresh function can just return the same object, since we only update
     * or refresh data on new logins anway.
     *
     * @see https://symfony.com/doc/current/security/user_provider.html#user-session-refresh
     *
     * @param UserInterface $user
     * @return UserInterface
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$this->supportsClass(get_class($user))) {
            throw new UnsupportedUserException(sprintf('Unsupported user class "%s"', get_class($user)));
        }

        return $user;
    }

    /**
     * {@inheritdoc}
     */
    public function supportsClass($class)
    {
        return 'Cobblestone\CcLogin\Security\CCUser' === $class;
    }

}
