from django.contrib.contenttypes.models import ContentType
from django.db.models import Prefetch
from django.db.models.expressions import RawSQL
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse

from circuits.models import Provider
from circuits.tables import ProviderTable
from dcim.filtersets import InterfaceFilterSet
from dcim.models import Interface, Site
from dcim.tables import SiteTable
from netbox.views import generic
from utilities.utils import count_related
from virtualization.filtersets import VMInterfaceFilterSet
from virtualization.models import VMInterface
from . import filtersets, forms, tables
from .constants import *
from .models import *
from .models import ASN
from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans


#
# VRFs
#

class VRFListView(generic.ObjectListView):
    queryset = VRF.objects.all()
    filterset = filtersets.VRFFilterSet
    filterset_form = forms.VRFFilterForm
    table = tables.VRFTable


class VRFView(generic.ObjectView):
    queryset = VRF.objects.all()

    def get_extra_context(self, request, instance):
        prefix_count = Prefix.objects.restrict(request.user, 'view').filter(vrf=instance).count()
        ipaddress_count = IPAddress.objects.restrict(request.user, 'view').filter(vrf=instance).count()

        import_targets_table = tables.RouteTargetTable(
            instance.import_targets.prefetch_related('tenant'),
            orderable=False
        )
        export_targets_table = tables.RouteTargetTable(
            instance.export_targets.prefetch_related('tenant'),
            orderable=False
        )

        return {
            'prefix_count': prefix_count,
            'ipaddress_count': ipaddress_count,
            'import_targets_table': import_targets_table,
            'export_targets_table': export_targets_table,
        }


class VRFEditView(generic.ObjectEditView):
    queryset = VRF.objects.all()
    form = forms.VRFForm


class VRFDeleteView(generic.ObjectDeleteView):
    queryset = VRF.objects.all()


class VRFBulkImportView(generic.BulkImportView):
    queryset = VRF.objects.all()
    model_form = forms.VRFCSVForm
    table = tables.VRFTable


class VRFBulkEditView(generic.BulkEditView):
    queryset = VRF.objects.prefetch_related('tenant')
    filterset = filtersets.VRFFilterSet
    table = tables.VRFTable
    form = forms.VRFBulkEditForm


class VRFBulkDeleteView(generic.BulkDeleteView):
    queryset = VRF.objects.prefetch_related('tenant')
    filterset = filtersets.VRFFilterSet
    table = tables.VRFTable


#
# Route targets
#

class RouteTargetListView(generic.ObjectListView):
    queryset = RouteTarget.objects.all()
    filterset = filtersets.RouteTargetFilterSet
    filterset_form = forms.RouteTargetFilterForm
    table = tables.RouteTargetTable


class RouteTargetView(generic.ObjectView):
    queryset = RouteTarget.objects.all()

    def get_extra_context(self, request, instance):
        importing_vrfs_table = tables.VRFTable(
            instance.importing_vrfs.prefetch_related('tenant'),
            orderable=False
        )
        exporting_vrfs_table = tables.VRFTable(
            instance.exporting_vrfs.prefetch_related('tenant'),
            orderable=False
        )

        return {
            'importing_vrfs_table': importing_vrfs_table,
            'exporting_vrfs_table': exporting_vrfs_table,
        }


class RouteTargetEditView(generic.ObjectEditView):
    queryset = RouteTarget.objects.all()
    form = forms.RouteTargetForm


class RouteTargetDeleteView(generic.ObjectDeleteView):
    queryset = RouteTarget.objects.all()


class RouteTargetBulkImportView(generic.BulkImportView):
    queryset = RouteTarget.objects.all()
    model_form = forms.RouteTargetCSVForm
    table = tables.RouteTargetTable


class RouteTargetBulkEditView(generic.BulkEditView):
    queryset = RouteTarget.objects.prefetch_related('tenant')
    filterset = filtersets.RouteTargetFilterSet
    table = tables.RouteTargetTable
    form = forms.RouteTargetBulkEditForm


class RouteTargetBulkDeleteView(generic.BulkDeleteView):
    queryset = RouteTarget.objects.prefetch_related('tenant')
    filterset = filtersets.RouteTargetFilterSet
    table = tables.RouteTargetTable


