from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST
from django_future.csrf import ensure_csrf_cookie
from edxmako.shortcuts import render_to_response

from xmodule.modulestore.django import modulestore, loc_mapper
from util.json_request import JsonResponse, expect_json
from student.roles import CourseRole, CourseInstructorRole, CourseStaffRole, GlobalStaff
from course_creators.views import user_requested_access

from .access import has_course_access

from student.models import CourseEnrollment
from xmodule.modulestore.locator import BlockUsageLocator
from django.http import HttpResponseNotFound
from student import auth


__all__ = ['request_course_creator', 'course_team_handler']


@require_POST
@login_required
def request_course_creator(request):
    """
    User has requested course creation access.
    """
    user_requested_access(request.user)
    return JsonResponse({"Status": "OK"})


# pylint: disable=unused-argument
@login_required
@ensure_csrf_cookie
@require_http_methods(("GET", "POST", "PUT", "DELETE"))
def course_team_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None, email=None):
    """
    The restful handler for course team users.

    GET
        html: return html page for managing course team
        json: return json representation of a particular course team member (email is required).
    POST or PUT
        json: modify the permissions for a particular course team member (email is required, as well as role in the payload).
    DELETE:
        json: remove a particular course team member from the course team (email is required).
    """
    location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
    if not has_course_access(request.user, location):
        raise PermissionDenied()

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        return _course_team_user(request, location, email)
    elif request.method == 'GET':  # assume html
        return _manage_users(request, location)
    else:
        return HttpResponseNotFound()


def _manage_users(request, locator):
    """
    This view will return all CMS users who are editors for the specified course
    """
    old_location = loc_mapper().translate_locator_to_location(locator)

    # check that logged in user has permissions to this item
    if not has_course_access(request.user, locator):
        raise PermissionDenied()

    course_module = modulestore().get_item(old_location)
    instructors = CourseInstructorRole(locator).users_with_role()
    # the page only lists staff and assumes they're a superset of instructors. Do a union to ensure.
    staff = set(CourseStaffRole(locator).users_with_role()).union(instructors)

    return render_to_response('manage_users.html', {
        'context_course': course_module,
        'staff': staff,
        'instructors': instructors,
        'allow_actions': has_course_access(request.user, locator, role=CourseInstructorRole),
    })


@expect_json
def _course_team_user(request, locator, email):
    """
    Handle the add, remove, promote, demote requests ensuring the requester has authority
    """
    # check that logged in user has permissions to this item
    if has_course_access(request.user, locator, role=CourseInstructorRole):
        # instructors have full permissions
        pass
    elif has_course_access(request.user, locator, role=CourseStaffRole) and email == request.user.email:
        # staff can only affect themselves
        pass
    else:
        msg = {
            "error": _("Insufficient permissions")
        }
        return JsonResponse(msg, 400)

    try:
        user = User.objects.get(email=email)
    except Exception:
        msg = {
            "error": _("Could not find user by email address '{email}'.").format(email=email),
        }
        return JsonResponse(msg, 404)

    # role hierarchy: globalstaff > "instructor" > "staff" (in a course)
    if request.method == "GET":
        # just return info about the user
        msg = {
            "email": user.email,
            "active": user.is_active,
            "role": None,
        }
        # what's the highest role that this user has? (How should this report global staff?)
        for role in [CourseInstructorRole(locator), CourseStaffRole(locator)]:
            if role.has_user(user):
                msg["role"] = role.ROLE
                break
        return JsonResponse(msg)

    # can't modify an inactive user
    if not user.is_active:
        msg = {
            "error": _('User {email} has registered but has not yet activated his/her account.').format(email=email),
        }
        return JsonResponse(msg, 400)

    if request.method == "DELETE":
        try:
            try_remove_instructor(request, locator, user)
        except CannotOrphanCourse as oops:
            return JsonResponse(oops.msg, 400)

        auth.remove_users(request.user, CourseStaffRole(locator), user)
        return JsonResponse()

    # all other operations require the requesting user to specify a role
    role = request.json.get("role", request.POST.get("role"))
    if role is None:
        return JsonResponse({"error": _("`role` is required")}, 400)

    old_location = loc_mapper().translate_locator_to_location(locator)
    if role == "instructor":
        if not has_course_access(request.user, locator, role=CourseInstructorRole):
            msg = {
                "error": _("Only instructors may create other instructors")
            }
            return JsonResponse(msg, 400)
        auth.add_users(request.user, CourseInstructorRole(locator), user)
        # auto-enroll the course creator in the course so that "View Live" will work.
        CourseEnrollment.enroll(user, old_location.course_id)
    elif role == "staff":
        # add to staff regardless (can't do after removing from instructors as will no longer
        # be allowed)
        auth.add_users(request.user, CourseStaffRole(locator), user)
        try:
            try_remove_instructor(request, locator, user)
        except CannotOrphanCourse as oops:
            return JsonResponse(oops.msg, 400)

        # auto-enroll the course creator in the course so that "View Live" will work.
        CourseEnrollment.enroll(user, old_location.course_id)

    return JsonResponse()


class CannotOrphanCourse(Exception):
    """
    Exception raised if an attempt is made to remove all responsible instructors from course.
    """
    def __init__(self, msg):
        self.msg = msg
        Exception.__init__(self)


def try_remove_instructor(request, locator, user):
    # remove all roles in this course from this user: but fail if the user
    # is the last instructor in the course team
    instructors = CourseInstructorRole(locator)
    if instructors.has_user(user):
        if instructors.users_with_role().count() == 1:
            msg = {"error":_("You may not remove the last instructor from a course")}
            raise CannotOrphanCourse(msg)
        else:
            auth.remove_users(request.user, instructors, user)