from logging import getLogger
from typing import Dict, List, Optional

from messagebus import Event, SubscriptionInfo
from messageprocessor import MessageProcessorCallback

from rediseventlock._TenantRedisClient import TenantRedisClient
from rediseventlock.exceptions import LockedEventException


class EventLockCallback(MessageProcessorCallback):
    def __init__(
        self,
        redis_client: TenantRedisClient,
        subscriptions: List[SubscriptionInfo],
        lock_timeout: int,
    ):
        self.redis_client = redis_client
        self.subscriptions: Dict[str, SubscriptionInfo] = {
            s.subject: s for s in (subscriptions or [])
        }
        self.lock_timeout = lock_timeout

        self.logger = getLogger()
        self._lock = None
        self._subject = None

    def _get_event_subscription(self, event_name: str) -> Optional[SubscriptionInfo]:
        return self.subscriptions.get(event_name)

    @staticmethod
    def _build_cache_key(sub_key: str):
        return ":".join(["EventLock", sub_key])

    def _need_lock(self, event_subject: str) -> bool:
        subscription_info = self._get_event_subscription(event_subject)
        return (
            subscription_info is not None
            and subscription_info.subscription_type == SubscriptionInfo.TYPE_NON_REENTRANT
        )

    def process_begun(self, event_name, request_id, **kwargs):
        event = kwargs.get("event")
        self._subject = None
        if event is not None and isinstance(event, Event):
            self._subject = event.subject
        if self._need_lock(self._subject):
            self.logger.debug(f"Checking {self._subject} locked")
            self._lock = self.redis_client.lock(
                name=self._build_cache_key(f"{self._subject}-Execution"),
                timeout=self.lock_timeout,
                blocking_timeout=self.lock_timeout,
            )
            if self._lock.acquire(blocking=False):
                self.logger.debug(f"Acquired lock for event {self._subject}")
                return
            self.logger.debug(f"Locked event {self._subject}")
            raise LockedEventException()

    def input_parsed(self, event_name, request_id, input_obj):
        pass

    def _release_lock(self):
        if self._lock is not None:
            try:
                self._lock.release()
                self.logger.debug(f"Released lock for event {self._subject}")
            except Exception as ex:
                self.logger.warning(
                    f"Error while releasing lock for event {self._subject}",
                    exc_info=ex,
                )
            finally:
                self._lock = None

    def business_called(self, event_name, request_id, input_obj, output_obj):
        self._release_lock()

    def process_finished(self, event_name, request_id, input_obj, output_obj):
        self._release_lock()

    def parse_exception(self, event_name, request_id, data, exception):
        self._release_lock()

    def unhandled_exception(self, event_name, request_id, input_obj, exception):
        self._release_lock()
