import re
import time

from core.chat_blob import ChatBlob
from core.db import DB
from core.registry import Registry
from core.sender_obj import SenderObj
from core.text import Text
from modules.core.accounting.services.account_service import AccountService
from modules.raidbot.raid.raidbot_controller import Raider


class AuctionBid:
    def __init__(self, sender: SenderObj, account, current_amount, max_amount):
        self.sender = sender
        self.account = account
        self.current_amount = current_amount
        self.max_amount = max_amount

    def __str__(self):
        return self.__dict__.__str__()


class AuctionStrategy:
    def __init__(self):
        self.bot = Registry.get_instance("bot")
        self.db: DB = Registry.get_instance("db")
        self.text: Text = Registry.get_instance("text")
        self.account_service: AccountService = Registry.get_instance("account_service")
        self.job_scheduler = Registry.get_instance("job_scheduler")
        self.raid_controller = None
        self.raid_controller = Registry.get_instance("raidbot_controller", is_optional=True)
        self.auction_start_time = None
        self.auction_end_time = None
        self.announce_interval = None
        self.is_started = False
        self.items = dict()
        self.winning_bids = dict()
        self.next_item_index = 1
        # noinspection PyTypeChecker
        self.auctioneer: SenderObj = None
        self.job_id = None
        self.is_running = False

    def start(self, sender: SenderObj, duration, announce_interval):
        if not self.items:
            return "Could not find any items to start auction."

        self.is_started = True
        self.auction_start_time = int(time.time())
        self.auctioneer = sender
        self.auction_end_time = self.auction_start_time + duration
        self.is_running = True
        self.announce_interval = announce_interval

        if len(self.items) > 1:
            items = ""
            for i, item in self.items.items():
                winning = ""
                if self.winning_bids.get(i, None):
                    winning = f"(<highlight>{self.winning_bids[i].current_amount}</highlight>)"
                items += f"{i}. {item} {winning}\n"
            msg = self.format_auction(f"{items}\n"
                                      f"» {self.text.paginate_single(self.get_auction_list())}")
            self.spam_raid_message(msg)
        else:
            item_index = list(self.items.keys())[0]
            item = self.items[item_index]
            sql = "SELECT winning_bid FROM auction_log WHERE item_name LIKE ? ORDER BY time DESC LIMIT 5"
            bids = self.db.query(sql, [item])
            if bids:
                avg_win_bid = int(sum(map(lambda x: x.winning_bid, bids)) / len(bids))
            else:
                avg_win_bid = 0

            bid_link = self.get_auction_list()
            bid_link = self.text.paginate_single(ChatBlob("Click to bid", bid_link.msg))
            msg = self.format_auction(f"1. {item}\n"
                                      f"Average winning bid: <highlight>{avg_win_bid}<end>\n"
                                      f"» {bid_link}")
            self.spam_raid_message(msg)

        self.create_next_announce_job()

    def cancel(self, _):
        self.cancel_job()
        self.is_running = False
        return "The Auction got cancelled."

    def add_item(self, item):
        self.items[self.next_item_index] = item
        self.next_item_index += 1
        return self.next_item_index

    def remove_item(self, item_id):
        del self.items[item_id]
        del self.winning_bids[item_id]

    def add_bid(self, sender: SenderObj, bid_amount, item_index, request):
        item_index = item_index or 1
        item = self.items.get(item_index, None)
        if not item:
            return "No item at given index."
        if bid_amount == 0:
            return "You tried to bid 0 points. seriously?"

        account = self.account_service.get_account(sender.char_id)
        main = account.main
        if self.raid_controller:
            raider: Raider = self.raid_controller.is_in_raid(main)
            if type(raider) == Raider:
                if not raider.is_active:
                    return "You are not participating in the raid, and cannot add to the loot."
            elif raider is None:
                return "You are not participating in the raid, and cannot add to the loot."
        if not account:
            return "You do not have an active account with this bot."
        elif account.disabled:
            return "Your account has been frozen. Contact an admin."

        points_used = self.get_points_used(main, item_index)
        points_available = account.points - points_used

        if points_available == 0:
            return "You got 0 points... no bids for you."

        if not bid_amount:
            return "You must specify an amount to bid."

        if isinstance(bid_amount, str) and bid_amount.lower() == "all":
            bid_amount = points_available

        if bid_amount > points_available:
            request.reply(f"You dont have enough points. Using {points_available:d} (All points you have)")
            bid_amount = points_available

        current_amount = 0
        winning_bid = self.winning_bids.get(item_index, None)
        if winning_bid:
            if winning_bid.sender.char_id == sender.char_id:
                return "You're already holding the highest bid."
            if bid_amount <= winning_bid.current_amount:
                return f"Your bid of <highlight>{bid_amount:d}<end> points was not enough. " \
                       f"<highlight>{winning_bid.sender.name}<end> is currently winning with a " \
                       f"bid of <highlight>{winning_bid.current_amount:d}<end>."
            elif bid_amount <= winning_bid.max_amount:
                winning_bid.current_amount = bid_amount
                return f"Your bid of <highlight>{bid_amount}<end> points was not enough. " \
                       f"<highlight>{winning_bid.sender.name}<end> is currently " \
                       f"winning with a bid of <highlight>{winning_bid.current_amount}<end>."
            else:
                current_amount = winning_bid.max_amount
                self.bot.send_private_message(winning_bid.sender.char_id,
                                              f"Your bid on {item} has been "
                                              f"overtaken by <highlight>{sender.name}<end>.")

        # increment 1 past current max bid
        current_amount += 1
        self.winning_bids[item_index] = AuctionBid(sender, account, current_amount, bid_amount)
        if self.auction_end_time - time.time() < 10:
            self.auction_end_time += 10
            self.spam_raid_message(
                f"<highlight>{sender.name}<end> now holds the leading "
                f"bid for {item} with a bid of <highlight>{current_amount:d}<end>. [AS: +10 seconds]")

        else:
            self.spam_raid_message(
                f"<highlight>{sender.name}<end> now holds the leading bid for {item} "
                f"with a bid of <highlight>{current_amount:d}<end>.")

        return f"Your max bid of <highlight>{bid_amount:d}<end> points for {item} has put you in the lead. " \
               f"You have <highlight>{points_available - bid_amount:d}<end> points left for bidding."

    def end(self):
        self.cancel_job()
        self.is_running = False

        blob = ""
        t = int(time.time())
        # noinspection SqlInsertValues
        sql = "INSERT INTO auction_log (" \
              "item_ref, item_name, winner_id, auctioneer_id, " \
              "time, winning_bid, second_highest_bid) " \
              "VALUES (?,?,?,?,?,?,?)"
        items = ""
        for i, item in self.items.items():
            winning_bid = self.winning_bids.get(i, None)
            if winning_bid:
                self.db.exec(sql, [item, item, winning_bid.sender.char_id, self.auctioneer.char_id, t,
                                   winning_bid.current_amount, 0])
                items += f"{i}. <highlight>{item}</highlight> (<highlight>{winning_bid.sender.name} » " \
                         f"{winning_bid.current_amount}P</highlight>)\n"
                blob += "%d. %s, won by <highlight>%s<end> with <green>%d<end> points\n" % (
                    i, item, winning_bid.sender.name, winning_bid.current_amount)
                main_id = self.account_service.get_main(winning_bid.sender.char_id).char_id
                self.account_service.rem_pts(main_id,
                                             winning_bid.current_amount,
                                             f"won {item}",
                                             self.auctioneer.char_id)
            else:
                items += f"{i}. <highlight>{item}</highlight> (<red>FFA</red>)\n"
                blob += "%d. %s, no bids made\n" % (i, item)

        result_blob = self.text.paginate_single(ChatBlob("The Auction results", blob))
        msg = self.format_auction(f"{items}"
                                  f"» {result_blob}")
        self.spam_raid_message(msg)

    def remove_bid(self, char_id, item_id):
        pass

    def spam_raid_message(self, message):
        self.bot.send_private_channel_message(message, fire_outgoing_event=False)

    def get_auction_list(self):
        blob = ""
        for i, item in self.items.items():
            if item[:2] == "<a" and item[-4:] == "</a>":
                item = re.findall(r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)",
                                  item)
            img = ""
            if isinstance(item[0], tuple):
                imgid = self.db.query_single("SELECT icon from aodb where lowid=? and highid=?",
                                             [item[0][2], item[0][3]])
                if imgid is not None:
                    img = self.text.make_image(imgid.get("icon")) + " - "
                blob += "%d. %s%s" % (i, img, item[0][0])

            else:
                img = ""
                blob += f"{i:d}. {img}{item}"
            winning_bid = self.winning_bids.get(i, None)
            if winning_bid:
                blob += f"<br><tab><highlight>{winning_bid.sender.name}<end> has the " \
                        f"winning bid of <highlight>{winning_bid.current_amount:d}<end>\n["
                blob += self.text.make_chatcmd("bid +1", f"/tell <myname> bid {winning_bid.current_amount + 1} {i}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid +10", f"/tell <myname> bid {winning_bid.current_amount + 10} {i}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid +25", f"/tell <myname> bid {winning_bid.current_amount + 25} {i}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid +100", f"/tell <myname> bid {winning_bid.current_amount + 100} {i}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid +500", f"/tell <myname> bid {winning_bid.current_amount + 500} {i}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid Max", f"/tell <myname> bid all {i}")
                blob += "]\n────────────────────────────────\n"
            else:
                blob += "<br><tab><green>No bidders<end>\n["
                blob += self.text.make_chatcmd("bid 1", f"/tell <myname> bid 1 {i:d}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid 10", f"/tell <myname> bid 10 {i:d}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid 25", f"/tell <myname> bid 25 {i:d}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid 100", f"/tell <myname> bid 100 {i:d}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid 500", f"/tell <myname> bid 500 {i:d}")
                blob += "]<tab>["
                blob += self.text.make_chatcmd("bid Max", f"/tell <myname> bid all {i:d}")
                blob += "]\n────────────────────────────────\n"

        return ChatBlob(f"The Auction list ({len(self.items):d})", blob)

    def get_points_used(self, main_id, item_index):
        points_used = 0
        for index, bid in self.winning_bids.items():
            if index != item_index and bid.account.char_id == main_id:
                points_used += bid.max_amount

        return points_used

    def auction_announce(self, _):
        time_left = self.time_left()
        if time_left <= 0:
            self.end()
            return

        item_count = len(self.items)
        if item_count > 1:
            items = ""
            for i, item in self.items.items():
                winning = ""
                if self.winning_bids.get(i, None):
                    winning = f"(<highlight>{self.winning_bids[i].sender.name} » " \
                              f"{self.winning_bids[i].current_amount}P</highlight>)"
                items += f"{i}. {item} {winning}\n"

            msg = self.format_auction(f"{items}\n"
                                      f"» {self.text.paginate_single(self.get_auction_list())}\n"
                                      f"<highlight>{time_left}</highlight> seconds left")

        else:
            item_index = list(self.items.keys())[0]
            item = self.items[item_index]
            winning_bid = self.winning_bids.get(item_index, None)

            if winning_bid:
                winner = f"<highlight>{winning_bid.sender.name}<end> now holds the " \
                         f"leading bid with a bid of <highlight>{winning_bid.current_amount:d}<end>."
            else:
                winner = "No bids made."
            msg = self.format_auction(f"1. {item}\n"
                                      f"{winner}\n"
                                      f"» {self.text.paginate_single(self.get_auction_list())}\n"
                                      f"<highlight>{time_left}</highlight> seconds left")

        self.spam_raid_message(msg)
        self.create_next_announce_job()

    def time_left(self):
        t = int(time.time())
        time_left = self.auction_end_time - t
        if time_left < 0:
            time_left = 0

        return time_left

    def create_next_announce_job(self):
        t = int(time.time())
        time_remaining = self.auction_end_time - t
        mod = time_remaining % self.announce_interval
        if mod == 0:
            mod = self.announce_interval

        next_job_t = t + mod
        self.job_id = self.job_scheduler.scheduled_job(self.auction_announce, next_job_t)

    def cancel_job(self):
        if self.job_id:
            self.job_scheduler.cancel_job(self.job_id)
            self.job_id = None

    def format_auction(self, message):
        return f"<yellow>\n" \
               f"────────[ Auction ]────────\n" \
               f"{message}\n" \
               f"────────[ Auction ]────────</yellow>"
