import json
import logging

import requests
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User, Group, Permission
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.urls import reverse, reverse_lazy
from django.http import HttpResponse, Http404, JsonResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.generic import UpdateView, TemplateView, DetailView
from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView
from django.db.models import Q
from itertools import chain
from django.utils.translation import ugettext as _, ugettext_noop

from . import GroupCodeMixin
from .util import (CheckedDetailView, AclUpdateView, search_user,
                   saml_available, DeleteViewBase,
    AclUpdateView, FilterMixin,
    TransferOwnershipConfirmView, TransferOwnershipView,
    DeleteViewBase,
    GraphMixin)
from ..forms import (
    AddGroupMemberForm, AclUserOrGroupAddForm, GroupPermissionForm,
    GroupCreateForm, GroupImportForm, GroupProfileUpdateForm, GroupExportForm,
    ExamCreateForm, ExamListSearchForm, ExamForm
)
from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException
from ..tables import ExamListTable
from exam.models import Exam
from dashboard.serializers import ExamSerializer
from vm.models import InstanceTemplate, Instance
from vm.operations import DestroyOperation, ShutdownOperation, ShutOffOperation, DeployOperation


class ExamList(LoginRequiredMixin, FilterMixin, SingleTableView):
    template_name = "dashboard/exam-list.html"
    model = Exam
    table_class = ExamListTable
    table_pagination = False
    allowed_filters = {
        'name': "name__icontains",
    }

    def get_context_data(self, *args, **kwargs):
        context = super(ExamList, self).get_context_data(*args, **kwargs)
        context['search_form'] = self.search_form
        return context

    def get(self, *args, **kwargs):
        user = self.request.user
        if not (user.has_perm('exam.create_exam') or user.has_perm('exam.start_exam')):
            raise PermissionDenied()
        self.search_form = ExamListSearchForm(self.request.GET)
        self.search_form.full_clean()
        if self.request.is_ajax():
            exams = [{
                'icon': i.os_type,
                'system': i.system,
                'url': reverse("dashboard.views.exam-detail",
                               kwargs={'pk': i.pk}),
                'name': i.name} for i in self.get_queryset()]
            return HttpResponse(
                json.dumps(exams),
                content_type="application/json",
            )
        else:
            return super(ExamList, self).get(*args, **kwargs)

    def create_acl_queryset(self, model):
        queryset = super(ExamList, self).create_acl_queryset(model)
        return queryset

    def get_queryset(self):
        qs = self.create_acl_queryset(Exam)
        self.create_fake_get()

        try:
            filters, excludes = self.get_queryset_filters()
            qs = qs.filter(**filters).exclude(**excludes).distinct()
        except ValueError:
            messages.error(self.request, _("Error during filtering."))

        return qs.select_related("template", "owner", "owner__profile")


class ExamCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
    form_class = ExamCreateForm

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/_modal.html']
        else:
            return ['dashboard/nojs-wrapper.html']

    def get(self, request, form=None, *args, **kwargs):
        if not request.user.has_perm('exam.create_exam'):
            raise PermissionDenied()
        if form is None:
            form = self.form_class()
        context = self.get_context_data(**kwargs)
        context.update({
            'template': 'dashboard/exam-create.html',
            'box_title': _('Create an Exam'),
            'form': form,
            'ajax_title': True,
        })
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if not request.user.has_perm('exam.create_exam'):
            raise PermissionDenied()
        form = self.form_class(request.POST)
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        form.cleaned_data
        form.setowner(request.user)
        savedform = form.save()
        messages.success(request, _('Exam successfully created.'))
        if request.is_ajax():
            return HttpResponse(
                json.dumps(
                    {'redirect': savedform.get_absolute_url()}
                ),
                content_type="application/json"
            )
        else:
            return redirect(savedform.get_absolute_url())


class ExamStart(LoginRequiredMixin, SuccessMessageMixin, DetailView):
    model = Exam

    def get(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "operator"):
            message = _("Only the operators can start the selected Exam VMs.")
            messages.warning(request, message)
            raise PermissionDenied()
        else:
            self.__start_vms(request)
        return redirect(self.get_object().get_absolute_url())

    def __start_vms(self, request):
        exam: Exam = self.get_object()

        if not exam.template:
            messages.warning(request, "No template is set to start!")
            return

        owners = []
        operators = []
        for user, level in exam.get_users_with_level():
            if level == "operator":
                operators.append(user)
            elif level == "owner":
                owners.append(user)

        users_have_vm = []
        for instance in exam.instances.all():
            users_have_vm.append(instance.owner)
            if instance.state not in DeployOperation.deny_states:
                DeployOperation(instance).call(user=self.request.user)

        users_need_vm = set(exam.examinees.all()) - set(users_have_vm)
        if users_need_vm:
            missing_users, instances = Instance.mass_create_for_users(exam.template, users_need_vm, owners, operators)
            for user in missing_users:
                messages.warning(request, _('Error starting VM for user "%s".') % user)
            for instance in instances:
                exam.instances.add(instance)


