from core.aochat import client_packets
from core.aochat import server_packets
from core.conn import Conn
from core.decorators import instance
from core.logger import Logger
from core.lookup.character_service import CharacterService


@instance()
class BuddyService:
    BUDDY_LOGON_EVENT = "buddy_logon"
    BUDDY_LOGOFF_EVENT = "buddy_logoff"

    def __init__(self):
        self.buddy_list = {}
        self.buddy_list_size = 0
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.bot = registry.get_instance("bot")
        self.event_service = registry.get_instance("event_service")

    def pre_start(self):
        self.bot.register_packet_handler(server_packets.BuddyAdded.id, self.handle_add)
        self.bot.register_packet_handler(server_packets.BuddyRemoved.id, self.handle_remove)
        self.bot.register_packet_handler(server_packets.LoginOK.id, self.handle_login_ok)
        self.event_service.register_event_type(self.BUDDY_LOGON_EVENT)
        self.event_service.register_event_type(self.BUDDY_LOGOFF_EVENT)

    def handle_add(self, conn: Conn, packet):
        buddy = self.buddy_list[conn.id].get(packet.char_id, {"types": [], "conn_id": conn.id})
        buddy["online"] = packet.online
        self.buddy_list[conn.id][packet.char_id] = buddy

        # verify that buddy does not exist on any other conn
        for conn_id, conn_buddy_list in self.buddy_list.items():
            if conn.id != conn_id:
                if buddy := conn_buddy_list.get(packet.char_id, None):
                    if buddy["online"] is None:
                        # remove from other conn list
                        del conn_buddy_list[packet.char_id]
                    else:
                        # remove from this conn
                        self.logger.warning(f"Removing char '{packet.char_id}' from conn '{conn.id}' "
                                            f"since it already exists on another conn")
                        conn.send_packet(client_packets.BuddyRemove(packet.char_id))

        if packet.online == 1:
            self.event_service.fire_event(self.BUDDY_LOGON_EVENT, packet)
        else:
            self.event_service.fire_event(self.BUDDY_LOGOFF_EVENT, packet)

    def handle_remove(self, conn: Conn, packet):
        conn_buddy_list = self.buddy_list[conn.id]
        if packet.char_id in conn_buddy_list:
            if len(conn_buddy_list[packet.char_id]["types"]) > 0:
                self.logger.warning(f"Removing buddy {packet.char_id} that still has "
                                    f"types {conn_buddy_list[packet.char_id]['types']}")

            del conn_buddy_list[packet.char_id]

    def handle_login_ok(self, conn: Conn, _):
        self.buddy_list_size += 1000
        self.buddy_list[conn.id] = {}
        self.buddy_list[conn.id][conn.char_id] = {"online": True, "types": [], "conn_id": conn.id}

    def add_buddy(self, char_id, _type):
        if not char_id:
            return False

        # check if we are trying to add a conn as a buddy
        if self.is_conn_char_id(char_id):
            return False

        buddy = self.get_buddy(char_id)
        if buddy:
            buddy["types"].append(_type)
        else:
            conn = self.get_conn_for_new_buddy()
            if conn.char_id != char_id:
                conn.send_packet(client_packets.BuddyAdd(char_id, "\1"))
                self.buddy_list[conn.id][char_id] = {"online": None, "types": [_type], "conn_id": conn.id}

        return True

    def is_conn_char_id(self, char_id):
        for _id, conn in self.bot.conns.items():
            if conn.char_id == char_id:
                return True

        return False

    def remove_buddy(self, char_id, _type, force_remove=False):
        if char_id:
            buddy = self.get_buddy(char_id)
            if not buddy:
                return False

            if _type in buddy["types"]:
                buddy["types"].remove(_type)

            if len(buddy["types"]) == 0 or force_remove:
                if not self.is_conn_char_id(char_id):
                    conn = self.bot.conns[buddy["conn_id"]]
                    conn.send_packet(client_packets.BuddyRemove(char_id))

            return True
        else:
            return False

    def get_buddy(self, char_id):
        for conn_id, conn_buddy_list in self.buddy_list.items():
            if char_id in conn_buddy_list:
                return conn_buddy_list[char_id]
        return None

    def is_online(self, char_id):
        buddy = self.get_buddy(char_id)
        if buddy is None:
            return None
        else:
            return buddy.get("online", None)

    def get_all_buddies(self):
        result = {}
        for conn_id, conn_buddy_list in self.buddy_list.items():
            for char_id, buddy in conn_buddy_list.items():
                result[char_id] = buddy

        return result

    def get_buddy_list_size(self):
        count = 0
        for conn_id, conn_buddy_list in self.buddy_list.items():
            count += len(conn_buddy_list)

        return count

    def get_conn_for_new_buddy(self):
        buddy_list_size = None
        _id = None
        for conn_id, conn_buddy_list in self.buddy_list.items():
            if buddy_list_size is None or len(conn_buddy_list) < buddy_list_size:
                buddy_list_size = len(conn_buddy_list)
                _id = conn_id

        return self.bot.conns.get(_id, None)
