"""
Module provides elements to be used in third-party auth pipeline.
"""
from __future__ import absolute_import, unicode_literals

from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _

from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser, UserDataSharingConsentAudit
from enterprise.utils import NotConnectedToOpenEdX

try:
    from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
except ImportError:
    configuration_helpers = None

try:
    from social_core.pipeline.partial import partial
except ImportError:
    from enterprise.decorators import null_decorator as partial  # pylint:disable=ungrouped-imports

try:
    from third_party_auth.pipeline import get as get_partial_pipeline
except ImportError:
    get_partial_pipeline = None

try:
    from third_party_auth.provider import Registry
except ImportError:
    Registry = None


def verify_third_party_auth_dependencies():
    """
    Ensure that all necessary third_party_auth dependencies are present.
    """
    third_party_dependencies = {
        'Registry': Registry,
        'get_partial_pipeline': get_partial_pipeline,
    }

    for dependency in third_party_dependencies:
        if third_party_dependencies[dependency] is None:
            raise NotConnectedToOpenEdX(
                _("The following dependency must be installed in an Open edX environment to look up third-party-auth "
                  "dependencies.")
                + "\nUnavailable: {dependency}".format(dependency=dependency)
            )


def get_enterprise_customer_for_request(request):
    """
    Get the EnterpriseCustomer associated with a particular request.
    """
    verify_third_party_auth_dependencies()
    pipeline = get_partial_pipeline(request)
    return get_enterprise_customer_for_running_pipeline(pipeline)


def get_enterprise_customer_for_running_pipeline(pipeline):  # pylint: disable=invalid-name
    """
    Get the EnterpriseCustomer associated with a running pipeline.
    """
    verify_third_party_auth_dependencies()
    if pipeline is None:
        return None
    provider = Registry.get_from_pipeline(pipeline)
    return get_enterprise_customer_for_sso(provider)


def get_enterprise_customer_for_sso(provider):
    """
    Get the EnterpriseCustomer object tied to an identity provider.
    """
    if provider is None:
        return None
    try:
        return EnterpriseCustomer.objects.get(  # pylint: disable=no-member
            enterprise_customer_identity_provider__provider_id=provider.provider_id
        )
    except EnterpriseCustomer.DoesNotExist:
        return None


def active_provider_enforces_data_sharing(request, enforcement_location):
    """
    Check whether the active provider enforces data sharing consent.

    Determine two things - first, whether there's an active third-party
    identity provider currently running, and second, if that active provider
    enforces data sharing consent at the given point in order to proceed.

    Args:
        request: HttpRequest object containing request data.
        enforcement_location (str): the point where to see data sharing consent state.
        argument can either be "optional", 'at_login' or 'at_enrollment'

    """
    running_pipeline = request.session.get('partial_pipeline_token')
    if running_pipeline:
        customer = get_enterprise_customer_for_request(request)
        return customer and customer.enforces_data_sharing_consent(enforcement_location)
    return False


def active_provider_requests_data_sharing(request):
    """
    Determine if the active EnterpriseCustomer requests data sharing consent.
    """
    running_pipeline = request.session.get('partial_pipeline_token')
    if running_pipeline:
        customer = get_enterprise_customer_for_request(request)
        return customer and customer.requests_data_sharing_consent
    return False


def get_consent_status_for_pipeline(pipeline):
    """
    Get the consent object for the current pipeline.
    """
    customer = get_enterprise_customer_for_running_pipeline(pipeline)
    user = pipeline.kwargs['user']
    try:
        return UserDataSharingConsentAudit.objects.get(
            user__user_id=user.id,
            user__enterprise_customer=customer,
        )
    except UserDataSharingConsentAudit.DoesNotExist:
        return None


@partial
def handle_enterprise_logistration(backend, user, **kwargs):
    """
    Perform tasks related to Enterprise-owned SSO signin requests.

    If the enterprise customer associated with the active SSO provider requires data sharing consent at login, ensure
    that the user has provided consent.

    If consent is required, and the user has not provided consent, then the user will be redirected to a page from which
    they can provide consent.

    If consent is not required, then the user in the process of logging in is linked to the Enterprise Customer.

    Args:
        backend: The class handling the SSO interaction (SAML, OAuth, etc)
        user: The user object in the process of being logged in with
        **kwargs: Any remaining pipeline variables

    Returns:
        redirect: If consent is required at login, but has not been provided, the user is redirected to a page where
            they can provide consent.

    """
    def redirect_to_consent():
        """
        Redirect the user to a consent page.

        Method redirects the user to a page from which they can provide
        required data sharing consent before proceeding in the pipeline
        """
        return redirect(reverse('grant_data_sharing_permissions'))

    enterprise_customer = get_enterprise_customer_for_running_pipeline({'backend': backend.name, 'kwargs': kwargs})
    if enterprise_customer is None:
        # This pipeline element is not being activated as a part of an Enterprise logistration
        return

    if not (enterprise_customer.requests_data_sharing_consent and
            enterprise_customer.enforce_data_sharing_consent == EnterpriseCustomer.AT_LOGIN):
        # This enterprise customer attached to this pipeline element does not request data sharing consent at login;
        # proceed with the creation of a link between the user and the enterprise customer, then exit.
        enterprise_customer_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            enterprise_customer=enterprise_customer,
            user_id=user.id
        )

        platform_name = configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
        messages.success(
            backend.strategy.request,
            _('{span_start}Account created{span_end} Thank you for creating an account with {platform_name}.').format(
                platform_name=platform_name,
                span_start='<span>',
                span_end='</span>',
            )
        )
        if not user.is_active:
            messages.info(
                backend.strategy.request,
                _(
                    '{span_start}Activate your account{span_end} Check your inbox for an activation email. '
                    'You will not be able to log back into your account until you have activated it.'
                ).format(span_start='<span>', span_end='</span>')
            )

        if (enterprise_customer.enforce_data_sharing_consent == EnterpriseCustomer.EXTERNALLY_MANAGED
                and enterprise_customer.enable_data_sharing_consent):

            UserDataSharingConsentAudit.objects.update_or_create(
                user=enterprise_customer_user,
                defaults={
                    'state': UserDataSharingConsentAudit.EXTERNALLY_MANAGED,
                }
            )

        return

    try:
        # Find an existing account-level consent record for the user
        consent = UserDataSharingConsentAudit.objects.get(
            user__user_id=user.id,
            user__enterprise_customer=enterprise_customer,
        )
    except UserDataSharingConsentAudit.DoesNotExist:
        return redirect_to_consent()

    if (not consent.enabled) and enterprise_customer.enforces_data_sharing_consent(EnterpriseCustomer.AT_LOGIN):
        # If consent has been declined, and the enterprise customer requires it, redirect to get it.
        return redirect_to_consent()
