from .user import user
from .user import user_builder
from .user import user_factory
from .exception import in_collection_exception

class users_collection:
    """
    This is responsible for managing users in the system. It could login new 
    users, or load them by ApiKey.
    """

    def __init__(self):
        """
        This create new collection.
        """

        self.__by_apikey = dict()
        self.__by_nick = dict()

    def get_by_nick(self, nick: str) -> user | None:
        """
        This function return user by nick.

        Parameters:
            nick (str): Nick of the user to get

        Returns:
            (user | None): User with that nick, or None
        """

        if self.exists(nick):
            return self.__by_nick[nick]

        return None

    def exists(self, nick: str) -> bool:
        """
        This function check that user with given nick exists in the 
        collection.

        Parameters:
            nick (str): Nick of the user to check

        Returns:    
            (bool): True when user exists, or False if not
        """

        return nick in self.__by_nick
    
    @property
    def all(self) -> list:
        """
        This return all users in the collection, as list, like in the 
        config file.
        """

        return list(self.__by_apikey.values())
    
    def add(self, target: user) -> object:
        """
        This add new user to the collection. When ApiKey or login already
        exists in the collection, then error had been raised.

        Parameters:
            target (user): New user to add

        Returns:
            (object): This object itself
        """

        if target.apikey in self.__by_apikey:
            error = "User with ApiKey \"" + target.apikey + "\" "
            error = error + "already in collection."

            raise in_collection_exception(error)

        if target.nick in self.__by_nick:
            error = "User with nick \"" + target.nick + "\" "
            error = error + "already in collection."

            raise in_collection_exception(error)

        self.__by_apikey[target.apikey] = target
        self.__by_nick[target.nick] = target

        return self

    def login(self, nick: str, password: str) -> user | None:
        """
        This try to login user by nick and password. When nick or password 
        had been wrong, then None had been returned. When nick and password
        are correct, then return that user.

        Parameters:
            nick (str): Nick of the user
            password (str): Password of the user

        Returns:
            (user): User with that nick and password, if exists
            (None): When user with that nick and password not exists
        """

        if not nick in self.__by_nick:
            return None

        target = self.__by_nick[nick]

        if target.password != password:
            return None

        return target

    def remove(self, target: user) -> object:
        """
        This function remove target user from collection.

        Parameters:
            target (user): Target user to remove from collection

        Returns:
            (object): This object itself
        """

        exists = target.nick in self.__by_nick
        exists = target.apikey in self.__by_apikey

        if not exists:
            raise in_collection_exception("Can not found required user.")

        by_nick = self.__by_nick.copy()
        by_apikey = self.__by_apikey.copy()

        by_nick.pop(target.nick)
        by_apikey.pop(target.apikey)

        self.__by_nick = by_nick
        self.__by_apikey = by_apikey

        return self

    def get(self, apikey: str) -> user | None:
        """
        This try to load user by ApiKey. When user with that ApiKey exists
        then return it. When not, return None.

        Parameters:
            apikey (str): ApiKey of the user to load

        Returns:
            (user): User with that ApiKey
            (None): When user with that ApiKey not exists
        """

        if not apikey in self.__by_apikey:
            return None

        return self.__by_apikey[apikey]
