# 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/>.


from datetime import timedelta
import json
import logging
from string import Template
from xml.dom import NotFoundErr

from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse, reverse_lazy
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, get_object_or_404
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_noop
from django.views.generic import (
    TemplateView, CreateView, UpdateView,
)

from braces.views import (
    LoginRequiredMixin, PermissionRequiredMixin,
)
from django_tables2 import SingleTableView

from vm.models import (
    InstanceTemplate, InterfaceTemplate, Instance, Lease, InstanceActivity
)
from storage.models import Disk

from ..forms import (
    TemplateForm, TemplateListSearchForm, AclUserOrGroupAddForm, LeaseForm,
)
from ..tables import TemplateListTable, LeaseListTable

from .util import (
    AclUpdateView, FilterMixin,
    TransferOwnershipConfirmView, TransferOwnershipView,
    DeleteViewBase,
    GraphMixin
)

from dashboard.serializers import InstanceTemplateSerializer, LeaseSerializer, InstanceSerializer

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

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

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

logger = logging.getLogger(__name__)


class TemplateChoose(LoginRequiredMixin, TemplateView):

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

    def get_context_data(self, *args, **kwargs):
        context = super(TemplateChoose, self).get_context_data(*args, **kwargs)
        templates = InstanceTemplate.get_objects_with_level("user",
                                                            self.request.user)
        context.update({
            'box_title': _('Choose template'),
            'ajax_title': True,
            'template': "dashboard/_template-choose.html",
            'templates': templates.all(),
        })
        return context

    def post(self, request, *args, **kwargs):
        if not request.user.has_perm('vm.create_template'):
            raise PermissionDenied()

        template = request.POST.get("parent")
        if template == "base_vm":
            return redirect(reverse("dashboard.views.template-create"))
        elif template is None:
            messages.warning(request, _("Select an option to proceed."))
            return redirect(reverse("dashboard.views.template-choose"))
        else:
            template = get_object_or_404(InstanceTemplate, pk=template)

        if not template.has_level(request.user, "user"):
            raise PermissionDenied()

        instance = Instance.create_from_template(
            template=template, owner=request.user, is_base=True)

        return redirect(instance.get_absolute_url())


