from datetime import datetime

from django.core.exceptions import FieldError
from django.db.models import CharField, F, Q
from django.db.models.expressions import SimpleCol
from django.db.models.fields.related_lookups import RelatedIsNull
from django.db.models.functions import Lower
from django.db.models.lookups import Exact, GreaterThan, IsNull, LessThan
from django.db.models.sql.query import Query
from django.db.models.sql.where import OR
from django.test import TestCase
from django.test.utils import register_lookup

from .models import Author, Item, ObjectC, Ranking


class TestQuery(TestCase):
    def test_simple_query(self):
        query = Query(Author)
        where = query.build_where(Q(num__gt=2))
        lookup = where.children[0]
        self.assertIsInstance(lookup, GreaterThan)
        self.assertEqual(lookup.rhs, 2)
        self.assertEqual(lookup.lhs.target, Author._meta.get_field('num'))

    def test_complex_query(self):
        query = Query(Author)
        where = query.build_where(Q(num__gt=2) | Q(num__lt=0))
        self.assertEqual(where.connector, OR)

        lookup = where.children[0]
        self.assertIsInstance(lookup, GreaterThan)
        self.assertEqual(lookup.rhs, 2)
        self.assertEqual(lookup.lhs.target, Author._meta.get_field('num'))

        lookup = where.children[1]
        self.assertIsInstance(lookup, LessThan)
        self.assertEqual(lookup.rhs, 0)
        self.assertEqual(lookup.lhs.target, Author._meta.get_field('num'))

    def test_multiple_fields(self):
        query = Query(Item)
        where = query.build_where(Q(modified__gt=F('created')))
        lookup = where.children[0]
        self.assertIsInstance(lookup, GreaterThan)
        self.assertIsInstance(lookup.rhs, SimpleCol)
        self.assertIsInstance(lookup.lhs, SimpleCol)
        self.assertEqual(lookup.rhs.target, Item._meta.get_field('created'))
        self.assertEqual(lookup.lhs.target, Item._meta.get_field('modified'))

    def test_transform(self):
        query = Query(Author)
        with register_lookup(CharField, Lower):
            where = query.build_where(~Q(name__lower='foo'))
        lookup = where.children[0]
        self.assertIsInstance(lookup, Exact)
        self.assertIsInstance(lookup.lhs, Lower)
        self.assertIsInstance(lookup.lhs.lhs, SimpleCol)
        self.assertEqual(lookup.lhs.lhs.target, Author._meta.get_field('name'))

    def test_negated_nullable(self):
        query = Query(Item)
        where = query.build_where(~Q(modified__lt=datetime(2017, 1, 1)))
        self.assertTrue(where.negated)
        lookup = where.children[0]
        self.assertIsInstance(lookup, LessThan)
        self.assertEqual(lookup.lhs.target, Item._meta.get_field('modified'))
        lookup = where.children[1]
        self.assertIsInstance(lookup, IsNull)
        self.assertEqual(lookup.lhs.target, Item._meta.get_field('modified'))

    def test_foreign_key(self):
        query = Query(Item)
        msg = 'Joined field references are not permitted in this query'
        with self.assertRaisesMessage(FieldError, msg):
            query.build_where(Q(creator__num__gt=2))

    def test_foreign_key_f(self):
        query = Query(Ranking)
        with self.assertRaises(FieldError):
            query.build_where(Q(rank__gt=F('author__num')))

    def test_foreign_key_exclusive(self):
        query = Query(ObjectC)
        where = query.build_where(Q(objecta=None) | Q(objectb=None))
        a_isnull = where.children[0]
        self.assertIsInstance(a_isnull, RelatedIsNull)
        self.assertIsInstance(a_isnull.lhs, SimpleCol)
        self.assertEqual(a_isnull.lhs.target, ObjectC._meta.get_field('objecta'))
        b_isnull = where.children[1]
        self.assertIsInstance(b_isnull, RelatedIsNull)
        self.assertIsInstance(b_isnull.lhs, SimpleCol)
        self.assertEqual(b_isnull.lhs.target, ObjectC._meta.get_field('objectb'))

[(19, 'django.db.models.sql.query.Query', 'Query', (['Author'], {}), False, 'from django.db.models.sql.query import Query\n'), (27, 'django.db.models.sql.query.Query', 'Query', (['Author'], {}), False, 'from django.db.models.sql.query import Query\n'), (42, 'django.db.models.sql.query.Query', 'Query', (['Item'], {}), False, 'from django.db.models.sql.query import Query\n'), (52, 'django.db.models.sql.query.Query', 'Query', (['Author'], {}), False, 'from django.db.models.sql.query import Query\n'), (62, 'django.db.models.sql.query.Query', 'Query', (['Item'], {}), False, 'from django.db.models.sql.query import Query\n'), (73, 'django.db.models.sql.query.Query', 'Query', (['Item'], {}), False, 'from django.db.models.sql.query import Query\n'), (79, 'django.db.models.sql.query.Query', 'Query', (['Ranking'], {}), False, 'from django.db.models.sql.query import Query\n'), (84, 'django.db.models.sql.query.Query', 'Query', (['ObjectC'], {}), False, 'from django.db.models.sql.query import Query\n'), (20, 'django.db.models.Q', 'Q', ([], {'num__gt': '(2)'}), False, 'from django.db.models import CharField, F, Q\n'), (53, 'django.test.utils.register_lookup', 'register_lookup', (['CharField', 'Lower'], {}), False, 'from django.test.utils import register_lookup\n'), (28, 'django.db.models.Q', 'Q', ([], {'num__gt': '(2)'}), False, 'from django.db.models import CharField, F, Q\n'), (28, 'django.db.models.Q', 'Q', ([], {'num__lt': '(0)'}), False, 'from django.db.models import CharField, F, Q\n'), (76, 'django.db.models.Q', 'Q', ([], {'creator__num__gt': '(2)'}), False, 'from django.db.models import CharField, F, Q\n'), (85, 'django.db.models.Q', 'Q', ([], {'objecta': 'None'}), False, 'from django.db.models import CharField, F, Q\n'), (85, 'django.db.models.Q', 'Q', ([], {'objectb': 'None'}), False, 'from django.db.models import CharField, F, Q\n'), (43, 'django.db.models.F', 'F', (['"""created"""'], {}), False, 'from django.db.models import CharField, F, Q\n'), (54, 'django.db.models.Q', 'Q', ([], {'name__lower': '"""foo"""'}), False, 'from django.db.models import CharField, F, Q\n'), (63, 'datetime.datetime', 'datetime', (['(2017)', '(1)', '(1)'], {}), False, 'from datetime import datetime\n'), (81, 'django.db.models.F', 'F', (['"""author__num"""'], {}), False, 'from django.db.models import CharField, F, Q\n')]