# Protean
import pytest

from protean.utils.query import Q

# Local/Relative Imports
from .elements import Person


class TestConjunctions:
    """Class that holds tests cases for Conjunctions (AND, OR, NeG)"""

    @pytest.fixture(autouse=True)
    def register_elements(self, test_domain):
        test_domain.register(Person)

    @pytest.fixture
    def create_3_people(self, test_domain):
        test_domain.get_dao(Person).create(
            id=2, first_name="Murdock", age=7, last_name="John"
        )
        test_domain.get_dao(Person).create(
            id=3, first_name="Jean", age=3, last_name="John"
        )
        test_domain.get_dao(Person).create(
            id=4, first_name="Bart", age=6, last_name="Carrie"
        )

    @pytest.fixture
    def create_5_people(self, test_domain):
        test_domain.get_dao(Person).create(
            id=2, first_name="Murdock", age=7, last_name="John"
        )
        test_domain.get_dao(Person).create(
            id=3, first_name="Jean", age=3, last_name="John"
        )
        test_domain.get_dao(Person).create(
            id=4, first_name="Bart", age=6, last_name="Carrie"
        )
        test_domain.get_dao(Person).create(
            id=5, first_name="Leslie", age=6, last_name="Underwood"
        )
        test_domain.get_dao(Person).create(
            id=6, first_name="Dave", age=6, last_name="Carrie"
        )

    def test_that_kwargs_to_filter_are_ANDed_by_default(
        self, test_domain, create_3_people
    ):
        q1 = test_domain.get_dao(Person).query.filter(last_name="John", age=3)
        assert q1.total == 1

    def test_that_kwargs_to_exclude_are_ORed_by_default(
        self, test_domain, create_3_people
    ):
        q1 = test_domain.get_dao(Person).query.exclude(last_name="John", age=3)
        assert q1.total == 1

        q2 = test_domain.get_dao(Person).query.exclude(last_name="Carrie", age=10)
        assert q2.total == 2

    def test_straightforward_AND_of_two_criteria(self, test_domain, create_3_people):
        # Filter by the Owner
        q1 = test_domain.get_dao(Person).query.filter(Q(last_name="John") & Q(age=3))
        assert q1.total == 1

    def test_straightforward_OR_of_two_criteria(self, test_domain, create_3_people):
        q1 = test_domain.get_dao(Person).query.filter(Q(last_name="John") | Q(age=3))
        assert q1.total == 2

    def test_combination_of_AND_followed_by_OR(self, test_domain, create_5_people):
        q1 = test_domain.get_dao(Person).query.filter(
            Q(last_name="John", first_name="Jean") | Q(age=6)
        )
        assert q1.total == 4

        q2 = test_domain.get_dao(Person).query.filter(Q(last_name="John") | Q(age=6))
        assert q2.total == 5

        q3 = test_domain.get_dao(Person).query.filter(
            (Q(last_name="John") & Q(age=7)) | (Q(last_name="Carrie") & Q(age=6))
        )
        assert q3.total == 3

    def test_combination_of_OR_followed_by_AND(self, test_domain, create_5_people):
        q1 = test_domain.get_dao(Person).query.filter(
            (Q(last_name="John") | Q(age=7)) & (Q(last_name="Carrie") | Q(age=6))
        )
        assert q1.total == 0

        q2 = test_domain.get_dao(Person).query.filter(
            (Q(last_name="John") | Q(age__gte=3))
            & (Q(first_name="Jean") | Q(first_name="Murdock"))
        )
        assert q2.total == 2

    def test_NEG_of_criteria(self, test_domain, create_5_people):
        q1 = test_domain.get_dao(Person).query.filter(~Q(last_name="John"))
        assert q1.total == 3

        q2 = test_domain.get_dao(Person).query.filter(~Q(last_name="John") | ~Q(age=7))
        assert q2.total == 4

    def test_empty_resultset_is_returned_correctly(self, test_domain, create_3_people):
        q1 = test_domain.get_dao(Person).query.filter(last_name="XYZ", age=100)
        assert q1.total == 0
