import inspect

from core.decorators import instance
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.text import Text


@instance()
class MessageHubService:
    def __init__(self):
        self.logger = Logger(__name__)
        self.hub = {}
        self.sources = []

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.setting_service = registry.get_instance("setting_service")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.text: Text = registry.get_instance("text")
        self.db = registry.get_instance("db")

    def pre_start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS message_hub_subscriptions ( "
                     "destination VARCHAR(50) NOT NULL,"
                     "source VARCHAR(50) NOT NULL"
                     ")")

    def register_message_source(self, source):
        """Call during pre_start"""
        if source not in self.sources:
            self.sources.append(source)

    def register_message_destination(self, destination, callback, default_sources, invalid_sources=None):
        """
        Call during start

        Args:
            destination: str
            callback: (ctx) -> void
            default_sources: [str...]
            invalid_sources: [str...]
        """

        if invalid_sources is None:
            invalid_sources = []
        if len(inspect.signature(callback).parameters) != 1:
            raise Exception(
                f"Incorrect number of arguments for handler '{callback.__module__}.{callback.__name__}()'")

        if destination in self.hub:
            self.logger.error(f"Message hub destination '{destination}' already subscribed")
            return
            # raise Exception(f"Message hub destination '{destination}' already subscribed")

        for source in default_sources:
            if source not in self.sources:
                self.logger.warning(
                    f"Could not subscribe destination '{destination}' to source '{source}' because source does not exist")

        self.hub[destination] = (DictObject({"name": destination,
                                             "callback": callback,
                                             "sources": default_sources,
                                             "invalid_sources": invalid_sources}))

        self.reload_mapping(destination)

    def reload_mapping(self, destination):
        data = self.db.query("SELECT source FROM message_hub_subscriptions WHERE destination = ?", [destination])
        if data:
            self.hub[destination].sources = list(map(lambda x: x.source, data))

    def send_message(self, source, sender, message, formatted_message):
        ctx = DictObject({"source": source,
                          "sender": sender,
                          "message": message,
                          "formatted_message": formatted_message})

        for _, c in self.hub.items():
            if source in c.sources:
                try:
                    c.callback(ctx)
                except Exception as e:
                    self.logger.error("", e)

    def subscribe_to_source(self, destination, source):
        if source not in self.sources:
            raise Exception(f"Message hub source '{source}' does not exist")

        obj = self.hub.get(destination, None)
        if not obj:
            raise Exception(f"Message hub destination '{destination}' does not exist")

        if source not in obj.sources:
            self.db.exec("DELETE FROM message_hub_subscriptions WHERE destination = ?", [destination])

            obj.sources.append(source)
            for source in obj.sources:
                self.db.exec("INSERT INTO message_hub_subscriptions (destination, source)"
                             "VALUES (?, ?)", [destination, source])

    def unsubscribe_from_source(self, destination, source):
        # if source not in self.sources:
        #    raise Exception("Message hub source '%s' does not exist" % source)

        obj = self.hub.get(destination, None)
        if not obj:
            raise Exception(f"Message hub destination '{destination}' does not exist")

        if source in obj.sources:
            self.db.exec("DELETE FROM message_hub_subscriptions WHERE destination = ?", [destination])

            obj.sources.remove(source)
            for source in obj.sources:
                self.db.exec("INSERT INTO message_hub_subscriptions (destination, source)"
                             "VALUES (?, ?)", [destination, source])