class ExamVMShutdown(LoginRequiredMixin, SuccessMessageMixin, DetailView):
    model = Exam

    def get(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "operator"):
            message = _("Only the operators can shutdown the selected Exam VMs.")
            messages.warning(request, message)
            raise PermissionDenied()
        else:
            self.__shutdown_vms(request)
        return redirect(self.get_object().get_absolute_url())

    def __shutdown_vms(self, request):
        exam: Exam = self.get_object()
        for instance in exam.instances.all():
            if instance.state in ShutdownOperation.accept_states:
                ShutdownOperation(instance).call(user=self.request.user)


class ExamVMStop(LoginRequiredMixin, SuccessMessageMixin, DetailView):
    model = Exam

    def get(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "operator"):
            message = _("Only the operators can stop the selected Exam VMs.")
            messages.warning(request, message)
            raise PermissionDenied()
        else:
            self.__stop_vms(request)
        return redirect(self.get_object().get_absolute_url())

    def __stop_vms(self, request):
        exam: Exam = self.get_object()
        for instance in exam.instances.all():
            if instance.state in ShutOffOperation.accept_states:
                ShutOffOperation(instance).call(user=self.request.user)


class ExamVMDestroy(LoginRequiredMixin, SuccessMessageMixin, DetailView):
    model = Exam

    def get(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "operator"):
            message = _("Only the operators can destroy the selected Exam VMs.")
            messages.warning(request, message)
            raise PermissionDenied()
        else:
            self.__destroy_vms(request)
        return redirect(self.get_object().get_absolute_url())

    def __destroy_vms(self, request):
        exam: Exam = self.get_object()
        for instance in exam.instances.all():
            if instance.destroyed_at:
                exam.instances.remove(instance)
                continue
            DestroyOperation(instance).call(user=self.request.user)
            exam.instances.remove(instance)


class ExamDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = Exam
    form_class = ExamForm
    template_name = "dashboard/exam-detail.html"
    success_message = _("Successfully modified Exam.")

    def get_context_data(self, *args, **kwargs):
        obj = self.get_object()
        context = super(ExamDetail, self).get_context_data(**kwargs)
        exam = context['exam']
        user = self.request.user
        is_operator = exam.has_level(user, "operator")
        is_owner = exam.has_level(user, "owner")
        context = super(ExamDetail, self).get_context_data(*args, **kwargs)
        context['is_operator'] = is_operator
        context['is_owner'] = is_owner
        template_queryset = ( InstanceTemplate.get_objects_with_level("operator", user).distinct())
        context["templates"] = template_queryset
        context['acl'] = AclUpdateView.get_acl_data(exam, user, 'dashboard.views.exam-detail.exam-acl')
        context['aclform'] = AclUserOrGroupAddForm()
        context['addmemberform'] = AddGroupMemberForm()
        context['users'] = exam.examinees.all()
        context['instances'] = exam.instances.all()
        return context

    def get_success_url(self):
        return reverse_lazy("dashboard.views.exam-detail", kwargs=self.kwargs)

    def get(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "owner"):
            message = _("Only the owners can modify the selected Exam.")
            messages.warning(request, message)
            return redirect(reverse_lazy("dashboard.views.exam-list"))
        return super(ExamDetail, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'operator'):
            raise PermissionDenied()

        if request.POST.get('new_name'):
            return self.__set_name(request)
        if request.POST.get('new_description'):
            return self.__set_description(request)
        if request.POST.get('new-template'):
            return self.__set_template(request)
        if request.POST.get('new_member'):
            return self.__set_member(request)
        if request.POST.get('new_members'):
            return self.__set_members(request)
        return redirect(self.object.get_absolute_url())

    def __set_name(self, request):
        new_name = request.POST.get("new_name")
        Exam.objects.filter(pk=self.object.pk).update(**{'name': new_name})

        success_message = _("Exam successfully renamed.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_name': new_name,
                'exam_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.object.get_absolute_url())

    def __set_description(self, request):
        new_description = request.POST.get("new_description")
        Exam.objects.filter(pk=self.object.pk).update(**{'description': new_description})

        success_message = _("Exam description successfully updated.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_description': new_description,
                'exam_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.object.get_absolute_url())

    def __set_template(self, request):
        new_template_id = request.POST.get("new-template")
        new_template = InstanceTemplate.objects.get(pk=new_template_id)
        if not new_template:
            raise PermissionDenied()
        Exam.objects.filter(pk=self.object.pk).update(**{'template': new_template})

        success_message = _("Exam template successfully changed.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_template': new_template,
                'exam_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.object.get_absolute_url())

    def __set_member(self, request):
        new_member_name = request.POST.get("new_member")
        new_member = search_user(new_member_name)
        if not new_member:
            raise PermissionDenied()
        Exam.objects.get(pk=self.object.pk).examinees.add(new_member)

        success_message = _("Exam users successfully changed.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_member': new_member,
                'exam_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.object.get_absolute_url())

    def __set_members(self, request):
        new_member_names = request.POST.get("new_members").split('\r\n')
        new_members = []
        for name in new_member_names:
            if not name:
                return
            try:
                user = search_user(name)
                Exam.objects.get(pk=self.object.pk).examinees.add(user)
                new_members.append(name)
            except User.DoesNotExist:
                messages.warning(request, _('User "%s" not found.') % name)

        success_message = _("Exam users successfully changed.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_members': new_members,
                'exam_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.object.get_absolute_url())


class ExamDelete(DeleteViewBase):
    model = Exam
    success_message = _("Exam successfully deleted.")

    def get_success_url(self):
        return reverse("dashboard.views.exam-list")

    def delete_obj(self, request, *args, **kwargs):
        object = self.get_object()
        object.delete()


class ExamRemoveAllUsersView(DeleteViewBase):
    model = Exam
    level = 'operator'
    slug_field = 'pk'
    slug_url_kwarg = 'exam_pk'
    success_message = _("All users successfully removed from exam.")

    def check_auth(self):
        if not self.get_object().has_level(
                self.request.user, self.level):
            raise PermissionDenied()

    def get_context_data(self, **kwargs):
        context = super(ExamRemoveAllUsersView, self).get_context_data(**kwargs)
        context['member'] = _("all examinees")
        return context

    def get_success_url(self):
        return reverse_lazy("dashboard.views.exam-detail", kwargs={'pk': self.get_object().pk})

    def delete_obj(self, request, *args, **kwargs):
        exam = self.get_object()
        exam.examinees.clear()


class ExamRemoveUserView(DeleteViewBase):
    model = Exam
    slug_field = 'pk'
    slug_url_kwarg = 'exam_pk'
    level = 'operator'
    member_key = 'user_pk'
    success_message = _("Member successfully removed from exam.")

    def check_auth(self):
        if not self.get_object().has_level(
                self.request.user, self.level):
                # self.request.user, self.level:
                raise PermissionDenied()

    def get_context_data(self, **kwargs):
        context = super(ExamRemoveUserView, self).get_context_data(**kwargs)
        try:
            context['member'] = User.objects.get(pk=self.user_pk)
        except User.DoesNotExist:
            raise Http404()
        return context

    def get_success_url(self):
        return reverse_lazy("dashboard.views.exam-detail", kwargs={'pk': self.get_object().pk})

    def get(self, request, user_pk, *args, **kwargs):
        self.user_pk = user_pk
        return super(ExamRemoveUserView, self).get(request, *args, **kwargs)

    def remove_member(self, pk):
        exam = self.get_object()
        exam.examinees.remove(User.objects.get(pk=pk))

    def delete_obj(self, request, *args, **kwargs):
        self.remove_member(kwargs[self.member_key])


class TransferExamOwnershipConfirmView(TransferOwnershipConfirmView):
    template = "dashboard/confirm/transfer-exam-ownership.html"
    model = Exam


class TransferExamOwnershipView(TransferOwnershipView):
    confirm_view = TransferExamOwnershipConfirmView
    model = Exam
    notification_msg = ugettext_noop(
        '%(owner)s offered you to take the ownership of '
        'his/her exam called %(instance)s. '
        '<a href="%(token)s" '
        'class="btn btn-success btn-small">Accept</a>')
    token_url = 'dashboard.views.exam-transfer-ownership-confirm'
    template = "dashboard/exam-tx-owner.html"
    required_permission_list = ["exam.create_exam", "exam.start_exam"]
