"""Test the duplication for django-polymorphic models"""

from unittest import skipIf

from django.test import TestCase

from ..utils import duplicate
from .duplicable_testapp.models import *  # pylint: disable=wildcard-import


@skipIf(not TEST_POLYMORPHIC, 'Do not test polymorphic adaptation as it is not installed.')
class DuplicablePolymorphicTestCase(TestCase):
    """Test that our app works well with djngo-polymorphic"""

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        cls.obj = PolymorphicChild1.objects.create(name='child1', name1='child1-1')

    def test_duplicate(self):
        """Test the duplication feature with django-polymorphic"""

        new_obj, dup_objects = duplicate(self.obj)

        # Two object should have been duplicated: the child instance, and it's base

        self.assertEqual(len(dup_objects), 2)

        dup_objects = dict(dup_objects)

        self.assertEqual(dup_objects[self.obj].pk, new_obj.pk)
        self.assertNotEqual(new_obj.pk, self.obj.pk)

        self.assertEqual(dup_objects[self.obj.polymorphicbase_ptr].pk,
                         new_obj.polymorphicbase_ptr_id)
        self.assertNotEqual(new_obj.polymorphicbase_ptr_id, self.obj.polymorphicbase_ptr_id)

    def test_non_duplicate_relation(self):
        """Test that the non duplicable relations are passed to the parent."""

        relation = Relation.objects.create(name='relation')
        obj = PolymorphicChild1.objects.create(name='child-tmp', name1='child-tmp1',
                                               relation=relation)

        # relations are duplicable
        new_obj, dup_objects = duplicate(obj)
        self.assertNotEqual(new_obj.relation.pk, obj.relation.pk)
        self.assertIn(obj.relation, dict(dup_objects))

        # relations are not duplicable
        new_obj, dup_objects = duplicate(obj, non_duplicable_relations=['relation'])
        self.assertEqual(new_obj.relation.pk, obj.relation.pk)
        self.assertNotIn(obj.relation, dict(dup_objects))

    def test_compare(self):
        """Test the comparison feature with django-polymorphic"""

        new_obj = self.obj.duplicate()

        # Nothing has hanged yet

        # Comparing direct objects
        changed, __ = self.obj.compare(new_obj)
        self.assertFalse(changed)

        # Comparing bases
        changed, __ = self.obj.polymorphicbase_ptr.compare(new_obj.polymorphicbase_ptr)
        self.assertFalse(changed)

        # Change a field on the base model
        new_obj.name = 'child2'
        new_obj.save()

        # Comparing direct objects
        changed, __ = self.obj.compare(new_obj)
        self.assertTrue(changed)

        # Comparing bases
        changed, __ = self.obj.polymorphicbase_ptr.compare(new_obj.polymorphicbase_ptr)
        self.assertTrue(changed)

        # Change a field on the child model
        new_obj.name = self.obj.name
        new_obj.name1 = 'child1-2'
        new_obj.save()

        # Comparing direct objects
        changed, __ = self.obj.compare(new_obj)
        self.assertTrue(changed)

        # Comparing bases
        changed, __ = self.obj.polymorphicbase_ptr.compare(new_obj.polymorphicbase_ptr)
        self.assertTrue(changed)

    def test_manager(self):
        """Test the ``PolymorphicManagerForVersionable``"""

        obj1 = self.obj.duplicate()
        obj2 = obj1.duplicate()

        # Some methods from the default ``PolymorphicManager``
        self.assertSetEqual(
            set(PolymorphicBase.objects.instance_of(PolymorphicChild1)),
            {self.obj, obj1, obj2}
        )

        # With our own ``duplicable_objects``
        self.assertSetEqual(
            set(PolymorphicBase.duplicable_objects.instance_of(PolymorphicChild1)),
            {self.obj, obj1, obj2}
        )

        # And the ones added by the ``duplicable`` app
        self.assertSetEqual(
            set(PolymorphicChild1.duplicable_objects.having_parent_version(self.obj)),
            {obj1}
        )
        self.assertSetEqual(
            set(PolymorphicChild1.duplicable_objects.having_original_version(self.obj)),
            {obj1, obj2}
        )

        # Now to the same test on a model with a new queryset and manager

        obj = PolymorphicChild2.objects.create(name='child2', name2='child2-1')
        obj1 = obj.duplicate()
        obj2 = obj1.duplicate(name='foo')

        # Some methods from the default ``PolymorphicManager``
        self.assertSetEqual(
            set(PolymorphicBase.objects.instance_of(PolymorphicChild2)),
            {obj, obj1, obj2}
        )

        # With our own ``duplicable_objects``
        self.assertSetEqual(
            set(PolymorphicBase.duplicable_objects.instance_of(PolymorphicChild2)),
            {obj, obj1, obj2}
        )

        # And the ones added by the ``duplicable`` app

        # Some on ``objects`` (redefined in our model)
        self.assertSetEqual(
            set(PolymorphicChild2.duplicable_objects.having_parent_version(obj)),
            {obj1}
        )
        # And some on ``duplicable_objects`` (from the ``PolymorphicForDuplicablle`` model)
        self.assertSetEqual(
            set(PolymorphicChild2.duplicable_objects.having_original_version(obj)),
            {obj1, obj2}
        )

        # Our own queryset/manager method must be on ``objects``

        # Access the new methd on the new queryset
        self.assertSetEqual(
            set(PolymorphicChild2.objects.name_is_foo()),
            {obj2}
        )

        # And access the new method on the new manager
        PolymorphicChild2.objects.do_nothing()
