# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE.  If not, see <http://www.gnu.org/licenses/>.


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
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
from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableView
from itertools import chain

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser

from vm.models import Instance, InstanceTemplate
from .util import (CheckedDetailView, AclUpdateView, search_user,
                   saml_available, DeleteViewBase)
from ..forms import (
    AddGroupMemberForm, AclUserOrGroupAddForm, GroupPermissionForm,
    GroupCreateForm, GroupImportForm, GroupProfileUpdateForm, GroupExportForm,
)
from ..models import FutureMember, GroupProfile
from ..store_api import Store, NoStoreException
from ..tables import GroupListTable

from dashboard.serializers import GroupSerializer

try:
    # Python 2: "unicode" is built-in
    unicode
except NameError:
    unicode = str

logger = logging.getLogger(__name__)


class GroupREST(APIView):
    authentication_classes = [TokenAuthentication,BasicAuthentication]
    permission_classes = [IsAdminUser]

    def get(self, request, format=None):
        if request.query_params.get('name'):
            try:
                group = Group.objects.filter(name__istartswith=request.query_params.get('name')).get()
                serializer = GroupSerializer(group, many=False)
                return JsonResponse(serializer.data, safe=False)
            except:
                return JsonResponse({}, status=404)
        groups = Group.objects.all()
        serializer = GroupSerializer(groups, many=True)
        return JsonResponse(serializer.data, safe=False)


class GetGroupREST(APIView):
    authentication_classes = [TokenAuthentication,BasicAuthentication]
    permission_classes = [IsAdminUser]

    def get(self, request, pk, format=None):
        groups = Group.objects.get(pk=pk)
        serializer = GroupSerializer(groups, many=False)
        return JsonResponse(serializer.data, safe=False)


class GroupCodeMixin(object):

    @classmethod
    def get_available_group_codes(cls, request):
        newgroups = []
        if saml_available:
            from djangosaml2.cache import StateCache, IdentityCache
            from djangosaml2.conf import get_config
            from djangosaml2.views import _get_subject_id
            from saml2.client import Saml2Client

            state = StateCache(request.session)
            conf = get_config(None, request)
            client = Saml2Client(conf, state_cache=state,
                                 identity_cache=IdentityCache(request.session))
            subject_id = _get_subject_id(request.session)
            if not subject_id:
                return newgroups
            identity = client.users.get_identity(subject_id,
                                                 check_not_on_or_after=False)
            if identity:
                attributes = identity[0]
                owneratrs = getattr(
                    settings, 'SAML_GROUP_OWNER_ATTRIBUTES', [])
                for group in chain(*[attributes[i]
                                     for i in owneratrs if i in attributes]):
                    try:
                        GroupProfile.search(group)
                    except Group.DoesNotExist:
                        newgroups.append(group)

        return newgroups


class GroupDetailView(CheckedDetailView):
    template_name = "dashboard/group-detail.html"
    model = Group
    read_level = 'operator'

    def get_has_level(self):
        return self.object.profile.has_level

    def get_context_data(self, **kwargs):
        context = super(GroupDetailView, self).get_context_data(**kwargs)
        context['group'] = self.object
        context['users'] = self.object.user_set.all()
        context['future_users'] = FutureMember.objects.filter(
            group=self.object)
        context['acl'] = AclUpdateView.get_acl_data(
            self.object.profile, self.request.user,
            'dashboard.views.group-acl')
        context['aclform'] = AclUserOrGroupAddForm()
        context['addmemberform'] = AddGroupMemberForm()
        context['group_profile_form'] = GroupProfileUpdate.get_form_object(
            self.request, self.object.profile)

        context.update({
            'group_objects': GroupProfile.get_objects_with_group_level(
                "operator", self.get_object()),
            'vm_objects': Instance.get_objects_with_group_level(
                "user", self.get_object()),
            'template_objects': InstanceTemplate.get_objects_with_group_level(
                "user", self.get_object()),
        })

        if self.request.user.is_superuser:
            context['group_perm_form'] = GroupPermissionForm(
                instance=self.object)

        return context

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

        if request.POST.get('new_name'):
            return self.__set_name(request)
        if request.POST.get('new_member'):
            return self.__add_user(request)
        if request.POST.get('new_members'):
            return self.__add_list(request)
        return redirect(reverse_lazy("dashboard.views.group-detail",
                                     kwargs={'pk': self.get_object().pk}))

    def __add_user(self, request):
        name = request.POST['new_member']
        self.__add_username(request, name)
        return redirect(reverse_lazy("dashboard.views.group-detail",
                                     kwargs={'pk': self.object.pk}))

    def __add_username(self, request, name):
        if not name:
            return
        try:
            entity = search_user(name)
            self.object.user_set.add(entity)
        except User.DoesNotExist:
            if saml_available:
                FutureMember.objects.get_or_create(org_id=name.upper(),
                                                   group=self.object)
            else:
                messages.warning(request, _('User "%s" not found.') % name)

    def __add_list(self, request):
        userlist = request.POST.get('new_members').split('\r\n')
        for line in userlist:
            self.__add_username(request, line)
        return redirect(reverse_lazy("dashboard.views.group-detail",
                                     kwargs={'pk': self.object.pk}))

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

        success_message = _("Group successfully renamed.")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_name': new_name,
                'group_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.group-detail",
                                         kwargs={'pk': self.object.pk}))


