# 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
from collections import OrderedDict
from itertools import chain
from operator import itemgetter

import openstack_api
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from dashboard.forms import AclUserOrGroupAddForm
from dashboard.views import AclUpdateView, FormView
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import (
    ValidationError
)
from django.core.urlresolvers import reverse_lazy
from django.db.models import Q
from django.http import HttpResponse, Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.views import View
from django.views.generic import (
    TemplateView, UpdateView, DeleteView, CreateView,
    DetailView)
from django_tables2 import SingleTableView
from firewall.models import (
    Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
    SwitchPort, EthernetDevice, Firewall
)
from netaddr import IPNetwork
from network.models import Vxlan, EditorElement, SubnetPool
from openstack_api.utils.lazy_encoder import LazyTranslationEncoder
from vm.models import Interface, Instance

from .forms import (
    HostForm, VlanForm, DomainForm, GroupForm, RecordForm, BlacklistItemForm,
    RuleForm, VlanGroupForm, SwitchPortForm, FirewallForm, VxlanForm)
from .tables import (
    HostTable, VlanTable, SmallHostTable, DomainTable, GroupTable,
    RecordTable, BlacklistItemTable, RuleTable, VlanGroupTable,
    SmallRuleTable, SmallGroupRuleTable, SmallRecordTable, SwitchPortTable,
    SmallDhcpTable, FirewallTable, FirewallRuleTable, VxlanTable, )

try:
    from django.http import JsonResponse
except ImportError:
    from django.utils import simplejson

    class JsonResponse(HttpResponse):
        """JSON response for Django < 1.7
        https://gist.github.com/philippeowagner/3179eb475fe1795d6515
        """
        def __init__(self, content, mimetype='application/json',
                     status=None, content_type=None):
            super(JsonResponse, self).__init__(
                content=simplejson.dumps(content),
                mimetype=mimetype,
                status=status,
                content_type=content_type)


logger = logging.getLogger(__name__)


class MagicMixin(object):

    def get(self, *args, **kwargs):
        if self.request.is_ajax():
            result = self._get_ajax(*args, **kwargs)
            return JsonResponse({k: unicode(result[k] or "") for k in result})
        else:
            return super(MagicMixin, self).get(*args, **kwargs)

class IndexView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
    template_name = "network/index.html"

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

        size = 13
        blacklists = BlacklistItem.objects.all().order_by(
            '-modified_at')[:size]
        domains = Domain.objects.all().order_by('-modified_at')[:size]
        groups = Group.objects.all().order_by('-modified_at')[:size]
        hosts = Host.objects.all().order_by('-modified_at')[:size]
        records = Record.objects.all().order_by('-modified_at')[:size]
        vlans = Vlan.objects.all().order_by('-modified_at')[:size]
        vlangroups = VlanGroup.objects.all().order_by('-modified_at')[:size]
        rules = Rule.objects.all().order_by('-modified_at')[:size]

        result_list = []
        for i in (sorted(chain(domains, groups, hosts, records, vlans,
                               vlangroups, rules),
                         key=lambda x: x.modified_at, reverse=True)[:size]):
            result_list.append(
                {
                    'class_name': unicode(i.__class__.__name__),
                    'modified_at': i.modified_at,
                    'created_at': i.created_at,
                    'name': unicode(i),
                    'link': i.get_absolute_url()
                })

        context['latest_blacklists'] = blacklists
        context['latest'] = result_list
        return context


class BlacklistList(LoginRequiredMixin, SuperuserRequiredMixin,
                    SingleTableView):
    model = BlacklistItem
    table_class = BlacklistItemTable
    template_name = "network/blacklist-list.html"
    table_pagination = False


class BlacklistDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                      SuccessMessageMixin, UpdateView):
    model = BlacklistItem
    template_name = "network/blacklist-edit.html"
    form_class = BlacklistItemForm
    success_message = _(u'Successfully modified blacklist item %(ipv4)s.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.blacklist', kwargs=self.kwargs)

    def get_context_data(self, **kwargs):
        context = super(BlacklistDetail, self).get_context_data(**kwargs)
        context['blacklist_pk'] = self.object.pk
        return context


class BlacklistCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                      SuccessMessageMixin, CreateView):
    model = BlacklistItem
    template_name = "network/blacklist-create.html"
    form_class = BlacklistItemForm
    success_message = _(u'Successfully created blacklist item %(ipv4)s')


class BlacklistDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = BlacklistItem
    template_name = "network/confirm/base_delete.html"

    def get_context_data(self, **kwargs):
        """ display more information about the object """
        context = super(BlacklistDelete, self).get_context_data(**kwargs)
        if 'pk' in self.kwargs:
            to_delete = BlacklistItem.objects.get(pk=self.kwargs['pk'])
            context['object'] = "%s - %s" % (to_delete.ipv4, to_delete.reason)
            return context

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return self.request.POST['next']
        else:
            return reverse_lazy('network.blacklist_list')


class DomainList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    model = Domain
    table_class = DomainTable
    template_name = "network/domain-list.html"
    table_pagination = False


class DomainDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                   SuccessMessageMixin, UpdateView):
    model = Domain
    template_name = "network/domain-edit.html"
    form_class = DomainForm
    success_message = _(u'Successfully modified domain %(name)s.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.domain', kwargs=self.kwargs)

    def get_context_data(self, *args, **kwargs):
        context = super(DomainDetail, self).get_context_data(**kwargs)
        self.object = self.get_object()
        context['domain_pk'] = self.object.pk

        q = Record.objects.filter(
            domain=self.object,
            host__in=Host.objects.filter(
                interface__in=Interface.objects.filter(
                    instance__destroyed_at=None)
            )
        )
        context['record_list'] = SmallRecordTable(q)
        return context


class DomainCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                   SuccessMessageMixin, CreateView):
    model = Domain
    template_name = "network/domain-create.html"
    form_class = DomainForm
    success_message = _(u'Successfully created domain %(name)s.')


class DomainDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Domain
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return self.request.POST['next']
        else:
            return reverse_lazy('network.domain_list')

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        if unicode(self.object) != request.POST.get('confirm'):
            messages.error(request, _(u"Object name does not match."))
            return self.get(request, *args, **kwargs)

        response = super(DomainDelete, self).delete(request, *args, **kwargs)
        messages.success(request, _(u"Domain successfully deleted."))
        return response

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

        records_from_hosts = _(u'Records from hosts')
        deps = []
        # vlans
        vlans = Vlan.objects.filter(domain=self.object).all()
        if len(vlans) > 0:
            deps.append({
                'name': _('Vlans'),
                'data': vlans
            })

            # hosts
            hosts = Host.objects.filter(vlan__in=deps[0]['data'])
            if len(hosts) > 0:
                deps.append({
                    'name': _('Hosts'),
                    'data':  hosts
                })

                # records
                records = Record.objects.filter(
                    host__in=deps[1]['data']
                    # Q(domain=self.object) | (host__in=deps[1]['data'])
                )
                if len(records) > 0:
                    deps.append({
                        'name': records_from_hosts,
                        'data': records
                    })

        records = Record.objects.filter(domain=self.object)
        if len(records) > 0:
            # to filter out doubles (records from hosts and domains)
            indexes = map(itemgetter('name'), deps)
            n = indexes.index(records_from_hosts) if len(indexes) > 0 else 0
            deps.append({
                'name': u'Records only from the domain',
                'data': records.exclude(pk__in=deps[n]['data']) if n > 0
                else records
            })

        context['deps'] = deps
        context['confirmation'] = True
        return context


class FirewallList(LoginRequiredMixin, SuperuserRequiredMixin,
                   SingleTableView):
    model = Firewall
    table_class = FirewallTable
    template_name = "network/firewall-list.html"
    table_pagination = False


class FirewallDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                     SuccessMessageMixin, UpdateView):
    model = Firewall
    template_name = "network/firewall-edit.html"
    form_class = FirewallForm
    success_message = _(u'Succesfully modified firewall.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.firewall', kwargs=self.kwargs)

    def get_context_data(self, **kwargs):
        context = super(FirewallDetail, self).get_context_data(**kwargs)
        rules = Rule.objects.filter(firewall=self.object)
        context['rule_table'] = FirewallRuleTable(rules,
                                                  request=self.request)
        return context


class FirewallCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                     SuccessMessageMixin, CreateView):
    model = Firewall
    template_name = "network/firewall-create.html"
    form_class = FirewallForm
    success_message = _(u'Successfully created firewall.')


class FirewallDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Firewall
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.firewall_list')


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


class GroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                  SuccessMessageMixin, CreateView):
    model = Group
    template_name = "network/group-create.html"
    form_class = GroupForm
    success_message = _(u'Successfully created host group %(name)s.')


class GroupDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                  SuccessMessageMixin, UpdateView):
    model = Group
    template_name = "network/group-edit.html"
    form_class = GroupForm
    success_message = _(u'Successfully modified host group %(name)s.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.group', kwargs=self.kwargs)

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

        context['group_pk'] = self.object.pk

        # records
        q = Rule.objects.filter(hostgroup=self.object)
        context['rule_list'] = SmallRuleTable(q)
        return context


