"""
    OpenID Connect authentication backends for Django REST framework
    ================================================================

    This modules defines backends allowing to authenticate a user using tokens generated by an
    OpenID Connect Provider (OP).

"""

import requests
from django.utils.encoding import smart_text
from requests.exceptions import HTTPError
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import AuthenticationFailed

from oidc_rp.backends import create_oidc_user_from_claims, update_oidc_user_from_claims
from oidc_rp.conf import settings as oidc_rp_settings
from oidc_rp.models import OIDCUser
from oidc_rp.signals import oidc_user_created


class BearerTokenAuthentication(BaseAuthentication):
    """ Allows to authenticate users using a bearer token coming from an OP.

    This authentication backend is able to authorize users by using an access token to fetch user
    information from an OpenID Connect Provider (OP). If the access token does not allow to fetch
    such information then it means that the user should not be authenticated.

    """

    www_authenticate_realm = 'api'

    def authenticate(self, request):
        """ Authenticates users using a provided Bearer token. """
        # First step, retrieves the Bearer token from the authorization header.
        auth = get_authorization_header(request).split()
        if not auth or smart_text(auth[0].lower()) != 'bearer':
            return

        if len(auth) == 1:
            raise AuthenticationFailed('Invalid authorization header; no bearer token provided')
        elif len(auth) > 2:
            raise AuthenticationFailed('Invalid authorization header; many bearer tokens provided')

        bearer_token = smart_text(auth[1])

        # Tries to retrieve user information from the OP.
        try:
            userinfo_response = requests.get(
                oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT,
                headers={'Authorization': 'Bearer {0}'.format(bearer_token)})
            userinfo_response.raise_for_status()
        except HTTPError:
            raise AuthenticationFailed('Bearer token seems invalid or expired.')
        userinfo_response_data = userinfo_response.json()

        # Tries to retrieve a corresponding user in the local database and creates it if applicable.
        try:
            oidc_user = OIDCUser.objects.select_related('user').get(
                sub=userinfo_response_data.get('sub'))
        except OIDCUser.DoesNotExist:
            oidc_user = create_oidc_user_from_claims(userinfo_response_data)
            oidc_user_created.send(sender=self.__class__, request=request, oidc_user=oidc_user)
        else:
            update_oidc_user_from_claims(oidc_user, userinfo_response_data)

        return oidc_user.user, bearer_token