class GroupPermissionsView(SuperuserRequiredMixin, UpdateView):
    model = Group
    form_class = GroupPermissionForm
    slug_field = "pk"
    slug_url_kwarg = "group_pk"

    def get_success_url(self):
        return "%s#group-detail-permissions" % (
            self.get_object().groupprofile.get_absolute_url())


class GroupAclUpdateView(AclUpdateView):
    model = GroupProfile


class GroupList(LoginRequiredMixin, SingleTableView):
    template_name = "dashboard/group-list.html"
    model = Group
    table_class = GroupListTable
    table_pagination = False

    def get(self, *args, **kwargs):
        if self.request.is_ajax():
            groups = [{
                'url': reverse("dashboard.views.group-detail",
                               kwargs={'pk': i.pk}),
                'name': i.name} for i in self.get_queryset()]
            return HttpResponse(
                json.dumps(list(groups)),
                content_type="application/json",
            )
        else:
            return super(GroupList, self).get(*args, **kwargs)

    def get_queryset(self):
      #  logger.debug('GroupList.get_queryset() called. User: %s', unicode(self.request.user))
        profiles = GroupProfile.get_objects_with_level(
            'operator', self.request.user)
        groups = Group.objects.filter(groupprofile__in=profiles)
        s = self.request.GET.get("s")
        if s:
            groups = groups.filter(name__icontains=s)
        return groups


class GroupRemoveUserView(DeleteViewBase):
    model = Group
    slug_field = 'pk'
    slug_url_kwarg = 'group_pk'
    level = 'operator'
    member_key = 'member_pk'
    success_message = _("Member successfully removed from group.")

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

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

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

    def get(self, request, member_pk, *args, **kwargs):
        self.member_pk = member_pk
        return super(GroupRemoveUserView, self).get(request, *args, **kwargs)

    def remove_member(self, pk):
        container = self.get_object()
        container.user_set.remove(User.objects.get(pk=pk))

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


