import re
import secrets
import time
from collections import OrderedDict

from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Const, Int, Any
from core.db import DB
from core.decorators import instance, command, timerevent
from core.igncore import IgnCore
from core.setting_service import SettingService
from core.text import Text
from modules.core.accounting.services.account_service import AccountService
from modules.raidbot.raid.raidbot_controller import Raider
from modules.standard.items.items_controller import ItemsController
from modules.standard.loot.item_types import LootItem
from modules.standard.raid.leader_controller import LeaderController


@instance()
class LootController:
    NOT_LEADER_MSG = "Error! You must be raid leader, or have higher access " \
                     "level than the raid leader to use this command."

    def __init__(self):
        self.loot_list = OrderedDict()
        self.last_modify = None

    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.leader_controller: LeaderController = registry.get_instance("leader_controller", is_optional=True)
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.items_controller: ItemsController = registry.get_instance("items_controller")
        self.raid_controller = None
        self.raid_controller = registry.get_instance("raidbot_controller", is_optional=True)
        self.account_service: AccountService = registry.get_instance("account_service")
        self.alts_service: AccountService = registry.get_instance("account_service")
        self.alias: CommandAliasService = registry.get_instance("command_alias_service")

    def pre_start(self):
        self.db.load_sql_file(self.module_dir + "/raid_loot.sql", pre_optimized=True)
        self.db.create_view("raid_loot")
        self.alias.add_alias("add", "loot add")
        self.alias.add_alias("rem", "loot rem")
        self.alias.add_alias("remove", "loot remo")
        self.alias.add_alias("flatroll", "loot roll")
        self.alias.add_alias("result", "loot roll")
        self.alias.add_alias("results", "loot roll")
        self.alias.add_alias("list", "loot")
        self.alias.add_alias("win", "loot roll")

    @command(command="loot", params=[], description="Show the list of added items", access_level="member")
    def loot_cmd(self, _):
        if not self.loot_list:
            return "Loot list is empty."

        return self.get_loot_list()

    @command(command="loot", params=[Const("edit")], description="Show the list of added items", access_level="leader",
             sub_command="modify")
    def loot_edit_cmd(self, _, _1):
        if not self.loot_list:
            return "Loot list is empty."

        return self.get_loot_list(edit=True)

    @command(command="loot", params=[Const("clear")], description="Clear all loot", access_level="leader",
             sub_command="modify")
    def loot_clear_cmd(self, request, _):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if self.loot_list:
            self.loot_list.clear()
            self.last_modify = None
            self.bot.send_private_channel_message("Loot list cleared.")
        else:
            return "Loot list is already empty."

    @command(command="loot", params=[Const("remitem"), Int("item_index")],
             description="Remove an existing loot item", access_level="leader", sub_command="modify")
    def loot_rem_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            if self.loot_list[item_index]:
                self.last_modify = int(time.time())
                self.bot.send_private_channel_message(
                    f"Removed {self.loot_list.pop(item_index).get_item_str()} from loot list.")
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("increase"), Int("item_index")], description="Increase item count",
             access_level="leader", sub_command="modify")
    def loot_increase_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            loot_item = self.loot_list[item_index]

            if loot_item:
                loot_item.count += 1
                self.last_modify = int(time.time())
                self.bot.send_private_channel_message(
                    f"Increased item count for {loot_item.get_item_str()} to {loot_item.count:d}.")
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("decrease"), Int("item_index")], description="Decrease item count",
             access_level="leader", sub_command="modify")
    def loot_decrease_item_cmd(self, request, _, item_index: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if not self.loot_list:
            return "Loot list is empty."

        try:
            loot_item = self.loot_list[item_index]

            if loot_item:
                loot_item.count = loot_item.count - 1 if loot_item.count > 1 else 1
                self.last_modify = int(time.time())
                self.bot.send_private_channel_message(
                    f"Decreased item count for {loot_item.get_item_str()} to {loot_item.count:d}.")
            else:
                return "Item error."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("add"), Int("item_index")], description="Add yourself to item",
             access_level="member")
    def loot_add_to_cmd(self, request, _, item_index: int):
        main = self.alts_service.get_account(request.sender.char_id)
        if self.raid_controller:
            raider: Raider = self.raid_controller.is_in_raid(main.get("char_id", -1))
            if type(raider) == Raider:
                if not raider.is_active:
                    self.bot.send_mass_message(request.sender.char_id,
                                               "You are not participating in the raid, and cannot add to the loot.")
                    return
            elif raider is None:
                self.bot.send_mass_message(request.sender.char_id,
                                           "You are not participating in the raid, and cannot add to the loot.")
                return
        try:
            loot_item = self.loot_list[item_index]
            old_item = self.is_already_added(request.sender.name)
            if old_item:
                if old_item.get_item_str() == loot_item.get_item_str():
                    name = "You have" if request.channel == "msg" else request.sender.name
                    self.bot.send_mass_message(request.sender.char_id,
                                               f"{name} already added to {loot_item.get_item_str()}.")
                old_item.bidders.remove(request.sender.name)

            loot_item.bidders.append(request.sender.name)

            self.last_modify = int(time.time())

            if old_item is not None:
                text = f"moved from {old_item.get_item_str()} to {loot_item.get_item_str()}."
                self.bot.send_mass_message(request.sender.char_id, f"You have {text}")
                self.bot.send_private_channel_message(request.sender.name + " " + text)
            else:
                text = f"added to {loot_item.get_item_str()}."
                self.bot.send_mass_message(request.sender.char_id, f"You have {text}")
                self.bot.send_private_channel_message(request.sender.name + " " + text)

        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("rem")], description="Remove yourself from item", access_level="member")
    def loot_rem_from_cmd(self, request, _):
        try:
            loot_item = self.is_already_added(request.sender.name)

            if loot_item is not None:
                loot_item.bidders.remove(request.sender.name)

                self.last_modify = int(time.time())
                text = f"removed from {loot_item.get_item_str()}."
                self.bot.send_private_message(request.sender.char_id, f"You were {text}")
                self.bot.send_private_channel_message(request.sender.name + " was " + text)
            else:
                return "You are not added to any loot."
        except KeyError:
            return "Wrong index given."

    @command(command="loot", params=[Const("roll")], description="Roll all loot", access_level="leader",
             sub_command="modify")
    def loot_roll_cmd(self, request, _):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if self.loot_list:
            blob = ""
            remove = []
            for i, loot_item in self.loot_list.items():
                winners = []

                if loot_item.bidders:
                    if len(loot_item.bidders) <= loot_item.count:
                        winners = loot_item.bidders.copy()
                        loot_item.count -= len(loot_item.bidders)
                        loot_item.bidders = []
                    else:
                        for j in range(0, loot_item.count):
                            winner = secrets.choice(loot_item.bidders)

                            winners.append(winner)
                            loot_item.bidders.remove(winner)
                            loot_item.count = loot_item.count - 1 if loot_item.count > 0 else 0
                    for user in winners:
                        self.account_service.add_log(self.account_service.character_service.resolve_char_to_id(user),
                                                     "loot",
                                                     f"{loot_item.get_item_str()}",
                                                     request.sender.char_id)
                    blob += f"{i:d}. {loot_item.get_item_str()}\n"
                    blob += f" | Winners: <red>{'<end>, <red>'.join(winners)}<end>\n\n"
                if loot_item.count == 0:
                    remove.append(i)
            for i in remove:
                self.loot_list.pop(i)

            msg = ChatBlob("Roll results", blob, "\n________________\n\n<red>Time is UP!</red>\n", "\n________________")
            self.bot.send_private_channel_message(msg if len(blob) > 0 else "No one was added to any loot")
            if self.loot_list:
                count = 1
                for key in sorted(list(self.loot_list.keys())):
                    if self.loot_list[key].count <= 0:
                        del self.loot_list[key]
                    else:
                        loot_item = self.loot_list[key]
                        del self.loot_list[key]
                        self.loot_list[count] = loot_item
                        count += 1
            if len(self.loot_list) > 0:
                self.bot.send_private_channel_message(f"Theres still loot left to roll, the list has been rebuilt.")
        else:
            return "No loot to roll."

    @command(command="loot", params=[Const("addraiditem"), Int("raid_item_id"), Int("item_count")],
             description="Add item from pre-defined raid to loot list", access_level="leader", sub_command="modify")
    def loot_add_raid_item(self, request, _, raid_item_id: int, item_count: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        sql = "SELECT r.name, r.comment,  r.ql, a.lowid AS low_id, a.highid AS high_id FROM aodb a " \
              "LEFT JOIN raid_loot r ON (a.name = r.name AND a.highql >= r.ql) " \
              "WHERE r.id = ? LIMIT 1"
        item = self.db.query_single(sql, [raid_item_id])

        if item:
            self.add_item_to_loot(item, item.comment, item_count)

            self.bot.send_private_channel_message(f"Added {item.name} to loot list.")
        else:
            return f"Failed to add item with ID {raid_item_id}."

    @command(command="loot", params=[Const("addraid"), Any("raid"), Any("category")],
             description="Add all loot from pre-defined raid", access_level="leader", sub_command="modify")
    def loot_add_raid_loot(self, request, _, raid: str, category: str):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        items = self.db.query(
            "SELECT r.raid, r.category, r.id, r.ql, r.name, r.comment, r.multiloot, "
            "a.lowid AS low_id, "
            "a.highid AS high_id, a.icon "
            "FROM raid_loot r "
            "LEFT JOIN aodb a "
            "ON (r.name = a.name AND r.ql <= a.highql) "
            "WHERE r.raid = ? AND r.category = ? "
            "ORDER BY r.name",
            [raid, category]
        )

        if items:
            for item in items:
                self.add_item_to_loot(item, item.comment, item.multiloot)

            self.bot.send_private_channel_message(f"{category} table was added to loot.")
        else:
            return f"{category} does not have any items registered in loot table."

    @command(command="loot",
             params=[Const("additem", is_optional=True), Int("item"), Int("item_count", is_optional=True)],
             description="Add an item to loot list by item id", access_level="leader", sub_command="modify")
    def loot_add_item_id_cmd(self, request, _, item_id, item_count: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG

        if item_count is None:
            item_count = 1

        item = self.items_controller.get_by_item_id(item_id)
        if not item:
            return f"Could not find item with ID <highlight>{item_id}<end>."

        self.add_item_to_loot(item, item_count=item_count)

        self.bot.send_private_channel_message(f"{item.name} was added to loot list.")

    @command(command="loot",
             params=[Const("additem", is_optional=True), Any("item"), Int("item_count", is_optional=True)],
             description="Add an item to loot list", access_level="leader", sub_command="modify")
    def loot_add_item_cmd(self, request, _, item, item_count: int):
        if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False:
            return LeaderController.NOT_LEADER_MSG
        loot = ""
        if item_count is None:
            item_count = 1
        item = item.replace("&#39;", "'")
        items = re.findall(r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)", item)
        if items and item_count == 1:
            for item in items:
                item = self.text.make_item(int(item[2]), int(item[3]), int(item[4]), item[5])
                if loot != "":
                    loot += ", " + item
                else:
                    loot += item
                self.add_item_to_loot(item)
        else:
            out = re.match(r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)", item)
            if out:
                # print(out.groups())
                loot += item
                item = self.text.make_item(int(out[3]), int(out[4]), int(out[5]), out[6])
                self.add_item_to_loot((out[2] or "") + item + (out[7] or ""), item_count=item_count)

            else:
                loot += item
                self.add_item_to_loot(item, item_count=item_count)
        self.bot.send_private_channel_message(f"<highlight>{loot}<end> was added to loot list.")

    @timerevent(budatime="1h",
                description="Periodically check when loot list was last modified, and clear it "
                            "if last modification was done 1+ hours ago")
    def loot_clear_event(self, _1, _2):
        if self.loot_list and self.last_modify:
            if int(time.time()) - self.last_modify > 3600 and self.loot_list:
                self.last_modify = None
                self.loot_list = OrderedDict()
                self.bot.send_org_message("Loot was last modified more than 1 hour ago, list has been cleared.", fire_outgoing_event=False)
                self.bot.send_private_channel_message(
                    "Loot was last modified more than 1 hour ago, list has been cleared.", fire_outgoing_event=False)

    def is_already_added(self, name: str):
        for i, loot_item in self.loot_list.items():
            if name in loot_item.bidders:
                return loot_item
        return None

    def add_item_to_loot(self, item, comment=None, item_count=1):
        existing_item = next((loot_item for x, loot_item in self.loot_list.items() if loot_item.item == item), None)
        if existing_item:
            existing_item.count += 1
        else:
            # this prevents loot items from being re-numbered when items are removed
            end_index = list(self.loot_list.keys())[-1] + 1 if len(self.loot_list) > 0 else 1

            self.loot_list[end_index] = LootItem(item, comment, item_count)

        self.last_modify = int(time.time())

    def get_loot_list(self, edit=False):
        blob = ""
        remove = self.text.make_chatcmd("Remove from rolls", "/tell <myname> loot rem")
        blob += f"[{remove}]\n\n"
        for i, loot_item in self.loot_list.items():
            bidders = loot_item.bidders
            img = ""

            if loot_item.get_item_str()[:2] == "<a" and loot_item.get_item_str()[-4:] == "</a>":
                item = re.findall(r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)",
                                  loot_item.get_item_str())
                imgid = self.db.query_single("SELECT icon from aodb where lowid=? or highid=?",
                                             [item[0][2], item[0][3]])
                if imgid is not None:
                    img = self.text.make_image(imgid.get("icon")) + " - "
                blob += f"{i:d}. {img}{item[0][0]} "

            else:
                blob += f"{i:d}. {loot_item.get_item_str()} "
            blob += f"x{loot_item.count} [{self.text.make_chatcmd('Add', f'/tell <myname> loot add {i}')}]\n"
            blob += " └ "
            if edit:
                blob += f"<yellow>[ {self.text.make_tellcmd('+1', f'loot increase {i}')} | " \
                        f"{self.text.make_tellcmd('-1', f'loot decrease {i}')} ]" \
                        f"[ {self.text.make_tellcmd('REM', f'loot remitem {i}')} ]</yellow>\n\n"
            else:
                if len(bidders) > 0:
                    blob += f"<yellow>{', '.join(bidders)}<end>\n\n"
                else:
                    blob += "<yellow>None added<end>\n\n"

        return ChatBlob(f"Loot ({len(self.loot_list):d})", blob)
