import logging
from datetime import (
    timedelta,
)
from typing import Dict

import iso8601
import redis
import redis_lock
from iso8601 import ParseError
from timeutil import Clock
from ._RedisClusterLock import RedisClusterLock

logger = logging.getLogger(__name__)


class RedisLocker(object):

    key_template = "schedule.event.{}:lastPublished"

    lock_key_template = "schedule.event.{}:lastPublishedLock"

    def __init__(
        self,
        redis_client: redis.StrictRedis,
        clock: Clock,
    ):
        self.redis_client = redis_client
        self.clock = clock

        self.locks: Dict[str, redis_lock.Lock] = {}

    def must_publish(self, subject: str, await_time: float) -> bool:
        lock = self._get_lock(subject=subject)
        acquired = lock.acquire(blocking=False)
        if not acquired:
            return False

        try:
            key = self._get_key(subject=subject)
            next_publish_value = self.redis_client.get(name=key)
            if next_publish_value is None:
                self._mark_next_publish(
                    key=key,
                    await_time=await_time,
                )
                return True

            try:
                next_publish_str = next_publish_value.decode("utf-8")
                next_published = iso8601.parse_date(next_publish_str)
                now = self.clock.now()
                if next_published > now.replace(tzinfo=iso8601.UTC):
                    return False

                self._mark_next_publish(
                    key=key,
                    await_time=await_time,
                )
                return True
            except (UnicodeDecodeError, ParseError):
                self.redis_client.delete(key)
        finally:
            lock.release()

    def _get_key(self, subject: str) -> str:
        return self.key_template.format(subject)

    def _get_lock(self, subject: str) -> redis_lock.Lock:
        if subject not in self.locks:
            self.locks[subject] = RedisClusterLock(
                redis_client=self.redis_client,
                name=self.lock_key_template.format(subject),
                expire=5,
            )

        return self.locks[subject]

    def _mark_next_publish(
        self,
        key: str,
        await_time: float,
    ) -> None:
        next_published = self.clock.now() + timedelta(seconds=await_time - 5)
        logger.info("Next publish: %s", next_published.isoformat())
        self.redis_client.set(name=key, value=next_published.isoformat())