class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Group
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return self.request.POST['next']
        else:
            return reverse_lazy('network.group_list')

    def get_context_data(self, *args, **kwargs):
        context = super(GroupDelete, self).get_context_data(**kwargs)
        context['group_pk'] = self.object.pk
        return context


class HostMagicMixin(MagicMixin):
    def _get_ajax(self, *args, **kwargs):
        GET = self.request.GET
        result = {}
        vlan = get_object_or_404(Vlan.objects, pk=GET.get("vlan", ""))
        if "ipv4" in GET:
            try:
                result["ipv6"] = vlan.convert_ipv4_to_ipv6(GET["ipv4"]) or ""
            except:
                result["ipv6"] = ""
        else:
            try:
                result.update(vlan.get_new_address())
            except ValidationError:
                result["ipv4"] = ""
                result["ipv6"] = ""
        return result


class HostList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    model = Host
    table_class = HostTable
    template_name = "network/host-list.html"
    table_pagination = False

    def get_context_data(self, **kwargs):
        context = super(HostList, self).get_context_data(**kwargs)
        q = Vlan.objects.all().order_by("name")
        context['vlans'] = q
        return context

    def get_table_data(self):
        vlan_id = self.request.GET.get('vlan')
        if vlan_id:
            data = Host.objects.filter(vlan=vlan_id).select_related()
        else:
            data = Host.objects.select_related()

        search = self.request.GET.get("s")
        if search:
            data = data.filter(Q(hostname__icontains=search) |
                               Q(ipv4=search))  # ipv4 does not work TODO
        return data


class HostDetail(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, UpdateView):
    model = Host
    template_name = "network/host-edit.html"
    form_class = HostForm
    success_message = _(u'Successfully modified host %(hostname)s.')

    def _get_ajax(self, *args, **kwargs):
        if "vlan" not in self.request.GET:
            host = Host.objects.get(pk=kwargs['pk'])
            host = {
                'hostname': host.hostname,
                'ipv4': str(host.ipv4),
                'ipv6': str(host.ipv6),
                'fqdn': host.get_fqdn()
            }
            return host
        else:
            return super(HostDetail, self)._get_ajax(*args, **kwargs)

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(HostDetail, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        pk = self.kwargs.get('pk')
        # this is usually not None (well, with curl and whatnot it can be)
        if pk:
            groups = Host.objects.get(pk=pk).groups.all()
            groups = [i.pk for i in groups]
            # request.POST is immutable
            post_copy = request.POST.copy()
            post_copy.setlist('groups', groups)
            request.POST = post_copy
            return super(HostDetail, self).post(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(HostDetail, self).get_context_data(**kwargs)
        # own rules
        q = Rule.objects.filter(host=self.object).all()
        context['rule_list'] = SmallRuleTable(q)

        # rules from host groups
        group_rule_list = []
        for group in self.object.groups.all():
            q = Rule.objects.filter(hostgroup=group).all()
            group_rule_list.append({
                'table': SmallGroupRuleTable(q),
                'name': unicode(group),
                'pk': group.pk
            })
        context['group_rule_list'] = group_rule_list

        # available groups
        rest = Group.objects.exclude(pk__in=self.object.groups.all()).all()
        context['not_used_groups'] = rest

        # set host pk (we need this for URL-s)
        context['host_pk'] = self.kwargs['pk']

        from network.tables import HostRecordsTable
        context['records_table'] = HostRecordsTable(
            Record.objects.filter(host=self.get_object()),
            request=self.request, template="django_tables2/table_no_page.html"
        )

        return context

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.host', kwargs=self.kwargs)


class HostCreate(HostMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, CreateView):
    model = Host
    template_name = "network/host-create.html"
    form_class = HostForm
    success_message = _(u'Successfully created host %(hostname)s.')

    def get_initial(self):
        initial = super(HostCreate, self).get_initial()

        for i in ("vlan", "mac", "hostname"):
            if i in self.request.GET and i not in self.request.POST:
                initial[i] = self.request.GET[i]
        if "vlan" in initial:
            if not initial['vlan'].isnumeric():
                raise Http404()
            vlan = get_object_or_404(Vlan.objects, pk=initial['vlan'])
            try:
                initial.update(vlan.get_new_address())
            except ValidationError as e:
                messages.error(self.request, e.message)
        return initial


class HostDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Host
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return self.request.POST['next']
        else:
            return reverse_lazy('network.host_list')

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

        deps = []
        records = Record.objects.filter(host=self.object).all()
        if records:
            deps.append({
                'name': _('Records'),
                'data': records
            })

        context['deps'] = deps
        context['confirmation'] = True
        return context

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        if unicode(self.object) != request.POST.get('confirm'):
            messages.error(request, _(u"Object name does not match."))
            return self.get(request, *args, **kwargs)

        response = super(HostDelete, self).delete(request, *args, **kwargs)
        messages.success(request, _(u"Host successfully deleted."))
        return response


class RecordList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    model = Record
    table_class = RecordTable
    template_name = "network/record-list.html"
    table_pagination = False

    def get_context_data(self, **kwargs):
        context = super(RecordList, self).get_context_data(**kwargs)
        context['types'] = Record.CHOICES_type
        return context

    def get_table_data(self):
        type_id = self.request.GET.get('type')
        if type_id:
            data = Record.objects.filter(type=type_id).select_related()
        else:
            data = Record.objects.select_related()
        return data


class RecordDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                   SuccessMessageMixin, UpdateView):
    model = Record
    template_name = "network/record-edit.html"
    form_class = RecordForm
    # TODO fqdn
    success_message = _(u'Successfully modified record.')

    def get_context_data(self, **kwargs):
        context = super(RecordDetail, self).get_context_data(**kwargs)
        context['fqdn'] = self.object.fqdn
        context['record_pk'] = self.object.pk
        return context

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.record', kwargs=self.kwargs)


class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                   SuccessMessageMixin, CreateView):
    model = Record
    template_name = "network/record-create.html"
    form_class = RecordForm
    # TODO fqdn
    success_message = _(u'Successfully created record.')

    def get_initial(self):
        initial = super(RecordCreate, self).get_initial()
        initial['domain'] = self.request.GET.get('domain')

        host_pk = self.request.GET.get("host")
        try:
            host = Host.objects.get(pk=host_pk)
        except (Host.DoesNotExist, ValueError):
            host = None

        if host:
            initial.update({
                'type': "CNAME",
                'host': host,
                'address': host.get_fqdn(),
            })

        return initial


class RecordDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Record
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return self.request.POST['next']
        else:
            return reverse_lazy('network.record_list')


class RuleList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    model = Rule
    table_class = RuleTable
    template_name = "network/rule-list.html"
    table_pagination = False

    def get_context_data(self, **kwargs):
        self.types = OrderedDict([
            ('vlan', _("Vlan")), ('vlangroup', _("Vlan group")),
            ('host', _("Host")), ('hostgroup', _("Host group")),
            ('firewall', _("Firewall"))
        ])
        context = super(RuleList, self).get_context_data(**kwargs)
        context['types'] = self.types
        return context

    def get_table_data(self):
        rules = Rule.objects.select_related('host', 'hostgroup', 'vlan',
                                            'vlangroup', 'firewall',
                                            'foreign_network', 'owner')

        rule_type = self.request.GET.get("type")
        if rule_type and rule_type in self.types.keys():
            rules = rules.filter(**{'%s__isnull' % rule_type: False})
        return rules


class RuleDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, UpdateView):
    model = Rule
    template_name = "network/rule-edit.html"
    form_class = RuleForm
    success_message = _(u'Successfully modified rule.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.rule', kwargs=self.kwargs)

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

        rule = self.get_object()

        context['rule'] = rule
        return context


class RuleCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, CreateView):
    model = Rule
    template_name = "network/rule-create.html"
    form_class = RuleForm
    success_message = _(u'Successfully created rule.')

    def get_initial(self):
        initial = super(RuleCreate, self).get_initial()
        initial.update({
            'host': self.request.GET.get('host'),
            'hostgroup': self.request.GET.get('hostgroup')
        })
        return initial


class RuleDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Rule
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.rule_list')


class SwitchPortList(LoginRequiredMixin, SuperuserRequiredMixin,
                     SingleTableView):
    model = SwitchPort
    table_class = SwitchPortTable
    template_name = "network/switch-port-list.html"
    table_pagination = False


class RuleCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, CreateView):
    model = Rule
    template_name = "network/rule-create.html"
    form_class = RuleForm
    success_message = _(u'Successfully created rule.')

    def get_initial(self):
        initial = super(RuleCreate, self).get_initial()
        initial.update({
            'host': self.request.GET.get('host'),
            'hostgroup': self.request.GET.get('hostgroup')
        })
        return initial


class SwitchPortDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                       SuccessMessageMixin, UpdateView):
    model = SwitchPort
    template_name = "network/switch-port-edit.html"
    form_class = SwitchPortForm
    success_message = _(u'Succesfully modified switch port.')

    def get_success_url(self):
        if 'pk' in self.kwargs:
            return reverse_lazy('network.switch_port', kwargs=self.kwargs)

    def get_context_data(self, **kwargs):
        context = super(SwitchPortDetail, self).get_context_data(**kwargs)
        context['switch_port_pk'] = self.object.pk
        context['devices'] = EthernetDevice.objects.filter(
            switch_port=self.object.pk)
        return context


class SwitchPortCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                       SuccessMessageMixin, CreateView):
    model = SwitchPort
    template_name = "network/switch-port-create.html"
    form_class = SwitchPortForm
    success_message = _(u'Successfully created switch port.')


class SwitchPortDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = SwitchPort
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.switch_port_list')


class VlanList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    model = Vlan
    table_class = VlanTable
    template_name = "network/vlan-list.html"
    table_pagination = False


class VlanAclUpdateView(AclUpdateView):
    model = Vlan


class VlanMagicMixin(MagicMixin):
    def _get_ajax(self, *args, **kwargs):
        GET = self.request.GET
        result = {}
        if "network4" in GET and "network6" in GET:
            try:
                result["ipv6_template"], result["host_ipv6_prefixlen"] = (
                    Vlan._magic_ipv6_template(IPNetwork(GET['network4']),
                                              IPNetwork(GET['network6'])))
            except:
                result["ipv6_template"] = result["host_ipv6_prefixlen"] = ""
        return result


class VlanDetail(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, UpdateView):
    model = Vlan
    template_name = "network/vlan-edit.html"
    form_class = VlanForm
    slug_field = 'vid'
    slug_url_kwarg = 'vid'
    success_message = _(u'Succesfully modified vlan %(name)s.')
    success_url = reverse_lazy('network.vlan_list')

    def get_context_data(self, **kwargs):
        context = super(VlanDetail, self).get_context_data(**kwargs)
        context['host_list'] = SmallHostTable(self.object.host_set.all())
        context['dhcp_list'] = SmallDhcpTable(self.object.get_dhcp_clients())
        context['vlan_vid'] = self.kwargs.get('vid')
        context['acl'] = AclUpdateView.get_acl_data(
            self.object, self.request.user, 'network.vlan-acl')
        context['aclform'] = AclUserOrGroupAddForm()
        return context


class VlanCreate(VlanMagicMixin, LoginRequiredMixin, SuperuserRequiredMixin,
                 SuccessMessageMixin, CreateView):
    model = Vlan
    template_name = "network/vlan-create.html"
    form_class = VlanForm
    success_message = _(u'Successfully created vlan %(name)s.')


class VlanDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Vlan
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.vlan_list')

    def get_object(self, queryset=None):
        """ we identify vlans by vid and not pk """
        return Vlan.objects.get(vid=self.kwargs['vid'])

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        if unicode(self.object) != request.POST.get('confirm'):
            messages.error(request, _(u"Object name does not match."))
            return self.get(request, *args, **kwargs)

        response = super(VlanDelete, self).delete(request, *args, **kwargs)
        messages.success(request, _(u"Vlan successfully deleted."))
        return response

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

        deps = []
        # hosts
        hosts = Host.objects.filter(vlan=self.object).all()
        if len(hosts) > 0:
            deps.append({
                'name': _('Hosts'),
                'data': hosts
            })

            # records
            records = Record.objects.filter(host__in=deps[0]['data'])
            if len(records) > 0:
                deps.append({
                    'name': _('Records'),
                    'data':  records
                })

        context['deps'] = deps
        context['confirmation'] = True
        return context


class VlanGroupList(LoginRequiredMixin, SuperuserRequiredMixin,
                    SingleTableView):
    model = VlanGroup
    table_class = VlanGroupTable
    template_name = "network/vlan-group-list.html"
    table_pagination = False


class VlanGroupDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                      SuccessMessageMixin, UpdateView):
    model = VlanGroup
    template_name = "network/vlan-group-edit.html"
    form_class = VlanGroupForm
    success_url = reverse_lazy('network.vlan_group_list')
    success_message = _(u'Successfully modified vlan group %(name)s.')

    def get_context_data(self, *args, **kwargs):
        context = super(VlanGroupDetail, self).get_context_data(**kwargs)
        context['vlangroup_pk'] = self.object.pk
        return context


class VlanGroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                      SuccessMessageMixin, CreateView):
    model = VlanGroup
    template_name = "network/vlan-group-create.html"
    form_class = VlanGroupForm
    success_message = _(u'Successfully created vlan group %(name)s.')


class VlanGroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = VlanGroup
    template_name = "network/confirm/base_delete.html"

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.vlan_group_list')


class VxlanList(LoginRequiredMixin, SingleTableView):
    model = Vxlan
    table_class = VxlanTable
    table_pagination = False

    def get_template_names(self):
        if self.request.user.is_superuser:
            return ["network/vxlan-superuser-list.html"]
        else:
            return ["network/vxlan-list.html"]

    def get_queryset(self):
        return Vxlan.get_objects_with_level('user', self.request.user)

    def get(self, *args, **kwargs):
        if self.request.is_ajax():
            return self._create_ajax_request()
        return super(VxlanList, self).get(*args, **kwargs)

    def _create_ajax_request(self):
        vxlans = self.get_queryset()
        vxlans = [{
            'pk': i.pk,
            'url': reverse_lazy('network.vxlan', args=[i.pk]),
            'icon': 'fa-sitemap',
            'name': i.name,
            'vni': i.vni if self.request.user.is_superuser else None
        } for i in vxlans]
        return JsonResponse(list(vxlans), safe=False)


class VxlanAclUpdateView(AclUpdateView):
    model = Vxlan


class VxlanDetail(LoginRequiredMixin, SuccessMessageMixin, DetailView): #TODO: check user
    # model = Network
    success_message = _(u'Succesfully modified vlan %(name)s.')
    success_url = reverse_lazy('network.vxlan-list')
    template_name = 'network/vxlan-edit.html'
    context_object_name = 'network'

    def get_object(self, queryset=None):
        return openstack_api.neutron.network_get(self.request, self.kwargs['pk'], expand_subnet=True)

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

        subnet = self.object.subnets[0] if len(self.object.subnets) > 0 else None

        form = VxlanForm(initial={
            'name': self.object.name,
            'networkAddress': subnet.cidr if subnet else None,
            'isDhcpEnabled': subnet.enable_dhcp if subnet else False,
        })

        context['isMultipleSubnetsAvailable'] = len(self.object.subnets) > 1
        context['isSubnetMissing'] = len(self.object.subnets) < 1
        context['vm_list'] = () #SmallVmTable(self.object.vm_interface.all())
        context['form'] = form
        return context

class VxlanCreate(LoginRequiredMixin, FormView):
    form_class = VxlanForm
    template_name = 'network/vxlan-create.html'

    def form_valid(self, form):
        network_created = openstack_api.neutron.network_create(self.request, name=form.cleaned_data['name'])
        try:
            if form.cleaned_data['isAdvancedConfig']:
                openstack_api.neutron.subnet_create(
                    self.request,
                    network_created.id,
                    ip_version=4, #TODO: there should be a radio button for protocol
                    cidr=form.cleaned_data['networkAddress'],
                    enable_dhcp=form.cleaned_data['isDhcpEnabled'],
                )
            else:
                # TODO: default ip version should read from SETTINGS
                openstack_api.neutron.subnet_create(
                    self.request,
                    network_created.id,
                    ip_version=4,
                    subnetpool_id=SubnetPool.get_id(self.request)
                )
        except:
            openstack_api.neutron.network_delete(self.request, network_created.id)
            #TODO: user friendly error handling
            raise Exception("Could not create subnet for network, deleted network.")

        return redirect(reverse_lazy('network.vxlan', kwargs={'pk': network_created.id}))

class VxlanDelete(LoginRequiredMixin, DeleteView): #TODO: check user
    model = Vlan
    read_level = 'owner'

    def get_template_names(self):
        if self.request.user.is_superuser:
            return ["network/confirm/base_delete.html"]
        else:
            return ["dashboard/confirm/base-delete.html"]

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('network.vxlan-list')

    def get_object(self, queryset=None):
        """ we identify vlans by vid and not pk """
        return Vxlan.objects.get(vni=self.kwargs['vni'])

    def delete(self, request, *args, **kwargs):
        if self.request.user.is_superuser:
            self.object = self.get_object()
            if unicode(self.object) != request.POST.get('confirm'):
                messages.error(request, _(u"Object name does not match."))
                return self.get(request, *args, **kwargs)

        response = super(VxlanDelete, self).delete(request, *args, **kwargs)
        messages.success(request, _(u"Vxlan successfully deleted."))
        return response

    def get_context_data(self, **kwargs):
        context = super(VxlanDelete, self).get_context_data(**kwargs)
        if self.request.user.is_superuser:
            context['confirmation'] = True
        return context

