import django_filters
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.http import QueryDict
from django.test import RequestFactory, TestCase
from mptt.fields import TreeForeignKey
from taggit.managers import TaggableManager

from core.models import ObjectType
from dcim.choices import *
from dcim.fields import MACAddressField
from dcim.filtersets import DeviceFilterSet, InterfaceFilterSet, SiteFilterSet
from dcim.models import (
    Device,
    DeviceRole,
    DeviceType,
    Interface,
    MACAddress,
    Manufacturer,
    Platform,
    Rack,
    Region,
    Site,
)
from extras.filters import TagFilter
from extras.models import SavedFilter, Tag, TaggedItem
from ipam.filtersets import ASNFilterSet
from ipam.models import ASN, RIR
from netbox.filtersets import BaseFilterSet
from utilities.filters import (
    MultiValueCharFilter,
    MultiValueDateFilter,
    MultiValueDateTimeFilter,
    MultiValueMACAddressFilter,
    MultiValueNumberFilter,
    MultiValueTimeFilter,
    TreeNodeMultipleChoiceFilter,
)
from wireless.choices import WirelessRoleChoices

User = get_user_model()


class TreeNodeMultipleChoiceFilterTestCase(TestCase):

    class SiteFilterSet(django_filters.FilterSet):
        region = TreeNodeMultipleChoiceFilter(
            queryset=Region.objects.all(),
            field_name='region',
            lookup_expr='in',
            to_field_name='slug',
        )

    def setUp(self):

        super().setUp()

        self.region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
        self.region2 = Region.objects.create(name='Test Region 2', slug='test-region-2')
        self.site1 = Site.objects.create(region=self.region1, name='Test Site 1', slug='test-site1')
        self.site2 = Site.objects.create(region=self.region2, name='Test Site 2', slug='test-site2')
        self.site3 = Site.objects.create(region=None, name='Test Site 3', slug='test-site3')

        self.queryset = Site.objects.all()

    def test_filter_single(self):

        kwargs = {'region': ['test-region-1']}
        qs = self.SiteFilterSet(kwargs, self.queryset).qs

        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0], self.site1)

    def test_filter_multiple(self):

        kwargs = {'region': ['test-region-1', 'test-region-2']}
        qs = self.SiteFilterSet(kwargs, self.queryset).qs

        self.assertEqual(qs.count(), 2)
        self.assertEqual(qs[0], self.site1)
        self.assertEqual(qs[1], self.site2)

    def test_filter_null(self):

        kwargs = {'region': [settings.FILTERS_NULL_CHOICE_VALUE]}
        qs = self.SiteFilterSet(kwargs, self.queryset).qs

        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0], self.site3)

    def test_filter_combined(self):

        kwargs = {'region': ['test-region-1', settings.FILTERS_NULL_CHOICE_VALUE]}
        qs = self.SiteFilterSet(kwargs, self.queryset).qs

        self.assertEqual(qs.count(), 2)
        self.assertEqual(qs[0], self.site1)
        self.assertEqual(qs[1], self.site3)