#
# RIRs
#

class RIRListView(generic.ObjectListView):
    queryset = RIR.objects.annotate(
        aggregate_count=count_related(Aggregate, 'rir')
    )
    filterset = filtersets.RIRFilterSet
    filterset_form = forms.RIRFilterForm
    table = tables.RIRTable


class RIRView(generic.ObjectView):
    queryset = RIR.objects.all()

    def get_extra_context(self, request, instance):
        aggregates = Aggregate.objects.restrict(request.user, 'view').filter(rir=instance).annotate(
            child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
        )
        aggregates_table = tables.AggregateTable(aggregates, user=request.user, exclude=('rir', 'utilization'))
        aggregates_table.configure(request)

        return {
            'aggregates_table': aggregates_table,
        }


class RIREditView(generic.ObjectEditView):
    queryset = RIR.objects.all()
    form = forms.RIRForm


class RIRDeleteView(generic.ObjectDeleteView):
    queryset = RIR.objects.all()


class RIRBulkImportView(generic.BulkImportView):
    queryset = RIR.objects.all()
    model_form = forms.RIRCSVForm
    table = tables.RIRTable


class RIRBulkEditView(generic.BulkEditView):
    queryset = RIR.objects.annotate(
        aggregate_count=count_related(Aggregate, 'rir')
    )
    filterset = filtersets.RIRFilterSet
    table = tables.RIRTable
    form = forms.RIRBulkEditForm


class RIRBulkDeleteView(generic.BulkDeleteView):
    queryset = RIR.objects.annotate(
        aggregate_count=count_related(Aggregate, 'rir')
    )
    filterset = filtersets.RIRFilterSet
    table = tables.RIRTable


#
# ASNs
#

class ASNListView(generic.ObjectListView):
    queryset = ASN.objects.annotate(
        site_count=count_related(Site, 'asns'),
        provider_count=count_related(Provider, 'asns')
    )
    filterset = filtersets.ASNFilterSet
    filterset_form = forms.ASNFilterForm
    table = tables.ASNTable


class ASNView(generic.ObjectView):
    queryset = ASN.objects.all()

    def get_extra_context(self, request, instance):
        # Gather assigned Sites
        sites = instance.sites.restrict(request.user, 'view')
        sites_table = SiteTable(sites, user=request.user)
        sites_table.configure(request)

        # Gather assigned Providers
        providers = instance.providers.restrict(request.user, 'view')
        providers_table = ProviderTable(providers, user=request.user)
        providers_table.configure(request)

        return {
            'sites_table': sites_table,
            'sites_count': sites.count(),
            'providers_table': providers_table,
            'providers_count': providers.count(),
        }


class ASNEditView(generic.ObjectEditView):
    queryset = ASN.objects.all()
    form = forms.ASNForm


class ASNDeleteView(generic.ObjectDeleteView):
    queryset = ASN.objects.all()


class ASNBulkImportView(generic.BulkImportView):
    queryset = ASN.objects.all()
    model_form = forms.ASNCSVForm
    table = tables.ASNTable


class ASNBulkEditView(generic.BulkEditView):
    queryset = ASN.objects.annotate(
        site_count=count_related(Site, 'asns')
    )
    filterset = filtersets.ASNFilterSet
    table = tables.ASNTable
    form = forms.ASNBulkEditForm


class ASNBulkDeleteView(generic.BulkDeleteView):
    queryset = ASN.objects.annotate(
        site_count=count_related(Site, 'asns')
    )
    filterset = filtersets.ASNFilterSet
    table = tables.ASNTable


#
# Aggregates
#

class AggregateListView(generic.ObjectListView):
    queryset = Aggregate.objects.annotate(
        child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
    )
    filterset = filtersets.AggregateFilterSet
    filterset_form = forms.AggregateFilterForm
    table = tables.AggregateTable


class AggregateView(generic.ObjectView):
    queryset = Aggregate.objects.all()


