from collections import OrderedDict

from ..exceptions import MissingBackend
from ..utils import module_member, user_is_authenticated
from .base import BaseAuth

# Cache for discovered backends.
BACKENDSCACHE = OrderedDict()


def load_backends(backends, force_load=False):
    """
    Load backends defined on SOCIAL_AUTH_AUTHENTICATION_BACKENDS, backends will
    be imported and cached on BACKENDSCACHE. The key in that dict will be the
    backend name, and the value is the backend class.

    Only subclasses of BaseAuth (and sub-classes) are considered backends.

    Previously there was a BACKENDS attribute expected on backends modules,
    this is not needed anymore since it's enough with the
    AUTHENTICATION_BACKENDS setting. BACKENDS was used because backends used to
    be split on two classes the authentication backend and another class that
    dealt with the auth mechanism with the provider, those classes are joined
    now.

    A force_load boolean argument is also provided so that get_backend
    below can retry a requested backend that may not yet be discovered.
    """
    global BACKENDSCACHE
    if force_load:
        BACKENDSCACHE = OrderedDict()
    if not BACKENDSCACHE:
        for auth_backend in backends:
            backend = module_member(auth_backend)
            if issubclass(backend, BaseAuth):
                BACKENDSCACHE[backend.name] = backend
    return BACKENDSCACHE


def get_backend(backends, name):
    """Returns a backend by name. Backends are stored in the BACKENDSCACHE
    cache dict. If not found, each of the modules referenced in
    AUTHENTICATION_BACKENDS is imported and checked for a BACKENDS
    definition. If the named backend is found in the module's BACKENDS
    definition, it's then stored in the cache for future access.
    """
    try:
        # Cached backend which has previously been discovered
        return BACKENDSCACHE[name]
    except KeyError:
        # Reload BACKENDS to ensure a missing backend hasn't been missed
        load_backends(backends, force_load=True)
        try:
            return BACKENDSCACHE[name]
        except KeyError:
            raise MissingBackend(name)


def user_backends_data(user, backends, storage):
    """
    Will return backends data for given user, the return value will have the
    following keys:
        associated: UserSocialAuth model instances for currently associated
                    accounts
        not_associated: Not associated (yet) backend names
        backends: All backend names.

    If user is not authenticated, then 'associated' list is empty, and there's
    no difference between 'not_associated' and 'backends'.
    """
    available = list(load_backends(backends).keys())
    values = {"associated": [], "not_associated": available, "backends": available}
    if user_is_authenticated(user):
        associated = storage.user.get_social_auth_for_user(user)
        not_associated = list(set(available) - {assoc.provider for assoc in associated})
        values["associated"] = associated
        values["not_associated"] = not_associated
    return values
