from unittest import TestCase
from datetime import date, datetime, timedelta
from uuid import uuid4
from bson import ObjectId

from rest_framework import fields
from drf_mongo_filters import filters, Filterset, ModelFilterset

from .models import SimpleDoc, DeepDoc, EmbDoc

class QuerysetTesting():
    def assertQuerysetDocs(self, qs, docs):
        ids = lambda o: o.id
        items = map(ids, qs)
        values = map(ids, docs)
        self.assertEqual(set(items), set(values))

class BasicTests(QuerysetTesting, TestCase):
    def tearDown(self):
        SimpleDoc.objects.delete()

    def test_bool(self):
        objects = [
            SimpleDoc.objects.create(f_bool=True),
            SimpleDoc.objects.create(f_bool=False),
            SimpleDoc.objects.create()
        ]

        class FS(Filterset):
            foo = filters.BooleanFilter(source='f_bool')

        fs = FS({'foo': True})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:1])

        fs = FS({'foo': False})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

    def test_exists(self):
        objects = [
            SimpleDoc.objects.create(),
            SimpleDoc.objects.create(f_int=1),
            SimpleDoc.objects.create(f_int=2),
        ]

        class FS(Filterset):
            foo = filters.ExistsFilter(source='f_int')

        fs = FS({'foo': True})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])

        fs = FS({'foo': False})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:1])

    def test_str(self):
        objects = [
            SimpleDoc.objects.create(f_str="foofoo"),
            SimpleDoc.objects.create(f_str="foobar"),
            SimpleDoc.objects.create(f_str="barbaz")
        ]

        class FS(Filterset):
            foo = filters.CharFilter('contains', source='f_str')

        fs = FS({'foo': "foo"})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:2])

        fs = FS({'foo': "bar"})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])


    def test_uuid(self):
        objects = [
            SimpleDoc.objects.create(f_uuid=uuid4()),
            SimpleDoc.objects.create(f_uuid=uuid4()),
            SimpleDoc.objects.create(f_uuid=uuid4()),
        ]

        class FS(Filterset):
            foo = filters.UUIDFilter(source='f_uuid')

        fs = FS({'foo': str(objects[1].f_uuid)})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

    def test_int(self):
        objects = [
            SimpleDoc.objects.create(f_int=10),
            SimpleDoc.objects.create(f_int=20),
            SimpleDoc.objects.create(f_int=30),
        ]

        class FS(Filterset):
            foo = filters.IntegerFilter('gte', source='f_int')
            bar = filters.IntegerFilter('lte', source='f_int')

        fs = FS({'foo': 20})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])

        fs = FS({'bar': 20})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:2])

        fs = FS({'foo':20, 'bar': 20})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

    def test_flt(self):
        objects = [
            SimpleDoc.objects.create(f_flt=0.10),
            SimpleDoc.objects.create(f_flt=0.20),
            SimpleDoc.objects.create(f_flt=0.30),
        ]

        class FS(Filterset):
            foo = filters.FloatFilter('gte', source='f_flt')
            bar = filters.FloatFilter('lte', source='f_flt')

        fs = FS({'foo': 0.2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])

        fs = FS({'bar': 0.2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:2])

        fs = FS({'foo': 0.2, 'bar': 0.2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

    def test_datetime(self):
        d0 = datetime.utcnow()
        dl = timedelta(milliseconds=1)
        d1 = d0 + dl
        d2 = d1 + dl
        d3 = d2 + dl

        objects = [
            SimpleDoc.objects.create(f_dt=d1),
            SimpleDoc.objects.create(f_dt=d2),
            SimpleDoc.objects.create(f_dt=d3),
        ]

        class FS(Filterset):
            dt = filters.DateTimeFilter(source='f_dt')
            dt_gt = filters.DateTimeFilter('gte', source='f_dt')
            dt_gte = filters.DateTimeFilter('gte', source='f_dt')

        fs = FS({'dt': d2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

        fs = FS({'dt_gt': d2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])

        fs = FS({'dt_gte': d2})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])

    def test_oid(self):
        oid = ObjectId()
        objects = [
            SimpleDoc.objects.create(f_oid=ObjectId()),
            SimpleDoc.objects.create(f_oid=oid),
            SimpleDoc.objects.create(f_oid=ObjectId()),
        ]

        class FS(Filterset):
            foo = filters.CharFilter(source='f_oid')

        fs = FS({'foo': oid})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

    def test_ref(self):
        oid = ObjectId()
        objects = [
            SimpleDoc.objects.create(f_ref=ObjectId()),
            SimpleDoc.objects.create(f_ref=oid),
            SimpleDoc.objects.create(f_ref=ObjectId()),
        ]

        class FS(Filterset):
            foo = filters.CharFilter(source='f_ref')

        fs = FS({'foo': oid})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:2])