class AggregatePrefixesView(generic.ObjectChildrenView):
    queryset = Aggregate.objects.all()
    child_model = Prefix
    table = tables.PrefixTable
    filterset = filtersets.PrefixFilterSet
    template_name = 'ipam/aggregate/prefixes.html'

    def get_children(self, request, parent):
        return Prefix.objects.restrict(request.user, 'view').filter(
            prefix__net_contained_or_equal=str(parent.prefix)
        ).prefetch_related('site', 'role', 'tenant', 'vlan')

    def prep_table_data(self, request, queryset, parent):
        # Determine whether to show assigned prefixes, available prefixes, or both
        show_available = bool(request.GET.get('show_available', 'true') == 'true')
        show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')

        return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)

    def get_extra_context(self, request, instance):
        return {
            'bulk_querystring': f'within={instance.prefix}',
            'active_tab': 'prefixes',
            'first_available_prefix': instance.get_first_available_prefix(),
            'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
            'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
        }


class AggregateEditView(generic.ObjectEditView):
    queryset = Aggregate.objects.all()
    form = forms.AggregateForm


class AggregateDeleteView(generic.ObjectDeleteView):
    queryset = Aggregate.objects.all()


class AggregateBulkImportView(generic.BulkImportView):
    queryset = Aggregate.objects.all()
    model_form = forms.AggregateCSVForm
    table = tables.AggregateTable


class AggregateBulkEditView(generic.BulkEditView):
    queryset = Aggregate.objects.prefetch_related('rir')
    filterset = filtersets.AggregateFilterSet
    table = tables.AggregateTable
    form = forms.AggregateBulkEditForm


class AggregateBulkDeleteView(generic.BulkDeleteView):
    queryset = Aggregate.objects.prefetch_related('rir')
    filterset = filtersets.AggregateFilterSet
    table = tables.AggregateTable


#
# Prefix/VLAN roles
#

class RoleListView(generic.ObjectListView):
    queryset = Role.objects.annotate(
        prefix_count=count_related(Prefix, 'role'),
        iprange_count=count_related(IPRange, 'role'),
        vlan_count=count_related(VLAN, 'role')
    )
    filterset = filtersets.RoleFilterSet
    filterset_form = forms.RoleFilterForm
    table = tables.RoleTable


class RoleView(generic.ObjectView):
    queryset = Role.objects.all()

    def get_extra_context(self, request, instance):
        prefixes = Prefix.objects.restrict(request.user, 'view').filter(
            role=instance
        )

        prefixes_table = tables.PrefixTable(prefixes, user=request.user, exclude=('role', 'utilization'))
        prefixes_table.configure(request)

        return {
            'prefixes_table': prefixes_table,
        }


class RoleEditView(generic.ObjectEditView):
    queryset = Role.objects.all()
    form = forms.RoleForm


class RoleDeleteView(generic.ObjectDeleteView):
    queryset = Role.objects.all()


class RoleBulkImportView(generic.BulkImportView):
    queryset = Role.objects.all()
    model_form = forms.RoleCSVForm
    table = tables.RoleTable


class RoleBulkEditView(generic.BulkEditView):
    queryset = Role.objects.all()
    filterset = filtersets.RoleFilterSet
    table = tables.RoleTable
    form = forms.RoleBulkEditForm


class RoleBulkDeleteView(generic.BulkDeleteView):
    queryset = Role.objects.all()
    table = tables.RoleTable


#
# Prefixes
#

class PrefixListView(generic.ObjectListView):
    queryset = Prefix.objects.all()
    filterset = filtersets.PrefixFilterSet
    filterset_form = forms.PrefixFilterForm
    table = tables.PrefixTable
    template_name = 'ipam/prefix_list.html'


class PrefixView(generic.ObjectView):
    queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')

    def get_extra_context(self, request, instance):
        try:
            aggregate = Aggregate.objects.restrict(request.user, 'view').get(
                prefix__net_contains_or_equals=str(instance.prefix)
            )
        except Aggregate.DoesNotExist:
            aggregate = None

        # Parent prefixes table
        parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
            Q(vrf=instance.vrf) | Q(vrf__isnull=True)
        ).filter(
            prefix__net_contains=str(instance.prefix)
        ).prefetch_related(
            'site', 'role', 'tenant'
        )
        parent_prefix_table = tables.PrefixTable(
            list(parent_prefixes),
            exclude=('vrf', 'utilization'),
            orderable=False
        )

        # Duplicate prefixes table
        duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
            vrf=instance.vrf, prefix=str(instance.prefix)
        ).exclude(
            pk=instance.pk
        ).prefetch_related(
            'site', 'role'
        )
        duplicate_prefix_table = tables.PrefixTable(
            list(duplicate_prefixes),
            exclude=('vrf', 'utilization'),
            orderable=False
        )

        return {
            'aggregate': aggregate,
            'parent_prefix_table': parent_prefix_table,
            'duplicate_prefix_table': duplicate_prefix_table,
        }