class TemplateCreate(SuccessMessageMixin, CreateView):
    model = InstanceTemplate
    form_class = TemplateForm

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

    def get_context_data(self, *args, **kwargs):
        context = super(TemplateCreate, self).get_context_data(*args, **kwargs)

        num_leases = Lease.get_objects_with_level("operator",
                                                  self.request.user).count()
        can_create_leases = self.request.user.has_perm("create_leases")
        context.update({
            'box_title': _("Create a new base VM"),
            'template': "dashboard/_template-create.html",
            'show_lease_create': num_leases < 1 and can_create_leases
        })
        return context

    def get(self, *args, **kwargs):
        if not self.request.user.has_perm('vm.create_base_template'):
            raise PermissionDenied()

        return super(TemplateCreate, self).get(*args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super(TemplateCreate, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

    def post(self, request, *args, **kwargs):
        if not self.request.user.has_perm('vm.create_base_template'):
            raise PermissionDenied()

        form = self.form_class(request.POST, user=request.user)
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        else:
            post = form.cleaned_data
            networks = self.__create_networks(post.pop("networks"),
                                              request.user)
            post.pop("parent")
            post['max_ram_size'] = post['ram_size']
            req_traits = post.pop("req_traits")
            tags = post.pop("tags")
            post['pw'] = User.objects.make_random_password()
            post['is_base'] = True

            inst = Instance.create(params=post, disks=[],
                                   networks=networks,
                                   tags=tags, req_traits=req_traits)

            return HttpResponseRedirect("%s#resources" %
                                        inst.get_absolute_url())

    def __create_networks(self, vlans, user):
        networks = []
        for v in vlans:
            if not v.has_level(user, "user"):
                raise PermissionDenied()
            networks.append(InterfaceTemplate(vlan=v, managed=v.managed))
        return networks

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-list")



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

    def get(self, request, format=None):
        if request.query_params.get("name"):
            try:
                template = InstanceTemplate.objects.filter(name__istartswith=request.query_params.get('name')).get()
                serializer = InstanceTemplateSerializer(template, many=False)
                return JsonResponse(serializer.data, safe=False)
            except:
                return JsonResponse(state=411)
        templates = InstanceTemplate.objects.all()
        serializer = InstanceTemplateSerializer(templates, many=True)
        return JsonResponse(serializer.data, safe=False)

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        serializer = InstanceTemplateSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

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

    def get(self, request, pk, format=None):
        templates = InstanceTemplate.objects.get(pk=pk)
        serializer = InstanceTemplateSerializer(templates, many=False)
        return JsonResponse(serializer.data, safe=False)

    def delete(self, request, pk, format=None):
        return JsonResponse(status=400)

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

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        user = User.objects.filter(username=data['username']).get()
        template = InstanceTemplate.objects.filter(name=data['template_name']).get()
        ikwargs = {
            'name': data['name'],
            'template': template,
            'owner': user,
        }
        amount = data.get("amount", 1)
        if 'num_cores' in data:
            ikwargs.update({'num_cores':data['num_cores']})
        
        if 'ram_size' in data:
            ikwargs.update({'ram_size':data['ram_size']})

        if 'priority' in data:
            ikwargs.update({'priority':data['priority']})

        if 'max_ram_size' in data:
            ikwargs.update({'max_ram_size':data['ram_size']})

        instances = Instance.mass_create_from_template(amount=amount,
                                                       **ikwargs)
        for i in instances:
            i.deploy._async(user=user)
        if amount == 1:
            serializer = InstanceSerializer(instances, many=True)
            return JsonResponse(serializer.data, status=201, safe=False)
        serializer = InstanceSerializer(instances, many=True)
        return JsonResponse(serializer.data, status=201, safe=False)

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

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        user = User.objects.get(pk=request.user.pk)
        template = InstanceTemplate.objects.get(pk=data['template'])
        ikwargs = {
            'name': data['name'],
            'template': template,
            'owner': user,
        }
        amount = data.get("amount", 1)
        if 'num_cores' in data:
            ikwargs.update({'num_cores':data['num_cores']})
        
        if 'ram_size' in data:
            ikwargs.update({'ram_size':data['ram_size']})

        if 'priority' in data:
            ikwargs.update({'priority':data['priority']})

        if 'max_ram_size' in data:
            ikwargs.update({'max_ram_size':data['ram_size']})

        instances = Instance.mass_create_from_template(amount=amount,
                                                       **ikwargs)
        for i in instances:
            i.deploy._async(user=user)
        if amount == 1:
            serializer = InstanceSerializer(instances, many=True)
            return JsonResponse(serializer.data, status=201, safe=False)
        serializer = InstanceSerializer(instances, many=True)
        return JsonResponse(serializer.data, status=201, safe=False)

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

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        template = InstanceTemplate.objects.get(pk=data['template'])
        ikwargs = {
            'name': data['name'],
            'users': data['users'],
            'template': template,
            'operator': data.get('operator', None),
            'admin': data.get('admin', None)
        }
        if 'num_cores' in data:
            ikwargs.update({'num_cores':data['num_cores']})
        
        if 'ram_size' in data:
            ikwargs.update({'ram_size':data['ram_size']})

        if 'priority' in data:
            ikwargs.update({'priority':data['priority']})

        if 'max_ram_size' in data:
            ikwargs.update({'max_ram_size':data['ram_size']})

        missing_users, instances = Instance.mass_create_for_users(**ikwargs)

        serializer = InstanceSerializer(instances, many=True)
        return JsonResponse(serializer.data, status=201)

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

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        template = InstanceTemplate.objects.get(pk=int(data['template']))
        ikwargs = {
            'name': data['name'],
            'users': data['users'],
            'template': template,
            'operator': data.get('operator', None),
            'admin': data.get('admin', None)        
        }
        if 'num_cores' in data:
            ikwargs.update({'num_cores':data['num_cores']})
        
        if 'ram_size' in data:
            ikwargs.update({'ram_size':data['ram_size']})

        if 'priority' in data:
            ikwargs.update({'priority':data['priority']})

        if 'max_ram_size' in data:
            ikwargs.update({'max_ram_size':data['ram_size']})

        missing_users, instances = Instance.mass_create_for_users_id(**ikwargs)

        serializer = InstanceSerializer(instances, many=True)
        return JsonResponse(serializer.data, status=201, safe=False)


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

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

    def post(self, request, format=None):
        data = JSONParser().parse(request)
        serializer = LeaseSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


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

    def get(self, request, pk, format=None):
        lease = Lease.objects.get(pk=pk)
        serializer = LeaseSerializer(lease, many=False)
        return JsonResponse(serializer.data, safe=False)


class TemplateAclUpdateView(AclUpdateView):
    model = InstanceTemplate


class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
    template_name = "dashboard/template-list.html"
    model = InstanceTemplate
    table_class = TemplateListTable
    table_pagination = False

    allowed_filters = {
        'name': "name__icontains",
        'tags[]': "tags__name__in",
        'tags': "tags__name__in",  # for search string
        'owner': "owner__username",
        'ram': "ram_size",
        'ram_size': "ram_size",
        'cores': "num_cores",
        'num_cores': "num_cores",
        'access_method': "access_method__iexact",
    }

    def get_context_data(self, *args, **kwargs):
        context = super(TemplateList, self).get_context_data(*args, **kwargs)
        user = self.request.user
        leases_w_operator = Lease.get_objects_with_level("operator", user)
        context['lease_table'] = LeaseListTable(
            leases_w_operator, request=self.request,
            template_name="django_tables2/table_no_page.html",
        )
        context['show_lease_table'] = (
            leases_w_operator.count() > 0 or
            user.has_perm("vm.create_leases")
        )

        context['search_form'] = self.search_form

        # templates without any instances
        # [t for t in InstanceTemplate.objects.all()
        #  if t.instance_set.count() < 1]
        never_instantiated = context['object_list'].annotate(
            instance_count=Count("instance_set")).filter(instance_count__lt=1)

        # templates without active virtual machines
        active_statuses = Instance.STATUS._db_values - set(["DESTROYED"])
        templates_wo_instances = context['object_list'].exclude(
            pk__in=InstanceTemplate.objects.filter(
                instance_set__status__in=active_statuses)
        ).exclude(pk__in=never_instantiated)

        def get_create_acts_younger_than(days):
            return InstanceActivity.objects.filter(
                activity_code="vm.Instance.create",
                finished__gt=timezone.now() - timedelta(days=days))

        # templates without active virtual machines
        # last machine started later than 90 days
        templates_wo_i_90 = templates_wo_instances.exclude(
            instance_set__activity_log__in=get_create_acts_younger_than(90))

        # templates without active virtual machines
        # last machine started later than 180 days
        templates_wo_i_180 = templates_wo_instances.exclude(
            instance_set__activity_log__in=get_create_acts_younger_than(180))

        context['unused_templates'] = {
            'never_instantiated': never_instantiated,
            'templates_wo_instances': templates_wo_instances,
            'templates_wo_instances_90': templates_wo_i_90,
            'templates_wo_instances_180': templates_wo_i_180,
        }

        return context

    def get(self, *args, **kwargs):
        self.search_form = TemplateListSearchForm(self.request.GET)
        self.search_form.full_clean()
        if self.request.is_ajax():
            templates = [{
                'icon': i.os_type,
                'system': i.system,
                'url': reverse("dashboard.views.template-detail",
                               kwargs={'pk': i.pk}),
                'name': i.name} for i in self.get_queryset()]
            return HttpResponse(
                json.dumps(templates),
                content_type="application/json",
            )
        else:
            return super(TemplateList, self).get(*args, **kwargs)

    def create_acl_queryset(self, model):
        queryset = super(TemplateList, self).create_acl_queryset(model)
        sql = ("SELECT count(*) FROM vm_instance WHERE "
               "vm_instance.template_id = vm_instancetemplate.id and "
               "vm_instance.destroyed_at is null and "
               "vm_instance.status = 'RUNNING'")
        queryset = queryset.extra(select={'running': sql})
        return queryset

    def get_queryset(self):
        logger.debug('TemplateList.get_queryset() called. User: %s', unicode(self.request.user))
        qs = self.create_acl_queryset(InstanceTemplate)
        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("lease", "owner", "owner__profile")


class TemplateDelete(DeleteViewBase):
    model = InstanceTemplate
    success_message = _("Template successfully deleted.")

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

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


class TemplateDetail(LoginRequiredMixin, GraphMixin,
                     SuccessMessageMixin, UpdateView):
    model = InstanceTemplate
    template_name = "dashboard/template-edit.html"
    form_class = TemplateForm
    success_message = _("Successfully modified template.")

    def get(self, request, *args, **kwargs):
        template = self.get_object()
        if not template.has_level(request.user, 'user'):
            raise PermissionDenied()
        if request.is_ajax():
            template = {
                'num_cores': template.num_cores,
                'ram_size': template.ram_size,
                'priority': template.priority,
                'arch': template.arch,
                'description': template.description,
                'system': template.system,
                'name': template.name,
                'disks': [{'pk': d.pk, 'name': d.name}
                          for d in template.disks.all()],
                'network': [
                    {'vlan_pk': i.vlan.pk, 'vlan': i.vlan.name,
                     'managed': i.managed}
                    for i in InterfaceTemplate.objects.filter(
                        template=self.get_object()).all()
                ]
            }
            return HttpResponse(json.dumps(template),
                                content_type="application/json")
        else:
            return super(TemplateDetail, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        obj = self.get_object()
        context = super(TemplateDetail, self).get_context_data(**kwargs)
        context['acl'] = AclUpdateView.get_acl_data(
            obj, self.request.user, 'dashboard.views.template-acl')
        context['disks'] = obj.disks.all()
        context['is_owner'] = obj.has_level(self.request.user, 'owner')
        context['aclform'] = AclUserOrGroupAddForm()
        context['parent'] = obj.parent
        context['show_graph'] = obj.has_level(self.request.user, 'operator')
        return context

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

    def post(self, request, *args, **kwargs):
        template = self.get_object()
        if not template.has_level(request.user, 'owner'):
            raise PermissionDenied()
        return super(TemplateDetail, self).post(self, request, args, kwargs)

    def get_form_kwargs(self):
        kwargs = super(TemplateDetail, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs


class DiskRemoveView(DeleteViewBase):
    model = Disk
    success_message = _("Disk successfully removed.")

    def get_queryset(self):
        qs = super(DiskRemoveView, self).get_queryset()
        return qs.exclude(template_set=None)

    def check_auth(self):
        disk = self.get_object()
        template = disk.template_set.get()
        if not template.has_level(self.request.user, 'owner'):
            raise PermissionDenied()

    def get_context_data(self, **kwargs):
        disk = self.get_object()
        template = disk.template_set.get()
        context = super(DiskRemoveView, self).get_context_data(**kwargs)
        context['title'] = _("Disk remove confirmation")
        context['text'] = _("Are you sure you want to remove "
                            "<strong>%(disk)s</strong> from "
                            "<strong>%(app)s</strong>?" % {'disk': disk,
                                                           'app': template}
                            )
        return context

    def delete_obj(self, request, *args, **kwargs):
        disk = self.get_object()
        template = disk.template_set.get()
        template.remove_disk(disk)
        disk.destroy()

    def get_success_url(self):
        return self.request.POST.get("next") or "/"


class LeaseCreate(LoginRequiredMixin, PermissionRequiredMixin,
                  SuccessMessageMixin, CreateView):
    model = Lease
    form_class = LeaseForm
    permission_required = 'vm.create_leases'
    template_name = "dashboard/lease-create.html"
    success_message = _("Successfully created a new lease.")

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-list")

    def form_valid(self, form):
        retval = super(LeaseCreate, self).form_valid(form)
        self.object.set_level(self.request.user, "owner")
        return retval


class LeaseAclUpdateView(AclUpdateView):
    model = Lease


class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = Lease
    form_class = LeaseForm
    template_name = "dashboard/lease-edit.html"
    success_message = _("Successfully modified lease.")

    def get_context_data(self, *args, **kwargs):
        obj = self.get_object()
        context = super(LeaseDetail, self).get_context_data(*args, **kwargs)
        context['acl'] = AclUpdateView.get_acl_data(
            obj, self.request.user, 'dashboard.views.lease-acl')
        return context

    def get_success_url(self):
        return reverse_lazy("dashboard.views.lease-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 lease.")
            messages.warning(request, message)
            return redirect(reverse_lazy("dashboard.views.template-list"))
        return super(LeaseDetail, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        if not self.get_object().has_level(request.user, "owner"):
            raise PermissionDenied()

        return super(LeaseDetail, self).post(request, *args, **kwargs)


class LeaseDelete(DeleteViewBase):
    model = Lease
    success_message = _("Lease successfully deleted.")

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

    def get_context_data(self, *args, **kwargs):
        c = super(LeaseDelete, self).get_context_data(*args, **kwargs)
        lease = self.get_object()
        templates = lease.instancetemplate_set
        if templates.count() > 0:
            text = _("You can't delete this lease because some templates "
                     "are still using it, modify these to proceed: ")

            c['text'] = text + ", ".join("<strong>%s (#%d)</strong>"
                                         "" % (o.name, o.pk)
                                         for o in templates.all())
            c['disable_submit'] = True
        return c

    def delete_obj(self, request, *args, **kwargs):
        object = self.get_object()
        if object.instancetemplate_set.count() > 0:
            raise SuspiciousOperation()
        object.delete()


class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView):
    template = "dashboard/confirm/transfer-template-ownership.html"
    model = InstanceTemplate


class TransferTemplateOwnershipView(TransferOwnershipView):
    confirm_view = TransferTemplateOwnershipConfirmView
    model = InstanceTemplate
    notification_msg = ugettext_noop(
        '%(owner)s offered you to take the ownership of '
        'his/her template called %(instance)s. '
        '<a href="%(token)s" '
        'class="btn btn-success btn-small">Accept</a>')
    token_url = 'dashboard.views.template-transfer-ownership-confirm'
    template = "dashboard/template-tx-owner.html"
