import pickle
import threading
from threading import Thread
from typing import (
    Dict,
    List,
    Optional,
)

from event_manager_api.shared.token import TK_EVT_EVENT
from messagebus import (
    Event,
    Message,
)
from msgbus import (
    MBContext,
    MBService,
)

from ._Service import Service


class EventManager(object):
    def __init__(self, mb_context: MBContext):
        self.mb_context = mb_context

        self.subscriptions_lock = threading.Lock()
        self.subscriptions: Dict[str, Dict[Service, Service]] = {}

    def subscribe(
        self,
        service: Service,
        subject: str,
    ):
        with self.subscriptions_lock:
            if subject not in self.subscriptions:
                self.subscriptions[subject] = {}

            self.subscriptions[subject][service] = service

    def unsubscribe(
        self,
        service: Service,
        subject: str,
    ):
        with self.subscriptions_lock:
            if subject in self.subscriptions and service in self.subscriptions[subject]:
                del self.subscriptions[subject][service]

            if len(self.subscriptions[subject]) == 0:
                del self.subscriptions[subject]

    def unsubscribe_all(
        self,
        service: Service,
    ):
        subject_to_delete = []
        with self.subscriptions_lock:
            for subject in self.subscriptions:
                if service in self.subscriptions[subject]:
                    del self.subscriptions[subject][service]

                if len(self.subscriptions[subject]) == 0:
                    subject_to_delete.append(subject)

            for subject in subject_to_delete:
                del self.subscriptions[subject]

    def get_subscriptions(self, subject: str) -> List[Service]:
        with self.subscriptions_lock:
            if subject not in self.subscriptions:
                return []
            return list(pickle.loads(pickle.dumps(self.subscriptions[subject])).keys())

    def publish(self, event: Event) -> Optional[Message]:
        subject = event.subject
        if subject not in self.subscriptions:
            return None

        if event.synchronous:
            return self._publish_event_to_subscribers(event, subject)
        else:
            Thread(
                name=f"PublishEventToSubscribers_{subject}",
                target=self._publish_event_to_subscribers,
                args=(event, subject),
                daemon=True,
            ).start()

            return None

    def _publish_event_to_subscribers(self, event, subject):
        event_message = event.convert_to_message()

        services = self.get_subscriptions(subject)
        for service in services:
            if not event.synchronous:
                self.mb_context.MB_SendOneWayMessage(
                    destination=MBService(
                        address=service.address,
                        port=service.port,
                    ),
                    token=event_message.token,
                    format=event_message.data_type,
                    data=event_message.data,
                )
            else:
                reply = self.mb_context.MB_SendMessage(
                    destination=MBService(
                        address=service.address,
                        port=service.port,
                    ),
                    token=event_message.token,
                    format=event_message.data_type,
                    data=event_message.data,
                    timeout=event.timeout,
                )
                if reply != TK_EVT_EVENT:
                    return Message(
                        token=reply.token,
                        data_type=reply.format,
                        data=reply.data,
                    )

        return None
