import inspect
from typing import List

from core.decorators import instance
from core.dict_object import DictObject
from core.logger import Logger


# noinspection PyUnusedLocal
@instance()
class AccessService:
    def __init__(self):
        self.access_levels = [
            {"label": "none", "level": 0, "handler": self.no_access},
            {"label": "all", "level": 100, "handler": self.all_access}]
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.character_service = registry.get_instance("character_service")
        self.account_service = registry.get_instance("account_service")

    def register_access_level(self, label, level, handler):
        """
        Call during pre_start

        Args:
            label: str
            level: int
            handler: (char_id: Int) -> bool
        """

        if len(inspect.signature(handler).parameters) != 1:
            raise Exception(
                "Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__))

        self.logger.debug("Registering access level %d with label '%s'" % (level, label))
        self.access_levels.append({"label": label.lower(), "level": level, "handler": handler})
        self.access_levels = sorted(self.access_levels, key=lambda k: k["level"])

    def get_access_levels(self) -> List[dict]:
        return self.access_levels

    def get_access_level(self, char_id) -> dict:
        account = self.account_service.get_main(char_id)
        # For performance's sake, we'll assume that the main has the highest rank;
        # if it only has the rank "all" (no rank), we'll also check the toon using the command.
        if account:
            al = self.get_single_access_level(account.char_id)
            if al["label"] == "all":
                al = self.get_single_access_level(char_id)
            return al
        else:
            return self.get_single_access_level(char_id)

    def compare_access_levels(self, access_level1, access_level2) -> int:
        """
        Returns a positive number if the access_level1 is greater than access_level2,
        a negative number if access_level1 is less than access_level2,
        and 0 if the access levels are equal.

        :param access_level1:
        :param access_level2:
        :return: int
        """
        a1 = self.get_access_level_by_label(access_level1)
        a2 = self.get_access_level_by_label(access_level2)

        return a2["level"] - a1["level"]

    def has_sufficient_access_level(self, char_id1, char_id2) -> bool:
        """
        Returns True if char1 has a higher access level than char2
        or if char1 is a verified alt of char2, and False otherwise.

        :param char_id1:
        :param char_id2:
        :return:
        """

        # return True if char_ids are the same
        if char_id1 == char_id2:
            return True

        # return True if both chars have the same main
        if self.account_service.get_main(char_id1).char_id == (self.account_service.get_main(char_id2) or {}).get(
                "char_id", 0):
            return True

        a1 = self.get_access_level(char_id1)
        a2 = self.get_access_level(char_id2)

        return a2["level"] - a1["level"] > 0

    def get_single_access_level(self, char) -> dict:
        char_id = self.character_service.resolve_char_to_id(char)
        for access_level in self.access_levels:
            if access_level["handler"](char_id):
                return access_level

    def get_access_level_by_level(self, level) -> dict or bool:
        for access_level in self.access_levels:
            if access_level["level"] == level:
                return access_level
        self.logger.warning(f"Access to undefined Accesslevel detected: {level} - answering with rank 'None'")
        return DictObject({"label": "none", "level": 0, "handler": self.no_access})

    def get_access_level_by_label(self, label) -> dict or bool:
        label = label.lower()
        for access_level in self.access_levels:
            if access_level["label"] == label:
                return access_level

        self.logger.warning(f"Access to undefined Accesslevel detected: {label} - answering with rank 'None'")
        return DictObject({"label": "none", "level": 0, "handler": self.no_access})

    def check_access(self, char, access_level_label) -> bool:
        char_id = self.character_service.resolve_char_to_id(char)
        if not char_id:
            return False
        # noinspection LongLine
        return (self.get_access_level(char) or {}).get("level", 100) <= \
               self.get_access_level_by_label(access_level_label)["level"]

    def no_access(self, char_id) -> bool:
        return False

    def all_access(self, char_id) -> bool:
        return True