class PrefixPrefixesView(generic.ObjectChildrenView):
    queryset = Prefix.objects.all()
    child_model = Prefix
    table = tables.PrefixTable
    filterset = filtersets.PrefixFilterSet
    template_name = 'ipam/prefix/prefixes.html'

    def get_children(self, request, parent):
        return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
            'site', 'vrf', 'vlan', 'role', 'tenant',
        )

    def prep_table_data(self, request, queryset, parent):
        # Determine whether to show assigned prefixes, available prefixes, or both
        show_available = bool(request.GET.get('show_available', 'true') == 'true')
        show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true')

        return add_requested_prefixes(parent.prefix, queryset, show_available, show_assigned)

    def get_extra_context(self, request, instance):
        return {
            'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}",
            'active_tab': 'prefixes',
            'first_available_prefix': instance.get_first_available_prefix(),
            'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
            'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
        }


class PrefixIPRangesView(generic.ObjectChildrenView):
    queryset = Prefix.objects.all()
    child_model = IPRange
    table = tables.IPRangeTable
    filterset = filtersets.IPRangeFilterSet
    template_name = 'ipam/prefix/ip_ranges.html'

    def get_children(self, request, parent):
        return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
            'vrf', 'role', 'tenant',
        )

    def get_extra_context(self, request, instance):
        return {
            'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
            'active_tab': 'ip-ranges',
            'first_available_ip': instance.get_first_available_ip(),
        }


class PrefixIPAddressesView(generic.ObjectChildrenView):
    queryset = Prefix.objects.all()
    child_model = IPAddress
    table = tables.IPAddressTable
    filterset = filtersets.IPAddressFilterSet
    template_name = 'ipam/prefix/ip_addresses.html'

    def get_children(self, request, parent):
        return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant')

    def prep_table_data(self, request, queryset, parent):
        show_available = bool(request.GET.get('show_available', 'true') == 'true')
        if show_available:
            return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool)

        return queryset

    def get_extra_context(self, request, instance):
        return {
            'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
            'active_tab': 'ip-addresses',
            'first_available_ip': instance.get_first_available_ip(),
        }


class PrefixEditView(generic.ObjectEditView):
    queryset = Prefix.objects.all()
    form = forms.PrefixForm


class PrefixDeleteView(generic.ObjectDeleteView):
    queryset = Prefix.objects.all()


class PrefixBulkImportView(generic.BulkImportView):
    queryset = Prefix.objects.all()
    model_form = forms.PrefixCSVForm
    table = tables.PrefixTable


class PrefixBulkEditView(generic.BulkEditView):
    queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
    filterset = filtersets.PrefixFilterSet
    table = tables.PrefixTable
    form = forms.PrefixBulkEditForm


class PrefixBulkDeleteView(generic.BulkDeleteView):
    queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
    filterset = filtersets.PrefixFilterSet
    table = tables.PrefixTable


#
# IP Ranges
#

class IPRangeListView(generic.ObjectListView):
    queryset = IPRange.objects.all()
    filterset = filtersets.IPRangeFilterSet
    filterset_form = forms.IPRangeFilterForm
    table = tables.IPRangeTable


class IPRangeView(generic.ObjectView):
    queryset = IPRange.objects.all()


class IPRangeIPAddressesView(generic.ObjectChildrenView):
    queryset = IPRange.objects.all()
    child_model = IPAddress
    table = tables.IPAddressTable
    filterset = filtersets.IPAddressFilterSet
    template_name = 'ipam/iprange/ip_addresses.html'

    def get_children(self, request, parent):
        return parent.get_child_ips().restrict(request.user, 'view').prefetch_related(
            'vrf', 'role', 'tenant',
        )

    def get_extra_context(self, request, instance):
        return {
            'active_tab': 'ip-addresses',
        }


