import time
from functools import partial

from core.aochat.server_packets import BuddyAdded, CharacterName
from core.chat_blob import ChatBlob
from core.command_param_types import Character, Const, Int
from core.command_request import CommandRequest
from core.db import DB
from core.decorators import instance, command, timerevent
from core.dict_object import DictObject
from core.lookup.pork_service import PorkService
from core.text import Text
from core.igncore import IgnCore


@instance()
class CharacterInfoController:
    BUDDY_IS_ONLINE_TYPE = "is_online"

    def __init__(self):
        self.name_history = []
        self.waiting_for_update = {}

    def inject(self, registry):
        self.bot: IgnCore = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.pork_service: PorkService = registry.get_instance("pork_service")
        self.command_alias_service = registry.get_instance("command_alias_service")
        self.util = registry.get_instance("util")
        self.account_service = registry.get_instance("account_service")
        self.buddy_service = registry.get_instance("buddy_service")
        self.alts_controller = registry.get_instance("alts_controller")

    def pre_start(self):
        self.bot.register_packet_handler(CharacterName.id, self.character_name_update)

    def start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS name_history ("
                     "char_id INT NOT NULL, "
                     "name VARCHAR(20) NOT NULL, "
                     "created_at INT NOT NULL, "
                     "PRIMARY KEY (char_id, name))")
        self.db.create_view("name_history")
        self.command_alias_service.add_alias("w", "whois")
        self.command_alias_service.add_alias("lookup", "whois")
        self.command_alias_service.add_alias("is", "whois")

    @command(command="whois",
             params=[Character("character"),
                     Int("server_num", is_optional=True),
                     Const("forceupdate", is_optional=True)],
             access_level="member",
             description="Get whois information for a character",
             extended_description="Use server_num 6 for RK2019 and server_num 5 for live")
    def whois_cmd(self, request, char, dimension, force_update):

        dimension = dimension or self.bot.dimension

        if dimension == self.bot.dimension and char.char_id:
            online_status = self.buddy_service.is_online(char.char_id)
            if online_status is None:
                self.bot.register_packet_handler(BuddyAdded.id, self.handle_buddy_status)
                self.waiting_for_update[char.char_id] = \
                    DictObject({"char_id": char.char_id, "name": char.name,
                                "callback": partial(self.show_output, char, dimension, force_update, request)})
                self.buddy_service.add_buddy(char.char_id, self.BUDDY_IS_ONLINE_TYPE)
            else:
                self.show_output(char, dimension, force_update, request, online_status)
        else:
            self.show_output(char, dimension, force_update, request, None)

    def show_output(self, char, dimension, force_update, request: CommandRequest, online_status):
        max_cache_age = 0 if force_update else 86400

        if dimension != self.bot.dimension:
            char_info = self.pork_service.request_char_info(char.name, dimension)
        else:
            char_info = self.pork_service.get_character_info(char.name, max_cache_age)

        if char_info and char_info.source != "chat_server":
            blob = "Name: %s (%s)\n" % (self.get_full_name(char_info),
                                        self.text.make_tellcmd("History",
                                                               f"history {char_info.name} {char_info.dimension}"))
            blob += f"Character Id: {char_info.char_id:d}\n"
            blob += f"Profession: {char_info.profession}\n"
            blob += f"Faction: {self.text.get_formatted_faction(char_info.faction)}\n"
            blob += f"Breed: {char_info.breed}\n"
            blob += f"Gender: {char_info.gender}\n"
            blob += f"Level: {char_info.level:d}\n"
            blob += f"AI Level: <green>{char_info.ai_level:d}</green>\n"
            if char_info.org_id:
                blob += f"Org: <highlight>{char_info.org_name}</highlight> ({char_info.org_id:d})\n"
                blob += f"Org Rank: {char_info.org_rank_name} ({char_info.org_rank_id:d})\n"
            else:
                blob += "Org: &lt;None&gt;\n"
                blob += "Org Rank: &lt;None&gt;\n"
            blob += f"Source: {self.format_source(char_info, max_cache_age)}\n"
            blob += f"Dimension: {char_info.dimension}\n"

            if dimension == self.bot.dimension:
                blob += f"Status: {'<green>Active</green>' if char.char_id else '<red>Inactive</red>'}\n"

                blob += self.get_name_history(char.char_id)
                alts = self.account_service.get_alts(char.char_id)
                if len(alts) > 1:
                    blob += f"\n<header2>Alts ({len(alts):d})</header2>\n"
                    blob += self.alts_controller.format_alt_list(alts)

            more_info = self.text.paginate_single(ChatBlob("More Info", blob))
            msg = ChatBlob("More Info", blob, self.text.format_char_info(char_info, online_status, True)+" ")
            # msg = self.text.format_char_info(char_info, online_status, True) + " " + more_info
        elif char.char_id:
            blob = "<notice>Note: Could not retrieve detailed info for character.</notice>\n\n"
            blob += f"Name: <highlight>{char.name}</highlight>\n"
            blob += f"Character ID: <highlight>{char.char_id}</highlight>\n"
            if online_status is not None:
                blob += f"Online status: {'<green>Online</green>' if online_status else '<red>Offline</red>'}\n"
            blob += self.get_name_history(char.char_id)
            msg = ChatBlob(f"Basic Info for {char.name}", blob)
        else:
            msg = f"Could not find character <highlight>{char.name}</highlight> on RK{dimension:d}."
        if request.channel == "msg" and request.conn.id == "main":
            self.bot.send_mass_message(request.sender.char_id, msg)
        else:
            request.reply(msg)

    def get_name_history(self, char_id):
        blob = "\n<header2>Name History</header2>\n"
        data = self.db.query("SELECT name, created_at FROM name_history "
                             "WHERE char_id = ? ORDER BY created_at DESC",
                             [char_id])
        for row in data:
            blob += f"[{self.util.format_date(row.created_at)}] {row.name}\n"
        return blob

    @timerevent(budatime="1min", description="Save name history", is_hidden=True)
    def save_name_history_event(self, _, _1):
        if not self.name_history:
            return

        with self.db.pool.get_connection() as conn:
            with conn.cursor() as cur:
                cur.executemany("INSERT IGNORE INTO name_history (char_id, name, created_at) "
                                "VALUES (?, ?, ?)",
                                self.name_history)
        self.name_history = []

    def get_full_name(self, char_info):
        name = ""
        if char_info.first_name:
            name += char_info.first_name + " "

        name += "\"<highlight>" + char_info.name + "</highlight>\""

        if char_info.last_name:
            name += " " + char_info.last_name

        return name

    def format_source(self, char_info, max_cache_age):
        if char_info.cache_age == 0:
            return char_info.source
        elif char_info.cache_age < max_cache_age:
            return f"{char_info.source} (cache; {self.util.time_to_readable(char_info.cache_age)} old)"
        elif char_info.cache_age > max_cache_age:
            return f"{char_info.source} (old cache; {self.util.time_to_readable(char_info.cache_age)} old)"

    def handle_buddy_status(self, _, packet):
        obj = self.waiting_for_update.get(packet.char_id)
        if obj:
            self.buddy_service.remove_buddy(packet.char_id, self.BUDDY_IS_ONLINE_TYPE)
            del self.waiting_for_update[packet.char_id]
            if not self.waiting_for_update:
                self.bot.remove_packet_handler(BuddyAdded.id, self.handle_buddy_status)

            obj.callback(packet.online == 1)

    def character_name_update(self, _, packet):
        self.name_history.append((packet.char_id, packet.name, time.time()))
