from mock import patch

from mtp.apps.common.models import Orderable
from mtp.apps.common.tests.test_cases import MTPTestCase
from mtp.apps.common.utils import bypass_orderable
from mtp.apps.documents.factories import DocumentFactory
from mtp.apps.evaluations.factories import EvaluationFactory
from mtp.apps.roleplays.factories import RoleplayFactory
from mtp.apps.trainings.factories import TrainingFactory
from mtp.apps.workshops.factories import WorkshopFactory
from mtp.discrete_apps.duplicable.utils import duplicate


class OrderableTestCase(MTPTestCase):
    """Test different use cases of ``Orderable``."""

    @classmethod
    def setUpTestData(cls):
        """Create some test data."""

        super().setUpTestData()

        # Create a first training and some children
        cls.training1 = TrainingFactory()
        cls.document1 = DocumentFactory(training=cls.training1)
        cls.roleplay1 = RoleplayFactory(training=cls.training1)
        cls.workshop1 = WorkshopFactory(training=cls.training1)
        cls.evaluation1 = EvaluationFactory(training=cls.training1)

        # Create a second training and some children
        cls.training2 = TrainingFactory()
        cls.document2 = DocumentFactory(training=cls.training2)
        cls.roleplay2 = RoleplayFactory(training=cls.training2)
        cls.workshop2 = WorkshopFactory(training=cls.training2)
        cls.evaluation2 = EvaluationFactory(training=cls.training2)

    def setUp(self):
        """Reload all objects from DB for every tests."""

        super().setUp()
        self.reload_objects()

    def reload_objects(self):
        """Utility to reload all objects.

        Needed because when order is updated, we need to have the correct sort_order, and
        the private attributes added by ``Orderable``.

        """

        self.document1 = self.document1.__class__.objects.get(pk=self.document1.pk)
        self.roleplay1 = self.roleplay1.__class__.objects.get(pk=self.roleplay1.pk)
        self.workshop1 = self.workshop1.__class__.objects.get(pk=self.workshop1.pk)
        self.evaluation1 = self.evaluation1.__class__.objects.get(pk=self.evaluation1.pk)

        self.document2 = self.document2.__class__.objects.get(pk=self.document2.pk)
        self.roleplay2 = self.roleplay2.__class__.objects.get(pk=self.roleplay2.pk)
        self.workshop2 = self.workshop2.__class__.objects.get(pk=self.workshop2.pk)
        self.evaluation2 = self.evaluation2.__class__.objects.get(pk=self.evaluation2.pk)

    def test_default_concrete_children_order_is_correct(self):
        """Test that default order of children of many parents is correct"""

        # Order from 1 to 4 for training1 children
        self.assertEqual(self.document1.sort_order, 1)
        self.assertEqual(self.roleplay1.sort_order, 2)
        self.assertEqual(self.workshop1.sort_order, 3)
        self.assertEqual(self.evaluation1.sort_order, 4)

        # Order from 1 to 4 for training2 children
        self.assertEqual(self.document2.sort_order, 1)
        self.assertEqual(self.roleplay2.sort_order, 2)
        self.assertEqual(self.workshop2.sort_order, 3)
        self.assertEqual(self.evaluation2.sort_order, 4)

    def test_changing_concrete_children_order(self):
        """Test changing the child order of an instance."""

        # Update order of a child of training1
        self.document1.sort_order = 3
        self.document1.save()

        # Reload all from db to have updated orders
        self.reload_objects()

        # Orders of training1 children should have changed
        self.assertEqual(self.document1.sort_order, 3)
        self.assertEqual(self.roleplay1.sort_order, 1)
        self.assertEqual(self.workshop1.sort_order, 2)
        self.assertEqual(self.evaluation1.sort_order, 4)

        # Orders of training2 children should NOT have changed
        self.assertEqual(self.document2.sort_order, 1)
        self.assertEqual(self.roleplay2.sort_order, 2)
        self.assertEqual(self.workshop2.sort_order, 3)
        self.assertEqual(self.evaluation2.sort_order, 4)

    def test_duplicating_should_keep_order(self):
        """Order should be kept on duplicated objects."""

        # Update order of a child of training1
        self.document1.sort_order = 3
        self.document1.save()

        # Reload all from db to have updated orders
        self.reload_objects()

        with patch.object(Orderable, '_save', autospec=True) as patched:
            __, dups = duplicate(self.training1)

        # We should have passed through the ``Orderable`` saving
        self.assertGreater(patched.call_count, 0)

        dups = dict(dups)

        self.assertEqual(dups[self.document1].sort_order, 3)
        self.assertEqual(dups[self.roleplay1].sort_order, 1)
        self.assertEqual(dups[self.workshop1].sort_order, 2)
        self.assertEqual(dups[self.evaluation1].sort_order, 4)

    def test_duplicating_should_keep_order_if_bypassing_orderable(self):
        """Order should be kept on duplicated objects if we bypass the ``Orderable`` saving."""

        # Update order of a child of training1
        self.document1.sort_order = 3
        self.document1.save()

        # Reload all from db to have updated orders
        self.reload_objects()

        with bypass_orderable(Orderable):
            with patch.object(Orderable, 'get_filtered_manager', autospec=True) as patched:
                __, dups = duplicate(self.training1)

        # We should have NOT passed through the ``Orderable`` saving
        self.assertEqual(patched.call_count, 0)

        dups = dict(dups)

        self.assertEqual(dups[self.document1].sort_order, 3)
        self.assertEqual(dups[self.roleplay1].sort_order, 1)
        self.assertEqual(dups[self.workshop1].sort_order, 2)
        self.assertEqual(dups[self.evaluation1].sort_order, 4)