class IPRangeEditView(generic.ObjectEditView):
    queryset = IPRange.objects.all()
    form = forms.IPRangeForm


class IPRangeDeleteView(generic.ObjectDeleteView):
    queryset = IPRange.objects.all()


class IPRangeBulkImportView(generic.BulkImportView):
    queryset = IPRange.objects.all()
    model_form = forms.IPRangeCSVForm
    table = tables.IPRangeTable


class IPRangeBulkEditView(generic.BulkEditView):
    queryset = IPRange.objects.prefetch_related('vrf', 'tenant')
    filterset = filtersets.IPRangeFilterSet
    table = tables.IPRangeTable
    form = forms.IPRangeBulkEditForm


class IPRangeBulkDeleteView(generic.BulkDeleteView):
    queryset = IPRange.objects.prefetch_related('vrf', 'tenant')
    filterset = filtersets.IPRangeFilterSet
    table = tables.IPRangeTable


#
# IP addresses
#

class IPAddressListView(generic.ObjectListView):
    queryset = IPAddress.objects.all()
    filterset = filtersets.IPAddressFilterSet
    filterset_form = forms.IPAddressFilterForm
    table = tables.IPAddressTable


class IPAddressView(generic.ObjectView):
    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')

    def get_extra_context(self, request, instance):
        # Parent prefixes table
        parent_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
            vrf=instance.vrf,
            prefix__net_contains_or_equals=str(instance.address.ip)
        ).prefetch_related(
            'site', 'role'
        )
        parent_prefixes_table = tables.PrefixTable(
            list(parent_prefixes),
            exclude=('vrf', 'utilization'),
            orderable=False
        )

        # Duplicate IPs table
        duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
            vrf=instance.vrf,
            address=str(instance.address)
        ).exclude(
            pk=instance.pk
        ).prefetch_related(
            'nat_inside'
        )
        # Exclude anycast IPs if this IP is anycast
        if instance.role == IPAddressRoleChoices.ROLE_ANYCAST:
            duplicate_ips = duplicate_ips.exclude(role=IPAddressRoleChoices.ROLE_ANYCAST)
        # Limit to a maximum of 10 duplicates displayed here
        duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False)

        # Related IP table
        related_ips = IPAddress.objects.restrict(request.user, 'view').exclude(
            address=str(instance.address)
        ).filter(
            vrf=instance.vrf, address__net_contained_or_equal=str(instance.address)
        )
        related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
        related_ips_table.configure(request)

        return {
            'parent_prefixes_table': parent_prefixes_table,
            'duplicate_ips_table': duplicate_ips_table,
            'more_duplicate_ips': duplicate_ips.count() > 10,
            'related_ips_table': related_ips_table,
        }


class IPAddressEditView(generic.ObjectEditView):
    queryset = IPAddress.objects.all()
    form = forms.IPAddressForm
    template_name = 'ipam/ipaddress_edit.html'

    def alter_object(self, obj, request, url_args, url_kwargs):

        if 'interface' in request.GET:
            try:
                obj.assigned_object = Interface.objects.get(pk=request.GET['interface'])
            except (ValueError, Interface.DoesNotExist):
                pass

        elif 'vminterface' in request.GET:
            try:
                obj.assigned_object = VMInterface.objects.get(pk=request.GET['vminterface'])
            except (ValueError, VMInterface.DoesNotExist):
                pass

        elif 'fhrpgroup' in request.GET:
            try:
                obj.assigned_object = FHRPGroup.objects.get(pk=request.GET['fhrpgroup'])
            except (ValueError, FHRPGroup.DoesNotExist):
                pass

        return obj