class TopologyBaseTab():
    def get_context_data(request, context=None):
        """Returns common context data for network topology views."""
        if context is None:
            context = {}

        context['launch_instance_allowed'] = True
        context['instance_quota_exceeded'] = False
        context['create_network_allowed'] = True
        context['network_quota_exceeded'] = False
        context['create_router_allowed'] = True
        context['router_quota_exceeded'] = False
        context['console_type'] = 'VNC'
        context['show_ng_launch'] = True
        context['show_legacy_launch'] = True
        return context

class GraphTab(TopologyBaseTab):
    name = _("Graph")
    slug = "graph"
    preload = True
    template_name = ("project/network_topology/_graph_view.html")

class NetworkEditorView(LoginRequiredMixin, TemplateView):
    tab_group_class = GraphTab
    template_name = 'network/editor.html'

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

        context['launch_instance_allowed'] = True
        context['instance_quota_exceeded'] = False
        context['create_network_allowed'] = True
        context['network_quota_exceeded'] = False
        context['create_router_allowed'] = True
        context['router_quota_exceeded'] = False
        context['console_type'] = 'VNC'
        context['show_ng_launch'] = True
        context['show_legacy_launch'] = True

        return context

class NetworkEditorDataView(LoginRequiredMixin, View):
    @property
    def is_router_enabled(self):
        # TODO
        return True

    def add_resource_url(self, view, resources):
        pass #TODO
        # tenant_id = self.request.user.tenant_id
        # for resource in resources:
        #     if (resource.get('tenant_id')
        #             and tenant_id != resource.get('tenant_id')):
        #         continue
        #     resource['url'] = reverse(view, None, [str(resource['id'])])

    def _check_router_external_port(self, ports, router_id, network_id):
        for port in ports:
            if (port['network_id'] == network_id
                    and port['device_id'] == router_id):
                return True
        return False

    def _get_networks(self, request):
        # Get neutron data
        # if we didn't specify tenant_id, all networks shown as admin user.
        # so it is need to specify the networks. However there is no need to
        # specify tenant_id for subnet. The subnet which belongs to the public
        # network is needed to draw subnet information on public network.
        try:
            neutron_networks = openstack_api.neutron.network_list_for_tenant(
                request,
                request.user.tenant_id)
        except Exception:
            neutron_networks = []
        networks = []
        for network in neutron_networks:
            obj = {'name': network.name_or_id,
                   'id': network.id,
                   'subnets': [{'id': subnet.id,
                                'cidr': subnet.cidr}
                               for subnet in network.subnets],
                   'status': network.status, #TODO
                   'original_status': network.status,
                   'router:external': network['router:external']}
            self.add_resource_url('horizon:project:networks:subnets:detail',
                                  obj['subnets'])
            networks.append(obj)

        return networks

    def _get_servers(self, request):
        console_invalid_status = {
            'shutoff', 'suspended', 'resize', 'verify_resize',
            'revert_resize', 'migrating', 'build', 'shelved',
            'shelved_offloaded'}

        # Get nova data
        try:
            servers, more = openstack_api.nova.server_list(request)
        except Exception:
            servers = []
        data = []
        console_type = "VNC"
        # lowercase of the keys will be used at the end of the console URL.
        for server in servers:
            server_data = {'name': server.name,
                           'status': server.status, #TODO
                           'original_status': server.status,
                           'task': getattr(server, 'OS-EXT-STS:task_state'),
                           'id': server.id}
            # Avoid doing extra calls for console if the server is in
            # a invalid status for console connection
            if server.status.lower() not in console_invalid_status:
                if console_type:
                    server_data['console'] = 'auto_console'

            data.append(server_data)
        self.add_resource_url('horizon:project:instances:detail', data)
        return data

    def _get_ports(self, request, networks):
        try:
            neutron_ports = openstack_api.neutron.port_list(request)
        except Exception:
            neutron_ports = []

        # we should filter out ports connected to non tenant networks
        # which they have no visibility to
        tenant_network_ids = [network['id'] for network in networks]
        ports = [{'id': port.id,
                  'network_id': port.network_id,
                  'device_id': port.device_id,
                  'fixed_ips': port.fixed_ips,
                  'device_owner': port.device_owner,
                  'status': port.status, #TODO
                  'original_status': port.status}
                 for port in neutron_ports
                 if port.device_owner != 'network:router_ha_interface'
                 and port.network_id in tenant_network_ids]
        self.add_resource_url('horizon:project:networks:ports:detail',
                              ports)
        return ports

    def _prepare_gateway_ports(self, routers, ports):
        # user can't see port on external network. so we are
        # adding fake port based on router information
        for router in routers:
            external_gateway_info = router.get('external_gateway_info')
            if not external_gateway_info:
                continue
            external_network = external_gateway_info.get(
                'network_id')
            if not external_network:
                continue
            if self._check_router_external_port(ports,
                                                router['id'],
                                                external_network):
                continue
            fake_port = {'id': 'gateway%s' % external_network,
                         'network_id': external_network,
                         'device_id': router['id'],
                         'fixed_ips': []}
            ports.append(fake_port)

    def _get_routers(self, request):
        if not self.is_router_enabled:
            return []
        try:
            neutron_routers = openstack_api.neutron.router_list(
                request,
                tenant_id=request.user.tenant_id)
        except Exception:
            neutron_routers = []

        routers = [{'id': router.id,
                    'name': router.name_or_id,
                    'status': router.status, #TODO
                    'original_status': router.status,
                    'external_gateway_info': router.external_gateway_info}
                   for router in neutron_routers]
        self.add_resource_url('horizon:project:routers:detail', routers)
        return routers

    def get(self, request, *args, **kwargs):
        networks = self._get_networks(request)
        data = {'servers': self._get_servers(request),
                'networks': networks,
                'ports': self._get_ports(request, networks),
                'routers': self._get_routers(request)}
        self._prepare_gateway_ports(data['routers'], data['ports'])
        json_string = json.dumps(data, cls=LazyTranslationEncoder,
                                 ensure_ascii=False)
        return HttpResponse(json_string, content_type='text/json')

