import json
import math
import typing

from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Character, Any, NamedParameters
from core.decorators import instance, command, event
from core.dict_object import DictObject

from core.logger import Logger

from core.setting_service import SettingService
from core.setting_types import TextSettingType
from core.text import Text
from core.translation_service import TranslationService
from core.igncore import IgnCore
from core.private_channel_service import PrivateChannelService

if typing.TYPE_CHECKING:
    from core.lookup.character_service import CharacterService
    from core.lookup.pork_service import PorkService
    from core.event_service import EventService


# noinspection SqlCaseVsIf,SqlCaseVsCoalesce
@instance()
class RIAdminController:
    PAGE_SIZE = 20
    UNASSIGNED_RAID_INSTANCE_ID = 0

    def __init__(self):
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot: IgnCore = registry.get_instance("bot")
        self.db = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.util = registry.get_instance("util")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.private_channel_service: PrivateChannelService = registry.get_instance("private_channel_service")
        self.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.pork: PorkService = registry.get_instance("pork_service")
        self.event_service: EventService = registry.get_instance("event_service")

    def start(self):
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS raid_instance (id INT PRIMARY KEY AUTO_INCREMENT, "
            "name VARCHAR(255) NOT NULL, "
            "bot varchar(32)) ENGINE MEMORY")
        self.db.exec(
            "CREATE TABLE IF NOT EXISTS raid_instance_char ("
            "raid_instance_id INT NOT NULL, "
            "char_id INT PRIMARY KEY, "
            "is_leader TINYINT NOT NULL) ENGINE MEMORY")
        self.setting_service.register(self.module_name, "riadmin_network",
                                      "[905848882, 272234, 1923370, 313107, 1217210821, 1134420908]",
                                      TextSettingType(), "Allowed bots (charID's)",
                                      extended_description="This setting is *NOT* synchronized across the network;"
                                                           " this needs to be done manually!")
    def pre_start(self):
        self.event_service.register_event_type("RAID_END")

    @event(event_type="buddy_logoff", description="Track raiders")
    def raider_logoff(self, _, event_data):
        if self.bot.is_ready():
            buddy = self.buddy_service.get_buddy(event_data.char_id)
            if not buddy:
                return
            if "raider" in buddy.get('types', []):
                user = self.db.query_single("SELECT * from player where char_id=?", [event_data.char_id])
                raid = self.get_raid_instance_by_char(event_data.char_id)
                if user and raid:
                    if raid.bot != 0:
                        self.bot.send_private_channel_message(f'[<red>RI</red>] Raider logged off: '
                                                              f'{user.name} - {user.profession} [{raid.name}]')
                self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id])
                self.buddy_service.remove_buddy(event_data.char_id, 'raider')

    @event(event_type=PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, description="Track Raiders")
    def raider_leave(self, _, event_data):
        if self.bot.is_ready():
            buddy = self.buddy_service.get_buddy(event_data.char_id)
            if not buddy:
                return
            if "raider" in buddy.get('types', []):
                user = self.pork.get_character_info(event_data.char_id)
                raid = self.get_raid_instance_by_char(event_data.char_id)
                part = int(raid.bot) if raid else 0
                if part in [self.bot.get_char_id(), 0]:
                    if part != 0:
                        self.bot.send_private_channel_message(
                            f'[<red>RI</red>] Raider left: {user.name} - {user.profession} '
                            f'[{raid.name if raid else "UNASSIGNED"}]')
                    self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id])
                    self.buddy_service.remove_buddy(event_data.char_id, 'raider')

    @event(event_type="connect", description="Init raider tracking on startup")
    def connect(self, _, _1):
        for user in self.get_raid_instance_chars():
            self.buddy_service.add_buddy(user.char_id, 'raider')

    @command(command="riadmin", params=[NamedParameters(['page'])], access_level="leader",
             description="Show the current RIs")
    def riadmin(self, _, named_params):
        refresh = self.text.make_tellcmd("Refresh", "riadmin")
        send = self.text.make_tellcmd("Apply", "riadmin send")
        clear = self.text.make_tellcmd("Clear All", "riadmin clear")
        blob = f"[{refresh}] - Update the RI List\n" \
               f"[{send}] - Send all RI's to their bots\n" \
               f"[{clear}] - Clear all RI's, and delete them"
        blob += "\n\n"

        page = int(named_params.page or "1")
        offset = (page - 1) * self.PAGE_SIZE
        data = self.get_raid_instance_chars()
        return self.format_pagination(data, offset, page, self.compact_char_display, 'Raid Instances',
                                      'No Raidinstances found.', 'riadmin ', self.PAGE_SIZE, blob)

    def format_pagination(self, data, offset, page, formatter, title, nullmsg, cmd, page_size=10, headline=""):
        raid_instances = self.get_raid_instances()
        blob = ""
        if page == 1:
            blob = headline
        selected = data[offset:offset + page_size]
        if len(data) == 0:
            return nullmsg
        num_assigned = 0
        num_unassigned = 0
        pages = ""

        if page > 1:
            pages += "Pages:   " + self.text.make_tellcmd("«« Page %d" % (page - 1), f'{cmd} --page={page - 1}')
        if offset + page_size < len(data):
            pages += f"   Page {page}/{math.ceil(len(data) / page_size)}"
            pages += "   " + self.text.make_tellcmd("Page %d »»" % (page + 1), f'{cmd} --page={page + 1}')
        pages += "\n"
        current_raid_instance_id = None
        for row in data:
            if row.raid_instance_id == self.UNASSIGNED_RAID_INSTANCE_ID:
                num_unassigned += 1
            else:
                if row.name:
                    num_assigned += 1
        blob += "" + pages
        for row in selected:
            if row.raid_instance_id != current_raid_instance_id:
                name = ""
                if row.bot:
                    bot = self.pork.get_character_info(row.bot)
                    name = f"[{bot.name}]" if bot else ""
                blob += f"\n<header2>{row.raid_instance_name} {name}</header2>\n"
                current_raid_instance_id = row.raid_instance_id

            if not row.char_id:
                continue

            blob += formatter(row)
            add_leader_link = (row.raid_instance_id != self.UNASSIGNED_RAID_INSTANCE_ID and not row.is_leader)
            blob += " " + self.get_assignment_links(raid_instances, row.name, add_leader_link)
            blob += "\n"
        blob += "\n" + pages
        out = ChatBlob(title, blob)
        out.page_postfix = f" (<highlight>{num_assigned}</highlight> Players)"
        if num_unassigned > 0:
            out.page_postfix = f" (Assigned: <highlight>{num_assigned}</highlight>, " \
                               f"Unassigned: <highlight>{num_unassigned}</highlight>)"
        return out

    @command(command="ritake", params=[Any("raiders")],
             access_level="all",
             description="take the RI from another bot")
    def ritake(self, request, raiders: str):
        users = raiders.split(",")
        if request.sender.char_id not in json.loads(self.setting_service.get_value('riadmin_network')):
            return
        for user in users:
            char = self.character_service.resolve_char_to_id(user.strip())
            if not char:
                continue
            if self.private_channel_service.in_private_channel(char):
                self.buddy_service.add_buddy(char, 'raider')
                continue
            self.bot.send_mass_message(char, 'You have been assigned to my RI :: <red>accept the invite!</red>')
            self.private_channel_service.invite(char)

    @command(command="riadmin",
             params=[Const("add"), Any("raid_instance"), Character("char")],
             access_level="leader",
             description="Add a character to a RI", sub_command="leader")
    def riadmin_add(self, _, _1, raid_instance_name, char):
        if not char.char_id:
            return self.getresp("global", "char_not_found", {"char": char.name})
        raid_instance = self.get_raid_instance(raid_instance_name)
        if not raid_instance:
            return f"Raid instance <highlight>{raid_instance_name}</highlight> does not exist."

        self.refresh_raid_instance_chars()

        row = self.db.query_single("SELECT raid_instance_id FROM raid_instance_char WHERE char_id = ?", [char.char_id])
        if row:
            if raid_instance.id == row.raid_instance_id:
                if 'raider' not in (
                        self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])):
                    self.buddy_service.add_buddy(char.char_id, 'raider')
                return f"Character <highlight>{char.name}</highlight> is already assigned to " \
                       f"raid instance <highlight>{raid_instance.name}</highlight>."
            else:
                if 'raider' not in (
                        self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])):
                    self.buddy_service.add_buddy(char.char_id, 'raider')
                self.update_char_raid_instance(char.char_id, raid_instance.id)
                return f"Character <highlight>{char.name}</highlight> has been assigned to " \
                       f"raid instance <highlight>{raid_instance.name}</highlight>."
        else:
            return f"Character <highlight>{char.name}</highlight> is not in the private channel."

    @command(command="riadmin", params=[Const("clear")], access_level="leader",
             description="Remove all raids and their players", sub_command="leader")
    def riadmin_clear(self, _, _1):
        query = self.db.query("SELECT * FROM raid_instance_char")
        for user in query:
            self.buddy_service.remove_buddy(user.char_id, 'raider')
        self.db.exec("DELETE FROM raid_instance_char where 1")
        self.db.exec("DELETE FROM raid_instance where 1")
        return f"All characters have been removed from raid instances."

    @command(command="riadmin", params=[Const("rem"), Character("char")], access_level="leader",
             description="remove a character from the RI", sub_command="leader")
    def riadmin_rem(self, _, _2, char):
        if not char.char_id:
            return self.getresp("global", "char_not_found", {"char": char.name})

        self.refresh_raid_instance_chars()

        row = self.db.query_single(
            "SELECT r2.name FROM raid_instance_char r1 "
            "JOIN raid_instance r2 ON r1.raid_instance_id = r2.id WHERE r1.char_id = ?",
            [char.char_id])
        if row:
            self.update_char_raid_instance(char.char_id, self.UNASSIGNED_RAID_INSTANCE_ID)
            return f"Character <highlight>{char.name}</highlight> has been removed from " \
                   f"raid instance <highlight>{row.name}</highlight>."
        else:
            return f"Character <highlight>{char.name}</highlight> is not assigned to any raid instances."

    @command(command="riadmin", params=[Const("leader"), Character("char")], access_level="leader",
             description="Set the leader for a RI", sub_command="leader")
    def riadmin_leader(self, _, _2, char):
        if not char.char_id:
            return self.getresp("global", "char_not_found", {"char": char.name})

        raid_instance = self.get_raid_instance_by_char(char.char_id)
        if not raid_instance:
            return f"Character <highlight>{char.name}</highlight> does not belong to a raid instance."

        self.set_leader(char.char_id, raid_instance.id)

        return f"Character <highlight>{char.name}</highlight> has been set as the leader for " \
               f"raid instance <highlight>{raid_instance.name}</highlight>."

    @command(command="riadmin", params=[Const("send")], access_level="leader",
             description="Send the RIs to their preset bots", sub_command="leader")
    def riadmin_send(self, _, _2):
        self.bot.send_private_channel_message("Exporting raids..")
        for raid_instance in self.get_raid_instances():
            data = self.db.query(
                "SELECT r.char_id, p.name, r.is_leader FROM raid_instance_char r "
                "left join player p on r.char_id=p.char_id WHERE raid_instance_id = ?",
                [raid_instance.id])
            if int(raid_instance.bot) == self.bot.get_char_id() or raid_instance.bot == "0" or not raid_instance.bot:
                continue
            self.bot.send_private_message(int(raid_instance.bot), "ritake " + ", ".join([k['name'] for k in data]),
                                          add_color=False)
            for char in data:
                if char.is_leader == 0:
                    self.private_channel_service.kick(char.char_id)

        return "Raid instance configuration has been applied."

    @command(command="riadmin", params=[Const("create"), Any("Raid_name"), Character("bot")],
             access_level="leader",
             description="Create or change a raid instance", sub_command="leader")
    def riadmin_create(self, _, _2, raid_instance_name, bot):
        if not bot.char_id:
            return self.getresp("global", "char_not_found", {"char": bot.name})
        if bot.char_id not in json.loads(self.setting_service.get_value('riadmin_network')):
            return "Bot not valid: please ask an Administrator to verify it first."
        raid_instance = self.get_raid_instance(raid_instance_name)
        if raid_instance:
            if raid_instance.name == raid_instance_name and raid_instance.bot == bot.char_id:
                return f"Raid instance <highlight>{raid_instance_name}</highlight> already exists."
            else:
                self.db.exec("UPDATE raid_instance SET name = ?, bot = ? WHERE id = ?",
                             [raid_instance_name, bot.char_id, raid_instance.id])
                return f"Raid instance <highlight>{raid_instance_name}</highlight> has been updated."
        else:
            self.db.exec("INSERT INTO raid_instance (name, bot) VALUES (?, ?)", [raid_instance_name, bot.char_id])
            return f"Raid instance <highlight>{raid_instance_name}</highlight> [<highlight>{bot.name}</highlight>] " \
                   f"has been created."

    @command(command="riadmin", params=[Const("delete"), Any("raid_instance_name")], access_level="leader",
             description="Remove a RI", sub_command="leader")
    def raid_instance_delete_cmd(self, _, _1, raid_instance_name):
        raid_instance = self.get_raid_instance(raid_instance_name)
        if not raid_instance:
            return f"Raid instance <highlight>{raid_instance_name}</highlight> does not exist."
        query = self.db.query("SELECT char_id from raid_instance_char where raid_instance_id=?", [raid_instance.id])
        for user in query:
            self.buddy_service.remove_buddy(user.char_id, 'raider')
        self.db.exec("DELETE FROM raid_instance_char WHERE raid_instance_id = ?", [raid_instance.id])
        self.db.exec("DELETE FROM raid_instance WHERE id = ?", [raid_instance.id])

        return f"Raid instance <highlight>{raid_instance_name}</highlight> has been deleted."

    def get_raid_instance_chars(self):
        self.refresh_raid_instance_chars()

        data = self.db.query("SELECT * FROM ("
                             "SELECT p.*, r2.id AS raid_instance_id, "
                             "r1.is_leader, r2.name AS raid_instance_name, r2.bot "
                             "FROM raid_instance r2 "
                             "LEFT JOIN raid_instance_char r1 ON r1.raid_instance_id = r2.id "
                             "LEFT JOIN player p ON r1.char_id = p.char_id "
                             "UNION "
                             "SELECT p.*, r3.raid_instance_id, "
                             "r3.is_leader, 'Unassigned' AS raid_instance_name, '' AS bot "
                             "FROM raid_instance_char r3 "
                             "LEFT JOIN player p ON r3.char_id = p.char_id "
                             "WHERE r3.raid_instance_id = ?) as r2r1pr3p "
                             "ORDER BY raid_instance_id != ? DESC, "
                             "raid_instance_name, "
                             "is_leader desc, "
                             "profession, level desc, "
                             "name",
                             [self.UNASSIGNED_RAID_INSTANCE_ID, self.UNASSIGNED_RAID_INSTANCE_ID])

        return data

    def refresh_raid_instance_chars(self):
        users = self.db.query('SELECT * from online o '
                              'left join player p on o.char_id=p.char_id '
                              'where o.channel = ?', [self.bot.name])
        for user in users:
            self.db.exec(
                "INSERT IGNORE INTO raid_instance_char (char_id, raid_instance_id, is_leader) VALUES (?, ?, 0)",
                [user.char_id, self.UNASSIGNED_RAID_INSTANCE_ID])
            self.buddy_service.add_buddy(user.char_id, 'raider')

    def update_char_raid_instance(self, char_id, raid_instance_id):
        return self.db.exec("UPDATE raid_instance_char SET raid_instance_id = ?, is_leader = 0 WHERE char_id = ?",
                            [raid_instance_id, char_id])

    def set_leader(self, char_id, raid_instance_id):
        self.db.exec("UPDATE raid_instance_char SET is_leader = 0 WHERE raid_instance_id = ?", [raid_instance_id])
        self.db.exec("UPDATE raid_instance_char SET is_leader = 1 WHERE raid_instance_id = ? AND char_id = ?",
                     [raid_instance_id, char_id])

    def compact_char_display(self, char_info):
        if char_info.level:
            msg = f" {self.util.get_prof_icon(char_info.profession)} " \
                  f"{self.text.zfill(char_info.level, 220)}/<green>{self.text.zfill(char_info.ai_level, 30)}</green> " \
                  f"{char_info.name: <13}"
        elif char_info.name:
            msg = " <highlight>%s</highlight>" % char_info.name
        else:
            msg = " <highlight>Unknown(%d)</highlight>" % char_info.char_id

        if char_info.is_leader:
            msg += " [<highlight>Leader</highlight>]"

        return msg

    def get_assignment_links(self, raid_instances, char_name, add_leader_link):
        links = list(map(lambda x: self.text.make_tellcmd(x.name, f"riadmin add {x.name} {char_name}"), raid_instances))
        links.insert(0, self.text.make_tellcmd("Rem", f"riadmin rem {char_name}"))
        if add_leader_link:
            links.insert(0, self.text.make_tellcmd("L", f"riadmin leader {char_name}"))
        return f'[{"|".join(links)}]'

    def get_raid_instances(self):
        data = self.db.query("SELECT id, name, bot FROM raid_instance ORDER BY name")
        return data

    def get_raid_instance(self, raid_instance_name):
        return self.db.query_single("SELECT id, name, bot FROM raid_instance WHERE name LIKE ?", [raid_instance_name])

    def get_raid_instance_by_char(self, char_id):
        return self.db.query_single("SELECT id, name, CASE WHEN bot IS NOT NULL THEN bot else 0 END as bot "
                                    "FROM raid_instance r1 JOIN raid_instance_char r2 ON r1.id = r2.raid_instance_id "
                                    "WHERE r2.char_id = ?", [char_id])

    def get_conn_by_id(self, bot):
        conn = self.bot.conns.get(bot)
        if conn:
            return conn

        conns = self.bot.get_conns(lambda x: x.char_name.lower() == bot.lower())
        if conns:
            return conns[0][1]

        return None
