"""Tests the redis lock with fakeredis

Adapted from https://github.com/andymccurdy/redis-py/blob/2.9.1/tests/test_lock.py
(just using unittest instead of pytest)
"""

import time
from unittest import TestCase
from django.conf import settings

from redis.client import Lock, LockError

from mtp.apps.common.tests.fake_redis import FakeRedis, FakeStrictRedis


class TestFakeRedisLock(TestCase):
    """Test locking with the ``FakeRedis`` class"""

    redis_class = FakeRedis

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.redis = cls.redis_class(
            host=settings.CACHE_REDIS_HOST,
            port=settings.CACHE_REDIS_PORT,
            db=settings.CACHE_REDIS_DB,
        )
        cls.redis.flushall()

    def test_lock(self):
        lock = self.redis.lock('foo')
        self.assertTrue(lock.acquire())
        self.assertEqual(self.redis['foo'], str(Lock.LOCK_FOREVER).encode())
        lock.release()
        self.assertIsNone(self.redis.get('foo'))

    def test_competing_locks(self):
        lock1 = self.redis.lock('foo')
        lock2 = self.redis.lock('foo')
        self.assertTrue(lock1.acquire())
        self.assertFalse(lock2.acquire(blocking=False))
        lock1.release()
        self.assertTrue(lock2.acquire())
        self.assertFalse(lock1.acquire(blocking=False))
        lock2.release()

    def test_timeouts(self):
        lock1 = self.redis.lock('foo', timeout=1)
        lock2 = self.redis.lock('foo')
        self.assertTrue(lock1.acquire())
        now = time.time()
        self.assertTrue(now < lock1.acquired_until < now + 1)
        self.assertEqual(lock1.acquired_until, float(self.redis['foo']))
        self.assertFalse(lock2.acquire(blocking=False))
        time.sleep(2)  # need to wait up to 2 seconds for lock to timeout
        self.assertTrue(lock2.acquire(blocking=False))
        lock2.release()

    def test_non_blocking(self):
        lock1 = self.redis.lock('foo')
        self.assertTrue(lock1.acquire(blocking=False))
        self.assertTrue(lock1.acquired_until)
        lock1.release()
        self.assertIsNone(lock1.acquired_until)

    def test_context_manager(self):
        with self.redis.lock('foo'):
            self.assertEqual(self.redis['foo'], str(Lock.LOCK_FOREVER).encode())
        self.assertIsNone(self.redis.get('foo'))

    def test_float_timeout(self):
        lock1 = self.redis.lock('foo', timeout=1.5)
        lock2 = self.redis.lock('foo', timeout=1.5)
        self.assertTrue(lock1.acquire())
        self.assertFalse(lock2.acquire(blocking=False))
        lock1.release()

    def test_high_sleep_raises_error(self):
        "If sleep is higher than timeout, it should raise an error"
        with self.assertRaises(LockError):
            self.redis.lock('foo', timeout=1, sleep=2)


class TestFakeStrictRedisLock(TestFakeRedisLock):
    """Test locking with the ``FakeStrictRedis`` class"""

    redis_class = FakeStrictRedis