def remove_host_group(request, **kwargs):
    host = Host.objects.get(pk=kwargs['pk'])
    group = Group.objects.get(pk=kwargs['group_pk'])

    # for get we show the confirmation page
    if request.method == "GET":
        return render(request,
                      'network/confirm/remove_host_group.html',
                      {
                          'group': group.name,
                          'host': host.hostname
                      })

    # for post we actually remove the group from the host
    elif request.method == "POST":
        host.groups.remove(group)
        if not request.is_ajax():
            messages.success(request, _(u"Successfully removed %(host)s from "
                                        "%(group)s group." % {
                                            'host': host,
                                            'group': group
                                        }))
        return redirect(reverse_lazy('network.host',
                                     kwargs={'pk': kwargs['pk']}))


def add_host_group(request, **kwargs):
    group_pk = request.POST.get('group')
    if request.method == "POST" and group_pk:
        host = Host.objects.get(pk=kwargs['pk'])
        group = Group.objects.get(pk=group_pk)
        host.groups.add(group)
        if not request.is_ajax():
            messages.success(request, _(u"Successfully added %(host)s to group"
                                        " %(group)s." % {
                                            'host': host,
                                            'group': group
                                        }))
        return redirect(reverse_lazy('network.host', kwargs=kwargs))


def remove_switch_port_device(request, **kwargs):
    device = EthernetDevice.objects.get(pk=kwargs['device_pk'])
    # for get we show the confirmation page
    if request.method == "GET":
        return render(request, 'network/confirm/base_delete.html',
                      {'object': device})

    # for post we actually remove the group from the host
    elif request.method == "POST":
        device.delete()
        if not request.is_ajax():
            messages.success(request, _(u"Successfully deleted ethernet device"
                                        " %(name)s." % {
                                            'name': device.name,
                                        }))
        return redirect(reverse_lazy('network.switch_port',
                                     kwargs={'pk': kwargs['pk']}))


def add_switch_port_device(request, **kwargs):
    device_name = request.POST.get('device_name')

    if (request.method == "POST" and device_name and len(device_name) > 0 and
            EthernetDevice.objects.filter(name=device_name).count() == 0):

        switch_port = SwitchPort.objects.get(pk=kwargs['pk'])
        new_device = EthernetDevice(name=device_name, switch_port=switch_port)
        new_device.save()
        if not request.is_ajax():
            messages.success(request, _(u"Successfully added %(name)s to this"
                                        " switch port" % {
                                            'name': device_name,
                                        }))
        return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))

    elif not len(device_name) > 0:
        messages.error(request, _("Ethernet device name cannot be empty."))
        return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))
    elif EthernetDevice.objects.get(name=device_name) is not None:
        messages.error(request, _("There is already an ethernet device with"
                                  " that name."))
        return redirect(reverse_lazy('network.switch_port', kwargs=kwargs))
