"""
Certificate end-points used by the student support UI.

See lms/djangoapps/support for more details.

"""
import logging
import urllib
from functools import wraps

from django.db import transaction
from django.db.models import Q
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseServerError
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_GET, require_POST
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey

from certificates import api
from certificates.models import CertificateInvalidation
from courseware.access import has_access
from lms.djangoapps.instructor_task.api import generate_certificates_for_students
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.models import CourseEnrollment, User
from util.json_request import JsonResponse
from xmodule.modulestore.django import modulestore

log = logging.getLogger(__name__)


def require_certificate_permission(func):
    """
    View decorator that requires permission to view and regenerate certificates.
    """
    @wraps(func)
    def inner(request, *args, **kwargs):  # pylint:disable=missing-docstring
        if has_access(request.user, "certificates", "global"):
            return func(request, *args, **kwargs)
        else:
            return HttpResponseForbidden()

    return inner


@require_GET
@require_certificate_permission
def search_certificates(request):
    """
    Search for certificates for a particular user OR along with the given course.

    Supports search by either username or email address along with course id.

    First filter the records for the given username/email and then filter against the given course id (if given).
    Show the 'Regenerate' button if a record found in 'generatedcertificate' model otherwise it will show the Generate
    button.

    Arguments:
        request (HttpRequest): The request object.

    Returns:
        JsonResponse

    Example Usage:
        GET /certificates/search?user=bob@example.com
        GET /certificates/search?user=bob@example.com&course_id=xyz

        Response: 200 OK
        Content-Type: application/json
        [
            {
                "username": "bob",
                "course_key": "edX/DemoX/Demo_Course",
                "type": "verified",
                "status": "downloadable",
                "download_url": "http://www.example.com/cert.pdf",
                "grade": "0.98",
                "created": 2015-07-31T00:00:00Z,
                "modified": 2015-07-31T00:00:00Z
            }
        ]

    """
    user_filter = urllib.unquote(urllib.quote_plus(request.GET.get("user", "")))
    if not user_filter:
        msg = _("user is not given.")
        return HttpResponseBadRequest(msg)

    try:
        user = User.objects.get(Q(email=user_filter) | Q(username=user_filter))
    except User.DoesNotExist:
        return HttpResponseBadRequest(_("user '{user}' does not exist").format(user=user_filter))

    certificates = api.get_certificates_for_user(user.username)
    for cert in certificates:
        cert["course_key"] = unicode(cert["course_key"])
        cert["created"] = cert["created"].isoformat()
        cert["modified"] = cert["modified"].isoformat()
        cert["regenerate"] = True

    course_id = urllib.quote_plus(request.GET.get("course_id", ""), safe=':/')
    if course_id:
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            return HttpResponseBadRequest(_("Course id '{course_id}' is not valid").format(course_id=course_id))
        else:
            try:
                if CourseOverview.get_from_id(course_key):
                    certificates = [certificate for certificate in certificates
                                    if certificate['course_key'] == course_id]
                    if not certificates:
                        return JsonResponse([{'username': user.username, 'course_key': course_id, 'regenerate': False}])
            except CourseOverview.DoesNotExist:
                msg = _("The course does not exist against the given key '{course_key}'").format(course_key=course_key)
                return HttpResponseBadRequest(msg)

    return JsonResponse(certificates)


def _validate_post_params(params):
    """
    Validate request POST parameters to the generate and regenerate certificates end-point.

    Arguments:
        params (QueryDict): Request parameters.

    Returns: tuple of (dict, HttpResponse)

    """
    # Validate the username
    try:
        username = params.get("username")
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        msg = _("User {username} does not exist").format(username=username)
        return None, HttpResponseBadRequest(msg)

    # Validate the course key
    try:
        course_key = CourseKey.from_string(params.get("course_key"))
    except InvalidKeyError:
        msg = _("{course_key} is not a valid course key").format(course_key=params.get("course_key"))
        return None, HttpResponseBadRequest(msg)

    return {"user": user, "course_key": course_key}, None