class TagFilterTestCase(TestCase):
    """
    Verify the AND (default), OR (`any`), and NOR (`n`) semantics of TagFilter/TagIDFilter.
    """
    queryset = Site.objects.all()
    filterset = SiteFilterSet

    @classmethod
    def setUpTestData(cls):
        tags = (
            Tag(name='Tag 1', slug='tag-1'),
            Tag(name='Tag 2', slug='tag-2'),
            Tag(name='Tag 3', slug='tag-3'),
        )
        Tag.objects.bulk_create(tags)

        sites = (
            Site(name='Site 1', slug='site-1'),
            Site(name='Site 2', slug='site-2'),
            Site(name='Site 3', slug='site-3'),
        )
        Site.objects.bulk_create(sites)
        sites[0].tags.set([tags[0], tags[1]])  # tag-1, tag-2
        sites[1].tags.set([tags[1]])  # tag-2 only
        sites[2].tags.set([tags[2]])  # tag-3 only

    def test_tag_and(self):
        tags = Tag.objects.filter(slug__in=('tag-1', 'tag-2'))
        params = {'tag': [tags[0].slug, tags[1].slug]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0].slug, 'site-1')

        params = {'tag_id': [tags[0].pk, tags[1].pk]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0].slug, 'site-1')

    def test_tag_any(self):
        tags = Tag.objects.filter(slug__in=('tag-1', 'tag-3'))
        params = {'tag__any': [tags[0].slug, tags[1].slug]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 2)
        self.assertEqual({site.slug for site in qs}, {'site-1', 'site-3'})

        params = {'tag_id__any': [tags[0].pk, tags[1].pk]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 2)
        self.assertEqual({site.slug for site in qs}, {'site-1', 'site-3'})

    def test_tag_negation(self):
        tags = Tag.objects.filter(slug__in=('tag-1', 'tag-3'))
        params = {'tag__n': [tags[0].slug, tags[1].slug]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0].slug, 'site-2')

        params = {'tag_id__n': [tags[0].pk, tags[1].pk]}
        qs = self.filterset(params, self.queryset).qs
        self.assertEqual(qs.count(), 1)
        self.assertEqual(qs[0].slug, 'site-2')


class DummyModel(models.Model):
    """
    Dummy model used by BaseFilterSetTest for filter validation. Should never appear in a schema migration.
    """
    charfield = models.CharField(
        max_length=10
    )
    numberfield = models.IntegerField(
        blank=True,
        null=True
    )
    choicefield = models.IntegerField(
        choices=(('A', 1), ('B', 2), ('C', 3))
    )
    datefield = models.DateField()
    datetimefield = models.DateTimeField()
    integerfield = models.IntegerField()
    macaddressfield = MACAddressField()
    timefield = models.TimeField()
    treeforeignkeyfield = TreeForeignKey(
        to='self',
        on_delete=models.CASCADE
    )

    tags = TaggableManager(through=TaggedItem)


