import re
import time
from threading import Thread

from core.bot_status import BotStatus
from core.buddy_service import BuddyService
from core.command_alias_service import CommandAliasService
from core.command_param_types import Int, Any, Const, Options
from core.db import DB
from core.decorators import instance, event, command
from core.dict_object import DictObject
from core.fifo_queue import FifoQueue
from core.igncore import IgnCore
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.private_channel_service import PrivateChannelService
from core.public_channel_service import PublicChannelService
from core.setting_service import SettingService
from core.text import Text
from core.util import Util
from modules.core.accounting.services.account_service import AccountService
from modules.standard.online.online_display import OnlineDisplay


class User:
    def __init__(self, char_id, loggedin=True):
        self.char_id = char_id
        self.logged = loggedin


@instance()
class OnlineController:
    def __init__(self):
        self.afk_list = {}
        self.afk_regex = re.compile("^(afk|brb) ?(.*)$", re.IGNORECASE)
        self.awaiting_data = FifoQueue()

    def inject(self, registry):
        self.logger = Logger(__name__)
        self.bot: IgnCore = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.util: Util = registry.get_instance("util")
        self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.priv: PrivateChannelService = registry.get_instance("private_channel_service")
        self.online_display: OnlineDisplay = OnlineDisplay(self.text, self.util, self.db, self.afk_list)
        self.account_service: AccountService = registry.get_instance("account_service")
        self.character_service: CharacterService = registry.get_instance("character_service")

    def pre_start(self):
        self.db.exec("DROP TABLE IF EXISTS online")
        self.db.shared.exec(
            "CREATE TABLE IF NOT EXISTS online (char_id BIGINT NOT NULL primary key, "
            "channel varchar(32) not null default 'notify', "
            "bot BIGINT not null, "
            "index bot(bot)) ENGINE MEMORY")
        self.db.create_view("online")
        self.command_alias_service.add_alias("o", "online")

    @event(event_type="connect", description="Start online watcher")
    def startup(self, _, _1):
        self.db.exec("DELETE from online where bot=?", [self.bot.get_char_id()])

        self.watchdog = Thread(name="watchdog", target=self.buddy_handler, daemon=True)
        self.watchdog.start()

    @event(event_type="private_channel_joined", description="Change online channel", is_hidden=True)
    def priv_join(self, _, event_data):
        self.db.exec("insert ignore into online(channel, char_id, bot) VALUES (?, ?, ?)",
                     [self.bot.name, event_data.char_id, self.bot.get_char_id()])

    @event(event_type="private_channel_left", description="Change online channel", is_hidden=True)
    def priv_leave(self, _, event_data):
        self.db.exec("delete from online where char_id = ? and bot = ? and channel=?",
                     [event_data.char_id, self.bot.get_char_id(), self.bot.name])
        for alt in self.account_service.get_alts(event_data.char_id):
            self.afk_list.pop(alt.char_id, None)

    @event(event_type="member_logon", description="declare players as online")
    def logon(self, _, event_data):
        self.awaiting_data.put([event_data, 'notify', True])

    @event(event_type="member_logoff", description="declare players as offline")
    def logoff(self, _, event_data):
        if self.bot.is_ready():
            self.awaiting_data.put([event_data, 'notify', False])

    def buddy_handler(self):
        while self.bot.status != BotStatus.SHUTDOWN:
            data, channel, logged = self.awaiting_data.get()
            buddy = (self.buddy_service.get_buddy(data.packet.char_id) or {}).get("types", [])
            if ("org_member" in buddy) or ("member" in buddy):
                if logged:
                    self.db.exec("INSERT IGNORE INTO online VALUES(?, ?, ?)",
                                 [data.packet.char_id, channel, self.bot.get_char_id()])
                    self.db.exec("UPDATE account set last_seen=? where char_id=?", [time.time(), data.packet.char_id])
                else:
                    self.db.exec("DELETE FROM online where char_id=? and bot=?",
                                 [data.packet.char_id, self.bot.get_char_id()])
                    self.db.exec("UPDATE account set last_seen=? where char_id=?", [time.time(), data.packet.char_id])
                    for alt in self.account_service.get_alts(data.packet.char_id):
                        self.afk_list.pop(alt.char_id, None)

    @command(command="online", params=[Const('all', is_optional=True),
                                       Int("min_level", is_optional=True),
                                       Any("profession", is_optional=True)],
             description="shows online players",
             access_level="member")
    def online_all_cmd(self, _, const_all, min_level, profession):
        query = ""
        params = [self.bot.name, self.bot.get_char_id()]
        if const_all:
            query += "and channel_id IN (1, 2, 3) "
        else:
            query += "and channel_id IN (1, 2) "
        if min_level:
            query += "and p.level >= ? "
            params.append(min_level)
        if profession:
            query += "and p.profession = ? "
            params.append(self.util.get_profession(profession))
        blob = self.online_display.format_by_channel_main(query, params)
        _.reply(self.online_display.format_blob(blob))

    @command(command="count",
             params=[Options(["org", "prof", "tl"], is_optional=True), Any("filter", is_optional=True)],
             description="Counts online players",
             access_level="member")
    def count(self, _, option, filters):
        query = "and channel_id IN (1, 2) "
        params = [self.bot.name, self.bot.get_char_id()]
        if not option:
            option = "prof"
        output = ""
        if option == "prof":
             output = self.online_display.count_prof(query, params, filters)
        elif option == "org":
            output = self.online_display.count_org(query, params, filters)
        elif option == "tl":
            if filters:
                try:
                    filters = int(filters)
                except ValueError:
                    return f"Invalid Title level: {filters}"
            output = self.online_display.count_tl(query, params, filters)
        return output if output != "" else "Nobody is in my private channel, sorry..."

    @event(PrivateChannelService.PRIVATE_CHANNEL_MESSAGE_EVENT, "Check for afk messages in private channel")
    def afk_check_private_channel_event(self, event_type, event_data):
        self.afk_check(event_data.char_id, event_data.message, lambda msg: self.bot.send_private_channel_message(msg))

    @event(PublicChannelService.ORG_CHANNEL_MESSAGE_EVENT, "Check for afk messages in org channel")
    def afk_check_org_channel_event(self, event_type, event_data):
        self.afk_check(event_data.char_id, event_data.message, lambda msg: self.bot.send_org_message(msg))

    def afk_check(self, char_id, message, channel_reply):
        matches = self.afk_regex.search(message)
        if matches:
            char_name = self.character_service.resolve_char_to_name(char_id)
            channel_reply(f"<highlight>{char_name}</highlight> is now afk.")
            alts = self.account_service.get_alts(char_id)
            for alt in alts:
                self.afk_list[alt.char_id] = DictObject({"message": message, "time": time.time()})
            # Dirty fix for players without account
            self.afk_list[char_id] = DictObject({"message": message, "time": time.time()})
        elif char_id in self.afk_list.keys():
            # TODO handle multiple rows

            alts = self.account_service.get_alts(char_id)
            data = None
            for alt in alts:
                out = self.afk_list.pop(alt.char_id)
                if data:
                    continue
                data = out
            if not alts:
                data = self.afk_list.pop(char_id)
            char_name = self.character_service.resolve_char_to_name(char_id)
            time_string = self.util.time_to_readable(int(time.time()) - data.time)
            channel_reply(f"<highlight>{char_name}</highlight> is back after {time_string}.")
