"""
Support tool for changing course enrollments.
"""
from __future__ import absolute_import

import six
from django.contrib.auth.models import User
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponseBadRequest
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import View
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework.generics import GenericAPIView
from six import text_type

from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response
from lms.djangoapps.support.decorators import require_support_permission
from lms.djangoapps.support.serializers import ManualEnrollmentSerializer
from lms.djangoapps.verify_student.models import VerificationDeadline
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_attribute_values
from openedx.core.djangoapps.enrollments.api import get_enrollments, update_enrollment
from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError
from openedx.core.djangoapps.enrollments.serializers import ModeSerializer
from student.models import ENROLLED_TO_ENROLLED, CourseEnrollment, CourseEnrollmentAttribute, ManualEnrollmentAudit
from util.json_request import JsonResponse


class EnrollmentSupportView(View):
    """
    View for viewing and changing learner enrollments, used by the
    support team.
    """

    @method_decorator(require_support_permission)
    def get(self, request):
        """Render the enrollment support tool view."""
        return render_to_response('support/enrollment.html', {
            'username': request.GET.get('user', ''),
            'enrollmentsUrl': reverse('support:enrollment_list'),
            'enrollmentSupportUrl': reverse('support:enrollment')
        })


class EnrollmentSupportListView(GenericAPIView):
    """
    Allows viewing and changing learner enrollments by support
    staff.
    """
    # TODO: ARCH-91
    # This view is excluded from Swagger doc generation because it
    # does not specify a serializer class.
    exclude_from_schema = True

    @method_decorator(require_support_permission)
    def get(self, request, username_or_email):
        """
        Returns a list of enrollments for the given user, along with
        information about previous manual enrollment changes.
        """
        try:
            user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
        except User.DoesNotExist:
            return JsonResponse([])

        enrollments = get_enrollments(user.username, include_inactive=True)
        for enrollment in enrollments:
            # Folds the course_details field up into the main JSON object.
            enrollment.update(**enrollment.pop('course_details'))
            course_key = CourseKey.from_string(enrollment['course_id'])
            # get the all courses modes and replace with existing modes.
            enrollment['course_modes'] = self.get_course_modes(course_key)
            # Add the price of the course's verified mode.
            self.include_verified_mode_info(enrollment, course_key)
            # Add manual enrollment history, if it exists
            enrollment['manual_enrollment'] = self.manual_enrollment_data(enrollment, course_key)
        return JsonResponse(enrollments)

    @method_decorator(require_support_permission)
    def post(self, request, username_or_email):
        """Allows support staff to alter a user's enrollment."""
        try:
            user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
            course_id = request.data['course_id']
            course_key = CourseKey.from_string(course_id)
            old_mode = request.data['old_mode']
            new_mode = request.data['new_mode']
            reason = request.data['reason']
            enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key)
            if enrollment.mode != old_mode:
                return HttpResponseBadRequest(u'User {username} is not enrolled with mode {old_mode}.'.format(
                    username=user.username,
                    old_mode=old_mode
                ))
        except KeyError as err:
            return HttpResponseBadRequest(u'The field {} is required.'.format(text_type(err)))
        except InvalidKeyError:
            return HttpResponseBadRequest(u'Could not parse course key.')
        except (CourseEnrollment.DoesNotExist, User.DoesNotExist):
            return HttpResponseBadRequest(
                u'Could not find enrollment for user {username} in course {course}.'.format(
                    username=username_or_email,
                    course=six.text_type(course_key)
                )
            )
        try:
            # Wrapped in a transaction so that we can be sure the
            # ManualEnrollmentAudit record is always created correctly.
            with transaction.atomic():
                update_enrollment(user.username, course_id, mode=new_mode, include_expired=True)
                manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit(
                    request.user,
                    enrollment.user.email,
                    ENROLLED_TO_ENROLLED,
                    reason=reason,
                    enrollment=enrollment
                )
                if new_mode == CourseMode.CREDIT_MODE:
                    provider_ids = get_credit_provider_attribute_values(course_key, 'id')
                    credit_provider_attr = {
                        'namespace': 'credit',
                        'name': 'provider_id',
                        'value': provider_ids[0],
                    }
                    CourseEnrollmentAttribute.add_enrollment_attr(
                        enrollment=enrollment, data_list=[credit_provider_attr]
                    )
                return JsonResponse(ManualEnrollmentSerializer(instance=manual_enrollment).data)
        except CourseModeNotFoundError as err:
            return HttpResponseBadRequest(text_type(err))

    @staticmethod
    def include_verified_mode_info(enrollment_data, course_key):
        """
        Add information about the verified mode for the given
        `course_key`, if that course has a verified mode.

        Args:
          enrollment_data (dict): Dictionary representing a single enrollment.
          course_key (CourseKey): The course which this enrollment belongs to.

        Returns:
          None
        """
        course_modes = enrollment_data['course_modes']
        for mode in course_modes:
            if mode['slug'] == CourseMode.VERIFIED:
                enrollment_data['verified_price'] = mode['min_price']
                enrollment_data['verified_upgrade_deadline'] = mode['expiration_datetime']
                enrollment_data['verification_deadline'] = VerificationDeadline.deadline_for_course(course_key)

    @staticmethod
    def manual_enrollment_data(enrollment_data, course_key):
        """
        Returns serialized information about the manual enrollment
        belonging to this enrollment, if it exists.

        Args:
          enrollment_data (dict): Representation of a single course enrollment.
          course_key (CourseKey): The course for this enrollment.

        Returns:
          None: If no manual enrollment change has been made.
          dict: Serialization of the latest manual enrollment change.
        """
        user = User.objects.get(username=enrollment_data['user'])
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment)
        if manual_enrollment_audit is None:
            return {}
        return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data

    @staticmethod
    def get_course_modes(course_key):
        """
        Returns a list of all modes including expired modes for a given course id

        Arguments:
            course_id (CourseKey): Search for course modes for this course.

        Returns:
            list of `Mode`

        """
        course_modes = CourseMode.modes_for_course(
            course_key,
            include_expired=True,
            exclude_credit=False
        )
        return [
            ModeSerializer(mode).data
            for mode in course_modes
        ]