class BaseFilterSetTestCase(TestCase):
    """
    Ensure that a BaseFilterSet automatically creates the expected set of filters for each filter type.
    """
    class DummyFilterSet(BaseFilterSet):
        charfield = django_filters.CharFilter()
        numberfield = django_filters.NumberFilter()
        macaddressfield = MultiValueMACAddressFilter()
        modelchoicefield = django_filters.ModelChoiceFilter(
            field_name='integerfield',  # We're pretending this is a ForeignKey field
            queryset=Site.objects.all()
        )
        modelmultiplechoicefield = django_filters.ModelMultipleChoiceFilter(
            field_name='integerfield',  # We're pretending this is a ForeignKey field
            queryset=Site.objects.all()
        )
        multiplechoicefield = django_filters.MultipleChoiceFilter(
            field_name='choicefield'
        )
        multivaluecharfield = MultiValueCharFilter(
            field_name='charfield'
        )
        tagfield = TagFilter()
        treeforeignkeyfield = TreeNodeMultipleChoiceFilter(
            queryset=DummyModel.objects.all()
        )

        class Meta:
            model = DummyModel
            fields = (
                'charfield',
                'numberfield',
                'choicefield',
                'datefield',
                'datetimefield',
                'integerfield',
                'macaddressfield',
                'modelchoicefield',
                'modelmultiplechoicefield',
                'multiplechoicefield',
                'tagfield',
                'timefield',
                'treeforeignkeyfield',
            )

    @classmethod
    def setUpTestData(cls):
        cls.filters = cls.DummyFilterSet().filters

    def test_char_filter(self):
        self.assertIsInstance(self.filters['charfield'], django_filters.CharFilter)
        self.assertEqual(self.filters['charfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['charfield'].exclude, False)
        self.assertEqual(self.filters['charfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['charfield__n'].exclude, True)
        self.assertEqual(self.filters['charfield__ie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['charfield__ie'].exclude, False)
        self.assertEqual(self.filters['charfield__nie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['charfield__nie'].exclude, True)
        self.assertEqual(self.filters['charfield__ic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['charfield__ic'].exclude, False)
        self.assertEqual(self.filters['charfield__nic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['charfield__nic'].exclude, True)
        self.assertEqual(self.filters['charfield__isw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['charfield__isw'].exclude, False)
        self.assertEqual(self.filters['charfield__nisw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['charfield__nisw'].exclude, True)
        self.assertEqual(self.filters['charfield__iew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['charfield__iew'].exclude, False)
        self.assertEqual(self.filters['charfield__niew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['charfield__niew'].exclude, True)
        self.assertEqual(self.filters['charfield__empty'].lookup_expr, 'empty')
        self.assertEqual(self.filters['charfield__empty'].exclude, False)
        self.assertEqual(self.filters['charfield__regex'].lookup_expr, 'regex')
        self.assertEqual(self.filters['charfield__regex'].exclude, False)
        self.assertEqual(self.filters['charfield__iregex'].lookup_expr, 'iregex')
        self.assertEqual(self.filters['charfield__iregex'].exclude, False)

    def test_number_filter(self):
        self.assertIsInstance(self.filters['numberfield'], django_filters.NumberFilter)
        self.assertEqual(self.filters['numberfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['numberfield'].exclude, False)
        self.assertEqual(self.filters['numberfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['numberfield__n'].exclude, True)
        self.assertEqual(self.filters['numberfield__lt'].lookup_expr, 'lt')
        self.assertEqual(self.filters['numberfield__lt'].exclude, False)
        self.assertEqual(self.filters['numberfield__lte'].lookup_expr, 'lte')
        self.assertEqual(self.filters['numberfield__lte'].exclude, False)
        self.assertEqual(self.filters['numberfield__gt'].lookup_expr, 'gt')
        self.assertEqual(self.filters['numberfield__gt'].exclude, False)
        self.assertEqual(self.filters['numberfield__gte'].lookup_expr, 'gte')
        self.assertEqual(self.filters['numberfield__gte'].exclude, False)
        self.assertEqual(self.filters['numberfield__empty'].lookup_expr, 'isnull')
        self.assertEqual(self.filters['numberfield__empty'].exclude, False)

    def test_mac_address_filter(self):
        self.assertIsInstance(self.filters['macaddressfield'], MultiValueMACAddressFilter)
        self.assertEqual(self.filters['macaddressfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['macaddressfield'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['macaddressfield__n'].exclude, True)
        self.assertEqual(self.filters['macaddressfield__ie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['macaddressfield__ie'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__nie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['macaddressfield__nie'].exclude, True)
        self.assertEqual(self.filters['macaddressfield__ic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['macaddressfield__ic'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__nic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['macaddressfield__nic'].exclude, True)
        self.assertEqual(self.filters['macaddressfield__isw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['macaddressfield__isw'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__nisw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['macaddressfield__nisw'].exclude, True)
        self.assertEqual(self.filters['macaddressfield__iew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['macaddressfield__iew'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__niew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['macaddressfield__niew'].exclude, True)
        self.assertEqual(self.filters['macaddressfield__regex'].lookup_expr, 'regex')
        self.assertEqual(self.filters['macaddressfield__regex'].exclude, False)
        self.assertEqual(self.filters['macaddressfield__iregex'].lookup_expr, 'iregex')
        self.assertEqual(self.filters['macaddressfield__iregex'].exclude, False)

    def test_model_choice_filter(self):
        self.assertIsInstance(self.filters['modelchoicefield'], django_filters.ModelChoiceFilter)
        self.assertEqual(self.filters['modelchoicefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['modelchoicefield'].exclude, False)
        self.assertEqual(self.filters['modelchoicefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['modelchoicefield__n'].exclude, True)

    def test_model_multiple_choice_filter(self):
        self.assertIsInstance(self.filters['modelmultiplechoicefield'], django_filters.ModelMultipleChoiceFilter)
        self.assertEqual(self.filters['modelmultiplechoicefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['modelmultiplechoicefield'].exclude, False)
        self.assertEqual(self.filters['modelmultiplechoicefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['modelmultiplechoicefield__n'].exclude, True)

    def test_multi_value_char_filter(self):
        self.assertIsInstance(self.filters['multivaluecharfield'], MultiValueCharFilter)
        self.assertEqual(self.filters['multivaluecharfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['multivaluecharfield'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['multivaluecharfield__n'].exclude, True)
        self.assertEqual(self.filters['multivaluecharfield__ie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['multivaluecharfield__ie'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__nie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['multivaluecharfield__nie'].exclude, True)
        self.assertEqual(self.filters['multivaluecharfield__ic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['multivaluecharfield__ic'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__nic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['multivaluecharfield__nic'].exclude, True)
        self.assertEqual(self.filters['multivaluecharfield__isw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['multivaluecharfield__isw'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__nisw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['multivaluecharfield__nisw'].exclude, True)
        self.assertEqual(self.filters['multivaluecharfield__iew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['multivaluecharfield__iew'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__niew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['multivaluecharfield__niew'].exclude, True)
        self.assertEqual(self.filters['multivaluecharfield__regex'].lookup_expr, 'regex')
        self.assertEqual(self.filters['multivaluecharfield__regex'].exclude, False)
        self.assertEqual(self.filters['multivaluecharfield__iregex'].lookup_expr, 'iregex')
        self.assertEqual(self.filters['multivaluecharfield__iregex'].exclude, False)

    def test_multi_value_date_filter(self):
        self.assertIsInstance(self.filters['datefield'], MultiValueDateFilter)
        self.assertEqual(self.filters['datefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['datefield'].exclude, False)
        self.assertEqual(self.filters['datefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['datefield__n'].exclude, True)
        self.assertEqual(self.filters['datefield__lt'].lookup_expr, 'lt')
        self.assertEqual(self.filters['datefield__lt'].exclude, False)
        self.assertEqual(self.filters['datefield__lte'].lookup_expr, 'lte')
        self.assertEqual(self.filters['datefield__lte'].exclude, False)
        self.assertEqual(self.filters['datefield__gt'].lookup_expr, 'gt')
        self.assertEqual(self.filters['datefield__gt'].exclude, False)
        self.assertEqual(self.filters['datefield__gte'].lookup_expr, 'gte')
        self.assertEqual(self.filters['datefield__gte'].exclude, False)

    def test_multi_value_datetime_filter(self):
        self.assertIsInstance(self.filters['datetimefield'], MultiValueDateTimeFilter)
        self.assertEqual(self.filters['datetimefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['datetimefield'].exclude, False)
        self.assertEqual(self.filters['datetimefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['datetimefield__n'].exclude, True)
        self.assertEqual(self.filters['datetimefield__lt'].lookup_expr, 'lt')
        self.assertEqual(self.filters['datetimefield__lt'].exclude, False)
        self.assertEqual(self.filters['datetimefield__lte'].lookup_expr, 'lte')
        self.assertEqual(self.filters['datetimefield__lte'].exclude, False)
        self.assertEqual(self.filters['datetimefield__gt'].lookup_expr, 'gt')
        self.assertEqual(self.filters['datetimefield__gt'].exclude, False)
        self.assertEqual(self.filters['datetimefield__gte'].lookup_expr, 'gte')
        self.assertEqual(self.filters['datetimefield__gte'].exclude, False)

    def test_multi_value_number_filter(self):
        self.assertIsInstance(self.filters['integerfield'], MultiValueNumberFilter)
        self.assertEqual(self.filters['integerfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['integerfield'].exclude, False)
        self.assertEqual(self.filters['integerfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['integerfield__n'].exclude, True)
        self.assertEqual(self.filters['integerfield__lt'].lookup_expr, 'lt')
        self.assertEqual(self.filters['integerfield__lt'].exclude, False)
        self.assertEqual(self.filters['integerfield__lte'].lookup_expr, 'lte')
        self.assertEqual(self.filters['integerfield__lte'].exclude, False)
        self.assertEqual(self.filters['integerfield__gt'].lookup_expr, 'gt')
        self.assertEqual(self.filters['integerfield__gt'].exclude, False)
        self.assertEqual(self.filters['integerfield__gte'].lookup_expr, 'gte')
        self.assertEqual(self.filters['integerfield__gte'].exclude, False)

    def test_multi_value_time_filter(self):
        self.assertIsInstance(self.filters['timefield'], MultiValueTimeFilter)
        self.assertEqual(self.filters['timefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['timefield'].exclude, False)
        self.assertEqual(self.filters['timefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['timefield__n'].exclude, True)
        self.assertEqual(self.filters['timefield__lt'].lookup_expr, 'lt')
        self.assertEqual(self.filters['timefield__lt'].exclude, False)
        self.assertEqual(self.filters['timefield__lte'].lookup_expr, 'lte')
        self.assertEqual(self.filters['timefield__lte'].exclude, False)
        self.assertEqual(self.filters['timefield__gt'].lookup_expr, 'gt')
        self.assertEqual(self.filters['timefield__gt'].exclude, False)
        self.assertEqual(self.filters['timefield__gte'].lookup_expr, 'gte')
        self.assertEqual(self.filters['timefield__gte'].exclude, False)

    def test_multiple_choice_filter(self):
        self.assertIsInstance(self.filters['multiplechoicefield'], django_filters.MultipleChoiceFilter)
        self.assertEqual(self.filters['multiplechoicefield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['multiplechoicefield'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['multiplechoicefield__n'].exclude, True)
        self.assertEqual(self.filters['multiplechoicefield__ie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['multiplechoicefield__ie'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__nie'].lookup_expr, 'iexact')
        self.assertEqual(self.filters['multiplechoicefield__nie'].exclude, True)
        self.assertEqual(self.filters['multiplechoicefield__ic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['multiplechoicefield__ic'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__nic'].lookup_expr, 'icontains')
        self.assertEqual(self.filters['multiplechoicefield__nic'].exclude, True)
        self.assertEqual(self.filters['multiplechoicefield__isw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['multiplechoicefield__isw'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__nisw'].lookup_expr, 'istartswith')
        self.assertEqual(self.filters['multiplechoicefield__nisw'].exclude, True)
        self.assertEqual(self.filters['multiplechoicefield__iew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['multiplechoicefield__iew'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__niew'].lookup_expr, 'iendswith')
        self.assertEqual(self.filters['multiplechoicefield__niew'].exclude, True)
        self.assertEqual(self.filters['multiplechoicefield__regex'].lookup_expr, 'regex')
        self.assertEqual(self.filters['multiplechoicefield__regex'].exclude, False)
        self.assertEqual(self.filters['multiplechoicefield__iregex'].lookup_expr, 'iregex')
        self.assertEqual(self.filters['multiplechoicefield__iregex'].exclude, False)

    def test_tag_filter(self):
        self.assertIsInstance(self.filters['tagfield'], TagFilter)
        self.assertEqual(self.filters['tagfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['tagfield'].exclude, False)
        self.assertEqual(self.filters['tagfield'].conjoined, True)
        self.assertEqual(self.filters['tagfield__n'].lookup_expr, 'exact')
        self.assertEqual(self.filters['tagfield__n'].exclude, True)
        self.assertEqual(self.filters['tagfield__n'].conjoined, True)
        self.assertEqual(self.filters['tagfield__any'].lookup_expr, 'exact')
        self.assertEqual(self.filters['tagfield__any'].exclude, False)
        self.assertEqual(self.filters['tagfield__any'].conjoined, False)

    def test_tree_node_multiple_choice_filter(self):
        self.assertIsInstance(self.filters['treeforeignkeyfield'], TreeNodeMultipleChoiceFilter)
        # TODO: lookup_expr different for negation?
        self.assertEqual(self.filters['treeforeignkeyfield'].lookup_expr, 'exact')
        self.assertEqual(self.filters['treeforeignkeyfield'].exclude, False)
        self.assertEqual(self.filters['treeforeignkeyfield__n'].lookup_expr, 'in')
        self.assertEqual(self.filters['treeforeignkeyfield__n'].exclude, True)


class DynamicFilterLookupExpressionTestCase(TestCase):
    """
    Validate function of automatically generated filters using the Device model as an example.
    """
    @classmethod
    def setUpTestData(cls):
        rir = RIR.objects.create(name='RIR 1', slug='rir-1')

        asns = (
            ASN(asn=65001, rir=rir),
            ASN(asn=65101, rir=rir),
            ASN(asn=65201, rir=rir),
        )
        ASN.objects.bulk_create(asns)

        manufacturers = (
            Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
            Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
            Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
        )
        Manufacturer.objects.bulk_create(manufacturers)

        device_types = (
            DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', is_full_depth=True),
            DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', is_full_depth=True),
            DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', is_full_depth=False),
        )
        DeviceType.objects.bulk_create(device_types)

        roles = (
            DeviceRole(name='Device Role 1', slug='device-role-1'),
            DeviceRole(name='Device Role 2', slug='device-role-2'),
            DeviceRole(name='Device Role 3', slug='device-role-3'),
        )
        for role in roles:
            role.save()

        platforms = (
            Platform(name='Platform 1', slug='platform-1'),
            Platform(name='Platform 2', slug='platform-2'),
            Platform(name='Platform 3', slug='platform-3'),
        )
        for platform in platforms:
            platform.save()

        regions = (
            Region(name='Region 1', slug='region-1'),
            Region(name='Region 2', slug='region-2'),
            Region(name='Region 3', slug='region-3'),
        )
        for region in regions:
            region.save()

        sites = (
            Site(name='Site 1', slug='abc-site-1', region=regions[0], status=SiteStatusChoices.STATUS_ACTIVE),
            Site(name='Site 2', slug='def-site-2', region=regions[1], status=SiteStatusChoices.STATUS_ACTIVE),
            Site(name='Site 3', slug='ghi-site-3', region=regions[2], status=SiteStatusChoices.STATUS_PLANNED),
        )
        Site.objects.bulk_create(sites)

        asns[0].sites.add(sites[0])
        asns[1].sites.add(sites[1])
        asns[2].sites.add(sites[2])

        racks = (
            Rack(name='Rack 1', site=sites[0]),
            Rack(name='Rack 2', site=sites[1]),
            Rack(name='Rack 3', site=sites[2]),
        )
        Rack.objects.bulk_create(racks)

        devices = (
            Device(
                name='Device 1',
                device_type=device_types[0],
                role=roles[0],
                platform=platforms[0],
                serial='ABC',
                asset_tag='1001',
                site=sites[0],
                rack=racks[0],
                position=1,
                face=DeviceFaceChoices.FACE_FRONT,
                status=DeviceStatusChoices.STATUS_ACTIVE,
                local_context_data={'foo': 123},
            ),
            Device(
                name='Device 2',
                device_type=device_types[1],
                role=roles[1],
                platform=platforms[1],
                serial='DEF',
                asset_tag='1002',
                site=sites[1],
                rack=racks[1],
                position=2,
                face=DeviceFaceChoices.FACE_FRONT,
                status=DeviceStatusChoices.STATUS_STAGED,
            ),
            Device(
                name='Device 3',
                device_type=device_types[2],
                role=roles[2],
                platform=platforms[2],
                serial='GHI',
                asset_tag='1003',
                site=sites[2],
                rack=racks[2],
                position=3,
                face=DeviceFaceChoices.FACE_REAR,
                status=DeviceStatusChoices.STATUS_FAILED,
            ),
        )
        Device.objects.bulk_create(devices)

        mac_addresses = (
            MACAddress(mac_address='00-00-00-00-00-01'),
            MACAddress(mac_address='aa-00-00-00-00-01'),
            MACAddress(mac_address='00-00-00-00-00-02'),
            MACAddress(mac_address='bb-00-00-00-00-02'),
            MACAddress(mac_address='00-00-00-00-00-03'),
            MACAddress(mac_address='cc-00-00-00-00-03'),
        )
        MACAddress.objects.bulk_create(mac_addresses)

        interfaces = (
            Interface(device=devices[0], name='Interface 1'),
            Interface(device=devices[0], name='Interface 2'),
            Interface(device=devices[1], name='Interface 3'),
            Interface(device=devices[1], name='Interface 4'),
            Interface(device=devices[2], name='Interface 5'),
            Interface(device=devices[2], name='Interface 6', rf_role=WirelessRoleChoices.ROLE_AP),
        )
        Interface.objects.bulk_create(interfaces)

        interfaces[0].mac_addresses.set([mac_addresses[0]])
        interfaces[1].mac_addresses.set([mac_addresses[1]])
        interfaces[2].mac_addresses.set([mac_addresses[2]])
        interfaces[3].mac_addresses.set([mac_addresses[3]])
        interfaces[4].mac_addresses.set([mac_addresses[4]])
        interfaces[5].mac_addresses.set([mac_addresses[5]])

    def test_site_name_negation(self):
        params = {'name__n': ['Site 1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_status_icontains(self):
        params = {'status__ic': [SiteStatusChoices.STATUS_ACTIVE]}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_status_icontains_negation(self):
        params = {'status__nic': [SiteStatusChoices.STATUS_ACTIVE]}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_site_slug_icontains(self):
        params = {'slug__ic': ['-1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_site_slug_icontains_negation(self):
        params = {'slug__nic': ['-1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_slug_startswith(self):
        params = {'slug__isw': ['abc']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_site_slug_startswith_negation(self):
        params = {'slug__nisw': ['abc']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_slug_endswith(self):
        params = {'slug__iew': ['-1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_site_slug_endswith_negation(self):
        params = {'slug__niew': ['-1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_slug_regex(self):
        params = {'slug__regex': ['^def-[a-z]*-2$']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_site_slug_iregex(self):
        params = {'slug__iregex': ['^DEF-[a-z]*-2$']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 1)

    def test_provider_asn_lt(self):
        params = {'asn__lt': [65101]}
        self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)

    def test_provider_asn_lte(self):
        params = {'asn__lte': [65101]}
        self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 2)

    def test_provider_asn_gt(self):
        params = {'asn__lt': [65101]}
        self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 1)

    def test_provider_asn_gte(self):
        params = {'asn__gte': [65101]}
        self.assertEqual(ASNFilterSet(params, ASN.objects.all()).qs.count(), 2)

    def test_site_region_negation(self):
        params = {'region__n': ['region-1']}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_site_region_id_negation(self):
        params = {'region_id__n': [Region.objects.first().pk]}
        self.assertEqual(SiteFilterSet(params, Site.objects.all()).qs.count(), 2)

    def test_device_name_eq(self):
        params = {'name': ['Device 1']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_name_negation(self):
        params = {'name__n': ['Device 1']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_name_startswith(self):
        params = {'name__isw': ['Device']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 3)

    def test_device_name_startswith_negation(self):
        params = {'name__nisw': ['Device 1']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_name_endswith(self):
        params = {'name__iew': [' 1']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_name_endswith_negation(self):
        params = {'name__niew': [' 1']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_name_icontains(self):
        params = {'name__ic': [' 2']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_name_icontains_negation(self):
        params = {'name__nic': [' ']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 0)

    def test_device_mac_address_negation(self):
        params = {'mac_address__n': ['00-00-00-00-00-01', 'aa-00-00-00-00-01']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_mac_address_startswith(self):
        params = {'mac_address__isw': ['aa:']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_mac_address_startswith_negation(self):
        params = {'mac_address__nisw': ['aa:']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_mac_address_endswith(self):
        params = {'mac_address__iew': [':02']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_mac_address_endswith_negation(self):
        params = {'mac_address__niew': [':02']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_mac_address_icontains(self):
        params = {'mac_address__ic': ['aa:', 'bb']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 2)

    def test_device_mac_address_icontains_negation(self):
        params = {'mac_address__nic': ['aa:', 'bb']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_mac_address_regex(self):
        params = {'mac_address__regex': ['^cc.*:03$']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_device_mac_address_iregex(self):
        params = {'mac_address__iregex': ['^CC.*:03$']}
        self.assertEqual(DeviceFilterSet(params, Device.objects.all()).qs.count(), 1)

    def test_interface_rf_role_empty(self):
        params = {'rf_role__empty': 'true'}
        self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 5)
        params = {'rf_role__empty': 'false'}
        self.assertEqual(InterfaceFilterSet(params, Interface.objects.all()).qs.count(), 1)


class SavedFilterApplicationTestCase(TestCase):
    """
    Validate the application of referenced SavedFilters to a FilterSet's data.
    """
    @classmethod
    def setUpTestData(cls):
        sites = (
            Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE),
            Site(name='Site 2', slug='site-2', status=SiteStatusChoices.STATUS_PLANNED),
        )
        Site.objects.bulk_create(sites)

        cls.saved_filter = SavedFilter.objects.create(
            name='Active Sites',
            slug='active-sites',
            parameters={'status': [SiteStatusChoices.STATUS_ACTIVE]},
        )
        cls.saved_filter.object_types.set([ObjectType.objects.get_for_model(Site)])

    def setUp(self):
        self.request = RequestFactory().get('/')
        self.request.user = User.objects.create_user('testuser')

    def test_filter_id_valid(self):
        # A referenced SavedFilter's parameters are applied to the queryset
        data = QueryDict(f'filter_id={self.saved_filter.pk}')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=self.request).qs.count(), 1)

    def test_filter_id_nonexistent(self):
        # A non-existent (but valid integer) filter_id is ignored
        data = QueryDict('filter_id=999999')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=self.request).qs.count(), 2)

    def test_filter_id_non_integer(self):
        # A non-integer filter_id is ignored rather than raising a ValueError (#22568)
        data = QueryDict('filter_id=abc')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=self.request).qs.count(), 2)

    def test_filter_slug(self):
        # A referenced SavedFilter may also be applied by slug
        data = QueryDict('filter=active-sites')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=self.request).qs.count(), 1)

    def test_private_filter_not_applied_for_other_user(self):
        # A private SavedFilter owned by another user must not be applied (#22790)
        owner = User.objects.create_user('owner')
        private_filter = SavedFilter.objects.create(
            name='Private',
            slug='private',
            user=owner,
            shared=False,
            parameters={'status': [SiteStatusChoices.STATUS_ACTIVE]},
        )
        private_filter.object_types.set([ObjectType.objects.get_for_model(Site)])

        request = RequestFactory().get('/')
        request.user = User.objects.create_user('other')
        data = QueryDict('filter=private')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=request).qs.count(), 2)

    def test_private_filter_applied_for_owner(self):
        # The owner of a private SavedFilter can still apply it (#22790)
        owner = User.objects.create_user('owner')
        private_filter = SavedFilter.objects.create(
            name='Private',
            slug='private',
            user=owner,
            shared=False,
            parameters={'status': [SiteStatusChoices.STATUS_ACTIVE]},
        )
        private_filter.object_types.set([ObjectType.objects.get_for_model(Site)])

        request = RequestFactory().get('/')
        request.user = owner
        data = QueryDict('filter=private')
        self.assertEqual(SiteFilterSet(data, Site.objects.all(), request=request).qs.count(), 1)

    def test_shared_filter_applied_without_request(self):
        # Without a request, a shared SavedFilter is still applied (anonymous visibility)
        data = QueryDict('filter=active-sites')
        self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 1)

    def test_private_filter_not_applied_without_request(self):
        # Without a request, a private SavedFilter is not applied (#22790)
        owner = User.objects.create_user('owner')
        private_filter = SavedFilter.objects.create(
            name='Private',
            slug='private',
            user=owner,
            shared=False,
            parameters={'status': [SiteStatusChoices.STATUS_ACTIVE]},
        )
        private_filter.object_types.set([ObjectType.objects.get_for_model(Site)])

        data = QueryDict('filter=private')
        self.assertEqual(SiteFilterSet(data, Site.objects.all()).qs.count(), 2)