# TODO: Standardize or remove this view
class IPAddressAssignView(generic.ObjectView):
    """
    Search for IPAddresses to be assigned to an Interface.
    """
    queryset = IPAddress.objects.all()

    def dispatch(self, request, *args, **kwargs):

        # Redirect user if an interface has not been provided
        if 'interface' not in request.GET and 'vminterface' not in request.GET:
            return redirect('ipam:ipaddress_add')

        return super().dispatch(request, *args, **kwargs)

    def get(self, request):
        form = forms.IPAddressAssignForm()

        return render(request, 'ipam/ipaddress_assign.html', {
            'form': form,
            'return_url': request.GET.get('return_url', ''),
        })

    def post(self, request):
        form = forms.IPAddressAssignForm(request.POST)
        table = None

        if form.is_valid():

            addresses = self.queryset.prefetch_related('vrf', 'tenant')
            # Limit to 100 results
            addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
            table = tables.IPAddressAssignTable(addresses)

        return render(request, 'ipam/ipaddress_assign.html', {
            'form': form,
            'table': table,
            'return_url': request.GET.get('return_url'),
        })


class IPAddressDeleteView(generic.ObjectDeleteView):
    queryset = IPAddress.objects.all()


class IPAddressBulkCreateView(generic.BulkCreateView):
    queryset = IPAddress.objects.all()
    form = forms.IPAddressBulkCreateForm
    model_form = forms.IPAddressBulkAddForm
    pattern_target = 'address'
    template_name = 'ipam/ipaddress_bulk_add.html'


class IPAddressBulkImportView(generic.BulkImportView):
    queryset = IPAddress.objects.all()
    model_form = forms.IPAddressCSVForm
    table = tables.IPAddressTable


class IPAddressBulkEditView(generic.BulkEditView):
    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
    filterset = filtersets.IPAddressFilterSet
    table = tables.IPAddressTable
    form = forms.IPAddressBulkEditForm


class IPAddressBulkDeleteView(generic.BulkDeleteView):
    queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
    filterset = filtersets.IPAddressFilterSet
    table = tables.IPAddressTable


#
# VLAN groups
#

class VLANGroupListView(generic.ObjectListView):
    queryset = VLANGroup.objects.annotate(
        vlan_count=count_related(VLAN, 'group')
    )
    filterset = filtersets.VLANGroupFilterSet
    filterset_form = forms.VLANGroupFilterForm
    table = tables.VLANGroupTable


class VLANGroupView(generic.ObjectView):
    queryset = VLANGroup.objects.all()

    def get_extra_context(self, request, instance):
        vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
            Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
        ).order_by('vid')
        vlans_count = vlans.count()
        vlans = add_available_vlans(vlans, vlan_group=instance)

        vlans_table = tables.VLANTable(vlans, user=request.user, exclude=('group',))
        if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
            vlans_table.columns.show('pk')
        vlans_table.configure(request)

        # Compile permissions list for rendering the object table
        permissions = {
            'add': request.user.has_perm('ipam.add_vlan'),
            'change': request.user.has_perm('ipam.change_vlan'),
            'delete': request.user.has_perm('ipam.delete_vlan'),
        }

        return {
            'vlans_count': vlans_count,
            'vlans_table': vlans_table,
            'permissions': permissions,
        }


class VLANGroupEditView(generic.ObjectEditView):
    queryset = VLANGroup.objects.all()
    form = forms.VLANGroupForm


class VLANGroupDeleteView(generic.ObjectDeleteView):
    queryset = VLANGroup.objects.all()


class VLANGroupBulkImportView(generic.BulkImportView):
    queryset = VLANGroup.objects.all()
    model_form = forms.VLANGroupCSVForm
    table = tables.VLANGroupTable


class VLANGroupBulkEditView(generic.BulkEditView):
    queryset = VLANGroup.objects.annotate(
        vlan_count=count_related(VLAN, 'group')
    )
    filterset = filtersets.VLANGroupFilterSet
    table = tables.VLANGroupTable
    form = forms.VLANGroupBulkEditForm


class VLANGroupBulkDeleteView(generic.BulkDeleteView):
    queryset = VLANGroup.objects.annotate(
        vlan_count=count_related(VLAN, 'group')
    )
    filterset = filtersets.VLANGroupFilterSet
    table = tables.VLANGroupTable


#
# FHRP groups
#