class GroupRemoveFutureUserView(GroupRemoveUserView):
    member_key = 'member_org_id'
    success_message = _("Future user successfully removed from group.")

    def get(self, request, member_org_id, *args, **kwargs):
        self.member_org_id = member_org_id
        return super(GroupRemoveUserView, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(GroupRemoveUserView, self).get_context_data(**kwargs)
        try:
            context['member'] = FutureMember.objects.get(
                org_id=self.member_org_id, group=self.get_object())
        except FutureMember.DoesNotExist:
            raise Http404()
        return context

    def remove_member(self, org_id):
        FutureMember.objects.filter(org_id=org_id,
                                    group=self.get_object()).delete()


class GroupRemoveAllUsersView(DeleteViewBase):
    model = Group
    level = 'operator'
    slug_field = 'pk'
    slug_url_kwarg = 'group_pk'
    success_message = _("All users successfully removed from group.")

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

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

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

    def delete_obj(self, request, *args, **kwargs):
        container = self.get_object()
        container.user_set.clear()
        FutureMember.objects.filter(group=container).delete()


class GroupDelete(DeleteViewBase):
    model = Group
    success_message = _("Group successfully deleted.")

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

    def get_success_url(self):
        return reverse_lazy('dashboard.views.group-list')


class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
    form_class = GroupCreateForm

    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_module_perms('auth'):
            raise PermissionDenied()
        if form is None:
            form = self.form_class(
                new_groups=self.get_available_group_codes(request))
        context = self.get_context_data(**kwargs)
        context.update({
            'template': 'dashboard/group-create.html',
            'box_title': _('Create a Group'),
            'form': form,
            'ajax_title': True,
        })
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if not request.user.has_module_perms('auth'):
            raise PermissionDenied()
        form = self.form_class(
            request.POST, new_groups=self.get_available_group_codes(request))
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        form.cleaned_data
        savedform = form.save()
        savedform.profile.set_level(request.user, 'owner')
        messages.success(request, _('Group successfully created.'))
        if request.is_ajax():
            return HttpResponse(
                json.dumps(
                    {'redirect': savedform.profile.get_absolute_url()}
                ),
                content_type="application/json"
            )
        else:
            return redirect(savedform.profile.get_absolute_url())


class GroupImportView(LoginRequiredMixin, TemplateView):
    form_class = GroupImportForm

    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_module_perms('auth'):
            raise PermissionDenied()
        try:
            Store(request.user)
        except NoStoreException:
            raise PermissionDenied()

        if form is None:
            form = self.form_class(user=request.user)
        context = self.get_context_data(**kwargs)
        context.update({
            'template': 'dashboard/group-import.html',
            'box_title': _('Import a Group'),
            'form': form,
            'ajax_title': True,
        })
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        if not request.user.has_module_perms('auth'):
            raise PermissionDenied()
        try:
            Store(request.user)
        except NoStoreException:
            raise PermissionDenied()

        form = self.form_class(request.POST, user=request.user)
        if form.is_valid():
            group_path = form.cleaned_data["group_path"]
            url = Store(request.user).request_download(group_path)
            json_str = requests.get(url).content
            profile = GroupProfile.create_from_json(request.user, json_str)
            if profile is None:
                raise SuspiciousOperation()

            success_message = _("Group successfully imported.")
            if request.is_ajax():
                response = {
                    'message': success_message,
                    'redirect': profile.get_absolute_url()
                }
                return HttpResponse(
                    json.dumps(response),
                    content_type="application/json"
                )
            else:
                messages.success(request, success_message)
                return redirect(profile.get_absolute_url())
        else:
            return self.get(request, form, *args, **kwargs)


class GroupExportView(LoginRequiredMixin, SingleObjectMixin, TemplateView):
    form_class = GroupExportForm
    model = Group
    pk_url_kwarg = "group_pk"

    def __init__(self):
        super(GroupExportView, self).__init__()
        self.object = None

    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):
        self.object = self.get_object()
        if not self.object.profile.has_level(request.user, 'operator'):
            raise PermissionDenied()
        try:
            Store(request.user)
        except NoStoreException:
            raise PermissionDenied()

        if form is None:
            form = self.form_class(group_name=self.object.name)
        context = self.get_context_data(**kwargs)
        context.update({
            'group': self.object,
            'template': 'dashboard/group-export.html',
            'box_title': _('Export Group'),
            'form': form,
            'ajax_title': True,
        })
        return self.render_to_response(context)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        group = self.object
        if not group.profile.has_level(request.user, 'operator'):
            raise PermissionDenied()
        try:
            Store(request.user)
        except NoStoreException:
            raise PermissionDenied()

        form = self.form_class(request.POST, group_name=self.object.name)
        if form.is_valid():
            name = form.cleaned_data["exported_name"]
            group_json = group.profile.convert_to_json()
            store = Store(request.user)
            url = store.request_upload("/")

            data = {'data': (name + '.group', group_json)}
            requests.post(url, files=data)

            success_message = _("Group successfully exported.")
            if request.is_ajax():
                response = {
                    'message': success_message,
                    'redirect': group.profile.get_absolute_url()
                }
                return HttpResponse(
                    json.dumps(response),
                    content_type="application/json"
                )
            else:
                messages.success(request, success_message)
                return redirect(group.profile.get_absolute_url())
        else:
            return self.get(request, form, *args, **kwargs)


class GroupProfileUpdate(SuccessMessageMixin, GroupCodeMixin,
                         LoginRequiredMixin, UpdateView):
    form_class = GroupProfileUpdateForm
    model = Group
    success_message = _('Group is successfully updated.')

    @classmethod
    def get_available_group_codes(cls, request, extra=None):
        result = super(GroupProfileUpdate, cls).get_available_group_codes(
            request)
        if extra and extra not in result:
            result += [extra]
        return result

    def get_object(self):
        group = super(GroupProfileUpdate, self).get_object()
        profile = group.profile
        if not profile.has_level(self.request.user, 'owner'):
            raise PermissionDenied
        else:
            return profile

    @classmethod
    def get_form_object(cls, request, instance, *args, **kwargs):
        kwargs['instance'] = instance
        kwargs['new_groups'] = cls.get_available_group_codes(
            request, instance.org_id)
        kwargs['superuser'] = request.user.is_superuser
        return cls.form_class(*args, **kwargs)

    def get(self, request, form=None, *args, **kwargs):
        self.object = self.get_object()
        if form is None:
            form = self.get_form_object(request, self.object)
        return super(GroupProfileUpdate, self).get(
            request, form, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        if not request.user.has_module_perms('auth'):
            raise PermissionDenied()
        self.object = self.get_object()
        form = self.get_form_object(request, self.object, self.request.POST)
        if not form.is_valid():
            return self.form_invalid(form)
        form.save()
        return self.form_valid(form)
