import requests

from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Int, Any, Options
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.registry import Registry


@instance()
class OrgListController:
    ORGLIST_BUDDY_TYPE = "orglist"
    single_org_uri = "https://people.anarchy-online.com/org/stats/d/5/name/%d/basicstats.xml?data_type=json"

    def __init__(self):
        self.orglist = None
        self.governing_types = DictObject({
            "Anarchism": ["Anarchist"],
            "Monarchy": ["Monarch", "Counsil", "Follower"],
            "Feudalism": ["Lord", "Knight", "Vassal", "Peasant"],
            "Republic": ["President", "Advisor", "Veteran", "Member", "Applicant"],
            "Faction": ["Director", "Board Member", "Executive", "Member", "Applicant"],
            "Department": ["President", "General", "Squad Commander", "Unit Commander",
                           "Unit Leader", "Unit Member", "Applicant"]
        })

    def inject(self, registry):
        self.bot = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("db")
        self.util = registry.get_instance("util")
        self.text = registry.get_instance("text")
        self.pork_service = registry.get_instance("pork_service")
        self.org_pork_service = registry.get_instance("org_pork_service")
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.character_service = registry.get_instance("character_service")

    @command(command="orglist", params=[Int("org_id")], access_level="member",
             description="Show online status of characters in an org")
    def orglist_cmd(self, request, org_id):
        self.start_orglist_lookup(request.reply, org_id)

    @command(command="orglist", params=[Any("character|org_name|org_id")], access_level="member",
             description="Show online status of characters in an org")
    def orglist_character_cmd(self, request, search):
        if search.isdigit():
            org_id = int(search)
        else:
            orgs = self.pork_service.find_orgs(search)
            num_orgs = len(orgs)
            if num_orgs == 0:
                char_info = self.pork_service.get_character_info(search)
                if char_info:
                    if not char_info.org_id:
                        return "<highlight>%s</highlight> does not appear to belong to an org." % search.capitalize()
                    else:
                        org_id = char_info.org_id
                else:
                    return "Could not find character or org <highlight>%s</highlight>." % search
            elif num_orgs == 1:
                org_id = orgs[0].org_id
            else:
                blob = ""
                for org in orgs:
                    blob += self.text.make_tellcmd(f"{org.org_name} ({org.org_id:d})",
                                                   f"orglist {org.org_id:d}") + "\n"
                return ChatBlob(f"Org List ({num_orgs:d})", blob)

        self.start_orglist_lookup(request.reply, org_id)

    @command(command="orgs",
             params=[Options(["info", "detail", "search"]), Any("Organisation")],
             access_level="member",
             description="View all information about org",
             sub_command="info")
    def display_org_any(self, _, _1, org):
        return self.display_org(org)

    def display_org(self, org):
        orgs = self.find_org(org)
        if len(orgs) == 1:
            info = requests.get(self.single_org_uri % orgs[0].org_id).json()

            blob = "Name: <notice>{NAME}</notice><br>"
            blob += "Org-ID: <notice>{ORG_INSTANCE}</notice><br>"
            blob += "Faction: <{side}>{SIDE_NAME}</{side}><br>"
            blob += "President: <highlight><president></highlight><br>"
            blob += "Government: <highlight>{GOVERNINGNAME}</highlight><br>"
            blob += "Number of members: <highlight>{NUMMEMBERS:,}</highlight><br>"
            blob += "Description: <{side}>{DESCRIPTION}</{side}><br><br>"
            blob += "Objective: <{side}>{OBJECTIVE}</{side}><br><br>"
            blob += "History: <{side}>{HISTORY}</{side}><br><br>"
            blob += "[{ADD}] - [{REM}]<br>"
            if info[0]["GOVERNINGNAME"] == "Department":
                blob += "<br><notice>Generals<end><br>"
                blob += "<generals>"
            blob = blob.format(**info[0], side=info[0]["SIDE_NAME"].lower(),
                               ADD=self.text.make_chatcmd("Add to our orgs",
                                                          "/tell <myname> orgs add %d" % orgs[0].org_id),
                               REM=self.text.make_chatcmd("Remove from our orgs",
                                                          "/tell <myname> orgs rem %d" % orgs[0].org_id))
            generals = ""
            for member in info[1]:
                if member["RANK"] == 0:
                    blob = blob.replace("<president>", member["NAME"])
                if info[0]["GOVERNINGNAME"] == "Department":
                    if member["RANK"] == 1:
                        generals += "- {NAME} ({LEVELX}/<green>{ALIENLEVEL}<end>) {PROF}<br>".format(**member)
            blob = blob.replace("<generals>", generals)
            return ChatBlob(orgs[0].org_name, blob)
        elif len(orgs) == 0:
            return f"No org with the name <highlight>{org}<end> was found on PoRK."
        else:
            blob = "Your search had multiple results; please pick an org:<br>"
            for org in orgs:
                if Registry.get_instance('org_alias_controller', is_optional=True):
                    blob += f'[{self.text.make_chatcmd("Add", f"/tell <myname> orgs add {org.org_id}")}]'
                    blob += f'[{self.text.make_chatcmd("More", f"/tell <myname> orgs info {org.org_id}")}]'
                blob += f' <highlight>{org.org_name}<end> ' \
                        f'(<highlight>{org.org_id}<end>) <{org.faction.lower()}>{org.faction}<end> ' \
                        f'[<highlight>{org.member_count}<end> members]<br><pagebreak>'
            return ChatBlob("Pick an Org", blob)

    def start_orglist_lookup(self, reply, org_id):
        if self.orglist:
            reply("There is an orglist already in progress.")
            return

        reply(f"Downloading org roster for org id {org_id:d}...")

        self.orglist = self.org_pork_service.get_org_info(org_id)

        if not self.orglist:
            reply(f"Could not find org with ID <highlight>{org_id:d}</highlight>.")
            return

        self.orglist.org_members = list(self.orglist.org_members.values())
        self.orglist.reply = reply
        self.orglist.waiting_org_members = {}
        self.orglist.finished_org_members = {}

        reply(
            f"Checking online status for {len(self.orglist.org_members):d} members of "
            f"<highlight>{self.orglist.org_info.name}</highlight>...")

        # process all name lookups
        while self.bot.iterate(1):
            pass

        self.check_for_orglist_end()

    @event(event_type=BuddyService.BUDDY_LOGON_EVENT,
           description="Detect online buddies for orglist command", is_hidden=True)
    def buddy_logon_event(self, _, event_data):
        if self.orglist and event_data.char_id in self.orglist.waiting_org_members:
            self.update_online_status(event_data.char_id, True)
            self.check_for_orglist_end()

    @event(event_type=BuddyService.BUDDY_LOGOFF_EVENT,
           description="Detect offline buddies for orglist command", is_hidden=True)
    def buddy_logoff_event(self, _, event_data):
        if self.orglist and event_data.char_id in self.orglist.waiting_org_members:
            self.update_online_status(event_data.char_id, False)
            self.check_for_orglist_end()

    def update_online_status(self, char_id, status):
        self.orglist.finished_org_members[char_id] = self.orglist.waiting_org_members[char_id]
        self.orglist.finished_org_members[char_id].online = status
        del self.orglist.waiting_org_members[char_id]

    def check_for_orglist_end(self):
        if self.orglist.org_members:
            self.iterate_org_members()

        if not self.orglist.waiting_org_members:
            self.orglist.reply(self.format_result())
            self.orglist = None

    def format_result(self):
        org_ranks = {}
        for rank_name in self.governing_types[self.orglist.org_info.governing_type]:
            org_ranks[rank_name] = DictObject({
                "online_members": [],
                "offline_members": []
            })

        org_ranks["Inactive"] = DictObject({
            "online_members": [],
            "offline_members": []
        })

        for char_id, org_member in self.orglist.finished_org_members.items():
            if org_member.online == 2:
                org_ranks["Inactive"].offline_members.append(org_member)
            elif org_member.online == 1:
                org_ranks[org_member.org_rank_name].online_members.append(org_member)
            else:
                org_ranks[org_member.org_rank_name].offline_members.append(org_member)

        blob = ""
        num_online = 0
        num_total = 0
        for rank_name, rank_info in org_ranks.items():
            rank_num_online = len(rank_info.online_members)
            rank_num_total = len(rank_info.offline_members) + rank_num_online
            blob += f"<header2>{rank_name} ({rank_num_online:d} / {rank_num_total:d})</header2>\n"
            num_online += rank_num_online
            num_total += rank_num_total
            for org_member in sorted(rank_info.online_members, key=lambda x: x.name):
                level = org_member.level if org_member.ai_level == 0 else \
                    f"{org_member.level:d}/<green>{org_member.ai_level:d}</green>"
                blob += self.format_org(org_member)
                # blob += f"{org_member.name} ({level}, " \
                #         f"{org_member.gender} {org_member.breed} <highlight>{org_member.profession}</highlight>)\n"

            if rank_num_total < 200:
                blob += "<font color='#555555'>" + ", ".join(
                    map(lambda x: x.name, sorted(rank_info.offline_members, key=lambda x: x.name))) + "</font>"
                blob += "\n"
            else:
                blob += "<font color='#555555'>Offline members omitted for brevity</font>\n"
            blob += "\n"

        return ChatBlob(f"Orglist for '{self.orglist.org_info.name}' ({num_online:d} / {num_total:d})", blob)

    def format_org(self, player, rank="", main_order=False):
        return f" {self.util.get_prof_icon(player.profession)} {rank}{self.text.zfill(player.level, 220)}/<green>{self.text.zfill(player.ai_level, 30)}</green> <{player.faction.lower()}>{player.name}</{player.faction.lower()}>\n"

    def iterate_org_members(self):
        # add org_members that we don't have online status for as buddies
        while self.orglist.org_members and self.buddy_list_has_available_slots():
            org_member = self.orglist.org_members.pop()
            char_id = org_member.char_id
            self.orglist.waiting_org_members[char_id] = org_member
            is_online = self.buddy_service.is_online(char_id)
            if is_online is None:
                if self.character_service.resolve_char_to_id(org_member.name):
                    self.buddy_service.add_buddy(char_id, self.ORGLIST_BUDDY_TYPE)
                    self.buddy_service.remove_buddy(char_id, self.ORGLIST_BUDDY_TYPE)
                else:
                    # character is inactive, set as offline
                    self.update_online_status(char_id, 2)
            else:
                self.update_online_status(char_id, is_online)

    def buddy_list_has_available_slots(self):
        return self.buddy_service.buddy_list_size - self.buddy_service.get_buddy_list_size() > 0

    def find_org(self, search, table="all_orgs"):
        if search.isdigit():
            return self.db.query("SELECT * FROM " + table + " where org_id = ?", [search])
        elif isinstance(search, str):
            return self.db.query("SELECT * FROM " + table + " where org_name LIKE ?", [f"%{search}%"])