class FHRPGroupListView(generic.ObjectListView):
    queryset = FHRPGroup.objects.annotate(
        member_count=count_related(FHRPGroupAssignment, 'group')
    )
    filterset = filtersets.FHRPGroupFilterSet
    filterset_form = forms.FHRPGroupFilterForm
    table = tables.FHRPGroupTable


class FHRPGroupView(generic.ObjectView):
    queryset = FHRPGroup.objects.all()

    def get_extra_context(self, request, instance):
        # Get assigned IP addresses
        ipaddress_table = tables.AssignedIPAddressesTable(
            data=instance.ip_addresses.restrict(request.user, 'view').prefetch_related('vrf', 'tenant'),
            orderable=False
        )

        # Get assigned interfaces
        members_table = tables.FHRPGroupAssignmentTable(
            data=FHRPGroupAssignment.objects.restrict(request.user, 'view').filter(group=instance),
            orderable=False
        )
        members_table.columns.hide('group')

        return {
            'ipaddress_table': ipaddress_table,
            'members_table': members_table,
            'member_count': FHRPGroupAssignment.objects.filter(group=instance).count(),
        }


class FHRPGroupEditView(generic.ObjectEditView):
    queryset = FHRPGroup.objects.all()
    form = forms.FHRPGroupForm
    template_name = 'ipam/fhrpgroup_edit.html'

    def get_return_url(self, request, obj=None):
        return_url = super().get_return_url(request, obj)

        # If we're redirecting the user to the FHRPGroupAssignment creation form,
        # initialize the group field with the FHRPGroup we just saved.
        if return_url.startswith(reverse('ipam:fhrpgroupassignment_add')):
            return_url += f'&group={obj.pk}'

        return return_url


class FHRPGroupDeleteView(generic.ObjectDeleteView):
    queryset = FHRPGroup.objects.all()


class FHRPGroupBulkImportView(generic.BulkImportView):
    queryset = FHRPGroup.objects.all()
    model_form = forms.FHRPGroupCSVForm
    table = tables.FHRPGroupTable


class FHRPGroupBulkEditView(generic.BulkEditView):
    queryset = FHRPGroup.objects.all()
    filterset = filtersets.FHRPGroupFilterSet
    table = tables.FHRPGroupTable
    form = forms.FHRPGroupBulkEditForm


class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
    queryset = FHRPGroup.objects.all()
    filterset = filtersets.FHRPGroupFilterSet
    table = tables.FHRPGroupTable


#
# FHRP group assignments
#

class FHRPGroupAssignmentEditView(generic.ObjectEditView):
    queryset = FHRPGroupAssignment.objects.all()
    form = forms.FHRPGroupAssignmentForm
    template_name = 'ipam/fhrpgroupassignment_edit.html'

    def alter_object(self, instance, request, args, kwargs):
        if not instance.pk:
            # Assign the interface based on URL kwargs
            content_type = get_object_or_404(ContentType, pk=request.GET.get('interface_type'))
            instance.interface = get_object_or_404(content_type.model_class(), pk=request.GET.get('interface_id'))
        return instance


class FHRPGroupAssignmentDeleteView(generic.ObjectDeleteView):
    queryset = FHRPGroupAssignment.objects.all()


#
# VLANs
#

class VLANListView(generic.ObjectListView):
    queryset = VLAN.objects.all()
    filterset = filtersets.VLANFilterSet
    filterset_form = forms.VLANFilterForm
    table = tables.VLANTable


class VLANView(generic.ObjectView):
    queryset = VLAN.objects.prefetch_related('site__region', 'tenant__group', 'role')

    def get_extra_context(self, request, instance):
        prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related(
            'vrf', 'site', 'role'
        )
        prefix_table = tables.PrefixTable(list(prefixes), exclude=('vlan', 'utilization'), orderable=False)

        return {
            'prefix_table': prefix_table,
        }


class VLANInterfacesView(generic.ObjectChildrenView):
    queryset = VLAN.objects.all()
    child_model = Interface
    table = tables.VLANDevicesTable
    filterset = InterfaceFilterSet
    template_name = 'ipam/vlan/interfaces.html'

    def get_children(self, request, parent):
        return parent.get_interfaces().restrict(request.user, 'view')

    def get_extra_context(self, request, instance):
        return {
            'active_tab': 'interfaces',
        }