# Grades can potentially be written - if so, let grading manage the transaction.
@transaction.non_atomic_requests
@require_POST
@require_certificate_permission
def regenerate_certificate_for_user(request):
    """
    Regenerate certificates for a user.

    This is meant to be used by support staff through the UI in lms/djangoapps/support

    Arguments:
        request (HttpRequest): The request object

    Returns:
        HttpResponse

    Example Usage:

        POST /certificates/regenerate
            * username: "bob"
            * course_key: "edX/DemoX/Demo_Course"

        Response: 200 OK

    """
    # Check the POST parameters, returning a 400 response if they're not valid.
    params, response = _validate_post_params(request.POST)
    if response is not None:
        return response

    # Check that the course exists
    course = modulestore().get_course(params["course_key"])
    if course is None:
        msg = _("The course {course_key} does not exist").format(course_key=params["course_key"])
        return HttpResponseBadRequest(msg)

    # Check that the user is enrolled in the course
    if not CourseEnrollment.is_enrolled(params["user"], params["course_key"]):
        msg = _("User {username} is not enrolled in the course {course_key}").format(
            username=params["user"].username,
            course_key=params["course_key"]
        )
        return HttpResponseBadRequest(msg)

    # Attempt to regenerate certificates
    try:
        certificate = api.regenerate_user_certificates(params["user"], params["course_key"], course=course)
    except:  # pylint: disable=bare-except
        # We are pessimistic about the kinds of errors that might get thrown by the
        # certificates API.  This may be overkill, but we're logging everything so we can
        # track down unexpected errors.
        log.exception(
            "Could not regenerate certificates for user %s in course %s",
            params["user"].id,
            params["course_key"]
        )
        return HttpResponseServerError(_("An unexpected error occurred while regenerating certificates."))

    # Deactivate certificate invalidation by setting active to False.
    _deactivate_invalidation(certificate)

    log.info(
        "Started regenerating certificates for user %s in course %s from the support page.",
        params["user"].id, params["course_key"]
    )
    return HttpResponse(200)


@transaction.non_atomic_requests
@require_POST
@require_certificate_permission
def generate_certificate_for_user(request):
    """
    Generate certificates for a user.

    This is meant to be used by support staff through the UI in lms/djangoapps/support

    Arguments:
        request (HttpRequest): The request object

    Returns:
        HttpResponse

    Example Usage:

        POST /certificates/generate
            * username: "bob"
            * course_key: "edX/DemoX/Demo_Course"

        Response: 200 OK

    """
    # Check the POST parameters, returning a 400 response if they're not valid.
    params, response = _validate_post_params(request.POST)
    if response is not None:
        return response

    try:
        # Check that the course exists
        CourseOverview.get_from_id(params["course_key"])
    except CourseOverview.DoesNotExist:
        msg = _("The course {course_key} does not exist").format(course_key=params["course_key"])
        return HttpResponseBadRequest(msg)
    else:
        # Check that the user is enrolled in the course
        if not CourseEnrollment.is_enrolled(params["user"], params["course_key"]):
            msg = _("User {username} is not enrolled in the course {course_key}").format(
                username=params["user"].username,
                course_key=params["course_key"]
            )
            return HttpResponseBadRequest(msg)

        # Attempt to generate certificate
        generate_certificates_for_students(
            request,
            params["course_key"],
            student_set="specific_student",
            specific_student_id=params["user"].id
        )
        return HttpResponse(200)


def _deactivate_invalidation(certificate):
    """
    Deactivate certificate invalidation by setting active to False.

    Arguments:
        certificate : The student certificate object

    Return:
        None
    """
    try:
        # Fetch CertificateInvalidation object
        certificate_invalidation = CertificateInvalidation.objects.get(
            generated_certificate=certificate,
            active=True
        )
        # Deactivate certificate invalidation if it was fetched successfully.
        certificate_invalidation.deactivate()
    except CertificateInvalidation.DoesNotExist:  # pylint: disable=bare-except
        pass
