from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, login
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.http import require_POST
from social_core.actions import do_auth, do_complete, do_disconnect
from social_core.utils import setting_name

from .utils import maybe_require_post, psa

NAMESPACE = getattr(settings, setting_name("URL_NAMESPACE"), None) or "social"

# Calling `session.set_expiry(None)` results in a session lifetime equal to
# platform default session lifetime.
DEFAULT_SESSION_TIMEOUT = None


@never_cache
@maybe_require_post
@psa(f"{NAMESPACE}:complete")
def auth(request, backend):
    return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME)


@never_cache
@csrf_exempt
@psa(f"{NAMESPACE}:complete")
def complete(request, backend, *args, **kwargs):
    """Authentication complete view"""
    return do_complete(
        request.backend,
        _do_login,
        user=request.user,
        redirect_name=REDIRECT_FIELD_NAME,
        request=request,
        *args,
        **kwargs,
    )


@never_cache
@login_required
@psa()
@require_POST
@csrf_protect
def disconnect(request, backend, association_id=None):
    """Disconnects given backend from current logged in user."""
    return do_disconnect(request.backend, request.user, association_id, redirect_name=REDIRECT_FIELD_NAME)


def get_session_timeout(social_user, enable_session_expiration=False, max_session_length=None):
    if enable_session_expiration:
        # Retrieve an expiration date from the social user who just finished
        # logging in; this value was set by the social auth backend, and was
        # typically received from the server.
        expiration = social_user.expiration_datetime()

        # We've enabled session expiration. Check to see if we got
        # a specific expiration time from the provider for this user;
        # if not, use the platform default expiration.
        if expiration:
            received_expiration_time = expiration.total_seconds()
        else:
            received_expiration_time = DEFAULT_SESSION_TIMEOUT

        # Check to see if the backend set a value as a maximum length
        # that a session may be; if they did, then we should use the minimum
        # of that and the received session expiration time, if any, to
        # set the session length.
        if received_expiration_time is None and max_session_length is None:
            # We neither received an expiration length, nor have a maximum
            # session length. Use the platform default.
            session_expiry = DEFAULT_SESSION_TIMEOUT
        elif received_expiration_time is None and max_session_length is not None:
            # We only have a maximum session length; use that.
            session_expiry = max_session_length
        elif received_expiration_time is not None and max_session_length is None:
            # We only have an expiration time received by the backend
            # from the provider, with no set maximum. Use that.
            session_expiry = received_expiration_time
        else:
            # We received an expiration time from the backend, and we also
            # have a set maximum session length. Use the smaller of the two.
            session_expiry = min(received_expiration_time, max_session_length)
    else:
        # If there's an explicitly-set maximum session length, use that
        # even if we don't want to retrieve session expiry times from
        # the backend. If there isn't, then use the platform default.
        if max_session_length is None:
            session_expiry = DEFAULT_SESSION_TIMEOUT
        else:
            session_expiry = max_session_length

    return session_expiry


def _do_login(backend, user, social_user):
    user.backend = f"{backend.__module__}.{backend.__class__.__name__}"
    # Get these details early to avoid any issues involved in the
    # session switch that happens when we call login().
    enable_session_expiration = backend.setting("SESSION_EXPIRATION", False)
    max_session_length_setting = backend.setting("MAX_SESSION_LENGTH", None)

    # Log the user in, creating a new session.
    login(backend.strategy.request, user)

    # Make sure that the max_session_length value is either an integer or
    # None. Because we get this as a setting from the backend, it can be set
    # to whatever the backend creator wants; we want to be resilient against
    # unexpected types being presented to us.
    try:
        max_session_length = int(max_session_length_setting)
    except (TypeError, ValueError):
        # We got a response that doesn't look like a number; use the default.
        max_session_length = None

    # Get the session expiration length based on the maximum session length
    # setting, combined with any session length received from the backend.
    session_expiry = get_session_timeout(
        social_user,
        enable_session_expiration=enable_session_expiration,
        max_session_length=max_session_length,
    )

    try:
        # Set the session length to our previously determined expiry length.
        backend.strategy.request.session.set_expiry(session_expiry)
    except OverflowError:
        # The timestamp we used wasn't in the range of values supported by
        # Django for session length; use the platform default. We tried.
        backend.strategy.request.session.set_expiry(DEFAULT_SESSION_TIMEOUT)