class VLANVMInterfacesView(generic.ObjectChildrenView):
    queryset = VLAN.objects.all()
    child_model = VMInterface
    table = tables.VLANVirtualMachinesTable
    filterset = VMInterfaceFilterSet
    template_name = 'ipam/vlan/vminterfaces.html'

    def get_children(self, request, parent):
        return parent.get_vminterfaces().restrict(request.user, 'view')

    def get_extra_context(self, request, instance):
        return {
            'active_tab': 'vminterfaces',
        }


class VLANEditView(generic.ObjectEditView):
    queryset = VLAN.objects.all()
    form = forms.VLANForm
    template_name = 'ipam/vlan_edit.html'


class VLANDeleteView(generic.ObjectDeleteView):
    queryset = VLAN.objects.all()


class VLANBulkImportView(generic.BulkImportView):
    queryset = VLAN.objects.all()
    model_form = forms.VLANCSVForm
    table = tables.VLANTable


class VLANBulkEditView(generic.BulkEditView):
    queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
    filterset = filtersets.VLANFilterSet
    table = tables.VLANTable
    form = forms.VLANBulkEditForm


class VLANBulkDeleteView(generic.BulkDeleteView):
    queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
    filterset = filtersets.VLANFilterSet
    table = tables.VLANTable


#
# Service templates
#

class ServiceTemplateListView(generic.ObjectListView):
    queryset = ServiceTemplate.objects.all()
    filterset = filtersets.ServiceTemplateFilterSet
    filterset_form = forms.ServiceTemplateFilterForm
    table = tables.ServiceTemplateTable


class ServiceTemplateView(generic.ObjectView):
    queryset = ServiceTemplate.objects.all()


class ServiceTemplateEditView(generic.ObjectEditView):
    queryset = ServiceTemplate.objects.all()
    form = forms.ServiceTemplateForm


class ServiceTemplateDeleteView(generic.ObjectDeleteView):
    queryset = ServiceTemplate.objects.all()


class ServiceTemplateBulkImportView(generic.BulkImportView):
    queryset = ServiceTemplate.objects.all()
    model_form = forms.ServiceTemplateCSVForm
    table = tables.ServiceTemplateTable


class ServiceTemplateBulkEditView(generic.BulkEditView):
    queryset = ServiceTemplate.objects.all()
    filterset = filtersets.ServiceTemplateFilterSet
    table = tables.ServiceTemplateTable
    form = forms.ServiceTemplateBulkEditForm


class ServiceTemplateBulkDeleteView(generic.BulkDeleteView):
    queryset = ServiceTemplate.objects.all()
    filterset = filtersets.ServiceTemplateFilterSet
    table = tables.ServiceTemplateTable


#
# Services
#

class ServiceListView(generic.ObjectListView):
    queryset = Service.objects.all()
    filterset = filtersets.ServiceFilterSet
    filterset_form = forms.ServiceFilterForm
    table = tables.ServiceTable


class ServiceView(generic.ObjectView):
    queryset = Service.objects.prefetch_related('ipaddresses')


class ServiceCreateView(generic.ObjectEditView):
    queryset = Service.objects.all()
    form = forms.ServiceCreateForm
    template_name = 'ipam/service_create.html'


class ServiceEditView(generic.ObjectEditView):
    queryset = Service.objects.prefetch_related('ipaddresses')
    form = forms.ServiceForm
    template_name = 'ipam/service_edit.html'


class ServiceDeleteView(generic.ObjectDeleteView):
    queryset = Service.objects.all()


class ServiceBulkImportView(generic.BulkImportView):
    queryset = Service.objects.all()
    model_form = forms.ServiceCSVForm
    table = tables.ServiceTable


class ServiceBulkEditView(generic.BulkEditView):
    queryset = Service.objects.prefetch_related('device', 'virtual_machine')
    filterset = filtersets.ServiceFilterSet
    table = tables.ServiceTable
    form = forms.ServiceBulkEditForm


class ServiceBulkDeleteView(generic.BulkDeleteView):
    queryset = Service.objects.prefetch_related('device', 'virtual_machine')
    filterset = filtersets.ServiceFilterSet
    table = tables.ServiceTable