class CompoundTests(QuerysetTesting, TestCase):
    def tearDown(self):
        SimpleDoc.objects.delete()
        DeepDoc.objects.delete()

    def test_list(self):
        objects = [
            SimpleDoc.objects.create(f_str="foo"),
            SimpleDoc.objects.create(f_str="bar"),
            SimpleDoc.objects.create(f_str="baz")
        ]

        class FS(Filterset):
            foo = filters.AnyFilter(source='f_str')
            bar = filters.NoneFilter(source='f_str')

        fs = FS({'foo': ["bar","baz"]})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:3])

        fs = FS({'bar': ["bar","baz"]})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[0:1])

    def test_None(self):
        objects = [
            DeepDoc.objects.create(f_list=[1,2]),
            DeepDoc.objects.create(f_list=[2,3]),
            DeepDoc.objects.create(f_list=[3,4]),
        ]

        class FS(Filterset):
            foo = filters.NoneFilter(source='f_list')

        fs = FS({'foo': [1,2]})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[2:3])

    def test_range(self):
        objects = [
            SimpleDoc.objects.create(f_int=3),
            SimpleDoc.objects.create(f_int=5),
            SimpleDoc.objects.create(f_int=7),
            SimpleDoc.objects.create(f_int=11),
            SimpleDoc.objects.create(f_int=13),
        ]

        class FS(Filterset):
            foo = filters.RangeFilter(child=fields.IntegerField(), source='f_int')
            bar = filters.RangeFilter(child=fields.IntegerField(), lookup=('gt','lt'), source='f_int')

        fs = FS({'foo': {'min':5, 'max':11}})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:4])

        fs = FS({'bar': {'min':5, 'max':11}})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[2:3])

    def test_range_intersect(self):
        objects = [
            SimpleDoc.objects.create(f_rng_beg=1, f_rng_end=3),
            SimpleDoc.objects.create(f_rng_beg=2, f_rng_end=4), # 4-6
            SimpleDoc.objects.create(f_rng_beg=3, f_rng_end=5), # 4-6
            SimpleDoc.objects.create(f_rng_beg=4, f_rng_end=6), # 4-6
            SimpleDoc.objects.create(f_rng_beg=5, f_rng_end=7), # 4-6
            SimpleDoc.objects.create(f_rng_beg=6, f_rng_end=8), # 4-6
            SimpleDoc.objects.create(f_rng_beg=7, f_rng_end=9)
        ]

        class FS(Filterset):
            foo = filters.IntersectRangeFilter(('f_rng_beg','f_rng_end'), child=fields.IntegerField())

        fs = FS({'foo': {'min':4, 'max':6}})
        qs = fs.filter_queryset(SimpleDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:-1])


class DeepFieldsTests(QuerysetTesting, TestCase):
    def tearDown(self):
        DeepDoc.objects.delete()

    def test_list(self):
        objects = [
            DeepDoc.objects.create(f_list=[10,11]),
            DeepDoc.objects.create(f_list=[20,21]),
            DeepDoc.objects.create(f_list=[30,31]),
        ]
        class FS(Filterset):
            foo = filters.IntegerFilter('gte', source='f_list')
        fs = FS({'foo': "20"})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])

    def test_dict(self):
        objects = [
            DeepDoc.objects.create(f_dict={'foo':"foo1", 'bar':"bar1"}),
            DeepDoc.objects.create(f_dict={'foo':"foo2", 'bar':"bar2"}),
            DeepDoc.objects.create(f_dict={'foo':"foo3", 'bar':"bar3"})
        ]
        class FS(Filterset):
            foo = filters.CharFilter('gte', source='f_dict.foo')
        fs = FS({'foo': "foo2"})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])

    def test_map(self):
        objects = [
            DeepDoc.objects.create(f_map={'foo':1, 'bar':1}),
            DeepDoc.objects.create(f_map={'foo':2, 'bar':2}),
            DeepDoc.objects.create(f_map={'foo':3, 'bar':3})
        ]
        class FS(Filterset):
            foo = filters.IntegerFilter('gte', source='f_map.foo')
        fs = FS({'foo': "2"})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])

    def test_emb(self):
        objects = [
            DeepDoc.objects.create(f_emb=EmbDoc(foo="foo1", bar="bar1")),
            DeepDoc.objects.create(f_emb=EmbDoc(foo="foo2", bar="bar2")),
            DeepDoc.objects.create(f_emb=EmbDoc(foo="foo3", bar="bar3")),
        ]
        class FS(Filterset):
            foo = filters.CharFilter('gte', source='f_emb.foo')
        fs = FS({'foo': "foo2"})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])

    def test_emblist(self):
        objects = [
            DeepDoc.objects.create(f_emblist=[EmbDoc(foo="foo10", bar="bar10"),EmbDoc(foo="foo11", bar="bar11")]),
            DeepDoc.objects.create(f_emblist=[EmbDoc(foo="foo20", bar="bar20"),EmbDoc(foo="foo21", bar="bar21")]),
            DeepDoc.objects.create(f_emblist=[EmbDoc(foo="foo30", bar="bar30"),EmbDoc(foo="foo31", bar="bar31")]),
        ]
        class FS(Filterset):
            foo = filters.CharFilter('gte', source='f_emblist.foo')
        fs = FS({'foo': "foo20"})
        qs = fs.filter_queryset(DeepDoc.objects.all())
        self.assertQuerysetDocs(qs, objects[1:])