"""Test the redis models of the ``trainings`` app"""

from mock import patch
from time import sleep

from ..constants import LockableFields
from ..models import Training
from ..redis_models import Training as RedisTraining

from .test_cases import TrainingWithActivitiesTestCase


# Set the lock timetout to 1 second for faster tests
@patch.object(RedisTraining, 'DUPLICATE_LOCK_TIMEOUT', 1)
class RedisTrainingModelTestCase(TrainingWithActivitiesTestCase):
    """Test the implementation of the ``Training`` limpyd model.

    Attributes
    ----------
    instance : ..redis_model.Training
        The instance of the ``Training`` limpyd model tied to ``self.training``.
    params : dict
        Dict used to simulate what could be passed from the outside to get a duplicate lock.
    key : string
        Expected key computed from the ``params`` attribute.

    """

    def setUp(self):
        """Save the redis instance of the training in a variable."""

        super().setUp()

        # Save the redis instance tied to ``self.training``
        self.instance = self.training.redis_instance

        self.dup_params = {
            'owner': self.trainer,
            'new_state': self.training.VERSION_STATES.PUBLISHED,
            'business_unit': self.business_unit
        }
        self.draft_params = {
            'owner': self.trainer,
        }

        # Keys are ordered, and we take the key plus the hash of the value for each part
        dup_key_parts = [
            'business_unit:%s' % hash(self.business_unit.pk),
            'new_state:%s' % hash('p'),
            'owner:%s' % hash(self.trainer.pk),
        ]
        draft_key_parts = [
            'owner:%s' % hash(self.trainer.pk)
        ]

        self.dup_key = ','.join(dup_key_parts)
        self.draft_key = ','.join(draft_key_parts)

    def test_get_lock_key_from_params(self):
        """Test that the duplicate lock key is correctly calculated."""

        key = RedisTraining.get_lock_key_from_params(**self.dup_params)
        self.assertEqual(key, self.dup_key)

    def test_get_action_lock_field(self):
        """Test that we can easily retrieve the correct field from the ``DynamicStringField``."""

        field = self.instance.get_action_lock_field(LockableFields.DUPLICATE_TO_MEMBER,
                                                    **self.dup_params)
        self.assertEqual(field, self.instance.duplicate_to_member_locks.get_for(self.dup_key))

        field = self.instance.get_action_lock_field(LockableFields.CREATE_DRAFT,
                                                    **self.draft_params)
        self.assertEqual(field, self.instance.create_draft_locks.get_for(self.draft_key))

    def test_is_duplicate_to_member_locked(self):
        """Test that the duplicate lock is assumed as set if the key exists."""

        # By default, it's not locked
        self.assertFalse(self.instance.is_duplicate_to_member_locked(**self.dup_params))

        # Set a value
        self.instance.get_action_lock_field(LockableFields.DUPLICATE_TO_MEMBER,
                                            **self.dup_params).set(1)

        # Now it is considered locked
        self.assertTrue(self.instance.is_duplicate_to_member_locked(**self.dup_params))

    def test_lock_duplicate_to_member(self):
        """Test the lock of a duplicate."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_duplicate_to_member(**self.dup_params)

        # The lock should have been set
        self.assertTrue(lock_set)

        # Using the cached lock should show the lock as set
        self.assertTrue(self.instance.is_duplicate_to_member_locked(**self.dup_params))

        # The key must exists
        self.assertTrue(self.instance.duplicate_to_member_locks.get_for(self.dup_key))

        # Trying from another instance of the same training (think "in another process")
        training = Training.objects.get(pk=self.training.pk)

        # The lock could not be set
        lock_set = training.redis_instance.lock_duplicate_to_member(**self.dup_params)
        self.assertFalse(lock_set)

    def test_lock_duplicate_to_member_auto_expire(self):
        """Test that the auto-expire of a duplicate lock works."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_duplicate_to_member(**self.dup_params)

        # The lock should have been set
        self.assertTrue(lock_set)
        self.assertTrue(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_duplicate_to_member_locked(**self.dup_params)
        )

        # Waiting 1 second (the default timeout for the tests)
        sleep(1)

        # The lock should be removed
        self.assertFalse(self.instance.is_duplicate_to_member_locked(**self.dup_params))
        self.assertFalse(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_duplicate_to_member_locked(**self.dup_params)
        )

    def test_release_duplicate_to_member_lock(self):
        """Test that we can release a duplicate lock."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_duplicate_to_member(**self.dup_params)

        # The lock should have been set
        self.assertTrue(lock_set)
        self.assertTrue(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_duplicate_to_member_locked(**self.dup_params)
        )

        # Release the lock
        self.instance.release_duplicate_to_member_lock(**self.dup_params)

        # The lock should be removed
        self.assertFalse(self.instance.is_duplicate_to_member_locked(**self.dup_params))
        self.assertFalse(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_duplicate_to_member_locked(**self.dup_params)
        )

    def test_is_create_draft_locked(self):
        """Test that the draft creation lock is assumed as set if the key exists."""

        # By default, it's not locked
        self.assertFalse(self.instance.is_create_draft_locked(**self.draft_params))

        # Set a value
        self.instance.get_action_lock_field(LockableFields.CREATE_DRAFT,
                                            **self.draft_params).set(1)

        # Now it is considered locked
        self.assertTrue(self.instance.is_create_draft_locked(**self.draft_params))

    def test_lock_create_draft(self):
        """Test the lock of a draft creation."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_create_draft(**self.draft_params)

        # The lock should have been set
        self.assertTrue(lock_set)

        # Using the cached lock should show the lock as set
        self.assertTrue(self.instance.is_create_draft_locked(**self.draft_params))

        # The key must exists
        self.assertTrue(self.instance.create_draft_locks.get_for(self.draft_key))

        # Trying from another instance of the same training (think "in another process")
        training = Training.objects.get(pk=self.training.pk)

        # The lock could not be set
        lock_set = training.redis_instance.lock_create_draft(**self.draft_params)
        self.assertFalse(lock_set)

    def test_lock_create_draft_auto_expire(self):
        """Test that the auto-expire of a draft creation lock works."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_create_draft(**self.draft_params)

        # The lock should have been set
        self.assertTrue(lock_set)
        self.assertTrue(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_create_draft_locked(**self.draft_params)
        )

        # Waiting 1 second (the default timeout for the tests)
        sleep(1)

        # The lock should be removed
        self.assertFalse(self.instance.is_create_draft_locked(**self.draft_params))
        self.assertFalse(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_create_draft_locked(**self.draft_params)
        )

    def test_release_create_draft_lock(self):
        """Test that we can release a draft creation lock."""

        # Lock the duplicate for the test params
        lock_set = self.instance.lock_create_draft(**self.draft_params)

        # The lock should have been set
        self.assertTrue(lock_set)
        self.assertTrue(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_create_draft_locked(**self.draft_params)
        )

        # Release the lock
        self.instance.release_create_draft_lock(**self.draft_params)

        # The lock should be removed
        self.assertFalse(self.instance.is_create_draft_locked(**self.draft_params))
        self.assertFalse(
            Training.objects.get(
                pk=self.training.pk
            ).redis_instance.is_create_draft_locked(**self.draft_params)
        )
