"""
Views for the credit Django app.
"""
from __future__ import unicode_literals

import datetime
import logging

import pytz
from django.conf import settings
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from edx_rest_framework_extensions.authentication import JwtAuthentication
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import viewsets, mixins, permissions, views, generics
from rest_framework.authentication import SessionAuthentication
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework_oauth.authentication import OAuth2Authentication

from openedx.core.djangoapps.credit.api import create_credit_request
from openedx.core.djangoapps.credit.exceptions import (
    UserNotEligibleException, InvalidCourseKey, CreditApiBadRequest, InvalidCreditRequest,
)
from openedx.core.djangoapps.credit.models import (
    CreditCourse, CreditProvider, CREDIT_PROVIDER_ID_REGEX,
    CreditEligibility, CreditRequest,
)
from openedx.core.djangoapps.credit.serializers import (
    CreditCourseSerializer, CreditProviderSerializer,
    CreditEligibilitySerializer, CreditProviderCallbackSerializer,
)
from openedx.core.lib.api.mixins import PutAsCreateMixin
from openedx.core.lib.api.permissions import IsStaffOrOwner

log = logging.getLogger(__name__)
AUTHENTICATION_CLASSES = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,)


class CreditProviderViewSet(viewsets.ReadOnlyModelViewSet):
    """ Credit provider endpoints. """

    lookup_field = 'provider_id'
    lookup_value_regex = CREDIT_PROVIDER_ID_REGEX
    authentication_classes = AUTHENTICATION_CLASSES
    pagination_class = None
    permission_classes = (permissions.IsAuthenticated,)
    queryset = CreditProvider.objects.all()
    serializer_class = CreditProviderSerializer

    def filter_queryset(self, queryset):
        queryset = super(CreditProviderViewSet, self).filter_queryset(queryset)

        # Filter by provider ID
        provider_ids = self.request.GET.get('provider_ids', None)

        if provider_ids:
            provider_ids = provider_ids.split(',')
            queryset = queryset.filter(provider_id__in=provider_ids)

        return queryset


class CreditProviderRequestCreateView(views.APIView):
    """ Creates a credit request for the given user and course, if the user is eligible for credit."""

    authentication_classes = AUTHENTICATION_CLASSES
    permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner,)

    def post(self, request, provider_id):
        """ POST handler. """
        # Get the provider, or return HTTP 404 if it doesn't exist
        provider = generics.get_object_or_404(CreditProvider, provider_id=provider_id)

        # Validate the course key
        course_key = request.data.get('course_key')
        try:
            course_key = CourseKey.from_string(course_key)
        except InvalidKeyError:
            raise InvalidCourseKey(course_key)

        # Validate the username
        username = request.data.get('username')
        if not username:
            raise ValidationError({'detail': 'A username must be specified.'})

        # Ensure the user is actually eligible to receive credit
        if not CreditEligibility.is_user_eligible_for_credit(course_key, username):
            raise UserNotEligibleException(course_key, username)

        try:
            credit_request = create_credit_request(course_key, provider.provider_id, username)
            return Response(credit_request)
        except CreditApiBadRequest as ex:
            raise InvalidCreditRequest(ex.message)


class CreditProviderCallbackView(views.APIView):
    """ Callback used by credit providers to update credit request status. """

    # This endpoint should be open to all external credit providers.
    authentication_classes = ()
    permission_classes = ()

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(CreditProviderCallbackView, self).dispatch(request, *args, **kwargs)

    def post(self, request, provider_id):
        """ POST handler. """
        provider = generics.get_object_or_404(CreditProvider, provider_id=provider_id)
        data = request.data

        # Ensure the input data is valid
        serializer = CreditProviderCallbackSerializer(data=data, provider=provider)
        serializer.is_valid(raise_exception=True)

        # Update the credit request status
        request_uuid = data['request_uuid']
        new_status = data['status']
        credit_request = generics.get_object_or_404(CreditRequest, uuid=request_uuid, provider=provider)
        old_status = credit_request.status
        credit_request.status = new_status
        credit_request.save()

        log.info(
            'Updated [%s] CreditRequest [%s] from status [%s] to [%s].',
            provider_id, request_uuid, old_status, new_status
        )

        return Response()


class CreditEligibilityView(generics.ListAPIView):
    """ Returns eligibility for a user-course combination. """

    authentication_classes = AUTHENTICATION_CLASSES
    pagination_class = None
    permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner)
    serializer_class = CreditEligibilitySerializer
    queryset = CreditEligibility.objects.all()

    def filter_queryset(self, queryset):
        username = self.request.GET.get('username')
        course_key = self.request.GET.get('course_key')

        if not (username and course_key):
            raise ValidationError(
                {'detail': 'Both the course_key and username querystring parameters must be supplied.'})

        course_key = unicode(course_key)

        try:
            course_key = CourseKey.from_string(course_key)
        except InvalidKeyError:
            raise ValidationError({'detail': '[{}] is not a valid course key.'.format(course_key)})
        return queryset.filter(
            username=username,
            course__course_key=course_key,
            deadline__gt=datetime.datetime.now(pytz.UTC)
        )


class CreditCourseViewSet(PutAsCreateMixin, mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
    """ CreditCourse endpoints. """

    lookup_field = 'course_key'
    lookup_value_regex = settings.COURSE_KEY_REGEX
    queryset = CreditCourse.objects.all()
    serializer_class = CreditCourseSerializer
    authentication_classes = AUTHENTICATION_CLASSES
    permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser)

    # In Django Rest Framework v3, there is a default pagination
    # class that transmutes the response data into a dictionary
    # with pagination information.  The original response data (a list)
    # is stored in a "results" value of the dictionary.
    # For backwards compatibility with the existing API, we disable
    # the default behavior by setting the pagination_class to None.
    pagination_class = None

    # This CSRF exemption only applies when authenticating without SessionAuthentication.
    # SessionAuthentication will enforce CSRF protection.
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(CreditCourseViewSet, self).dispatch(request, *args, **kwargs)

    def get_object(self):
        # Convert the serialized course key into a CourseKey instance
        # so we can look up the object.
        course_key = self.kwargs.get(self.lookup_field)
        if course_key is not None:
            self.kwargs[self.lookup_field] = CourseKey.from_string(course_key)

        return super(CreditCourseViewSet, self).get_object()
