import time
from typing import List

from core.buddy_service import BuddyService
from core.db import DB, SqlException
from core.decorators import instance, timerevent, event
from core.dict_object import DictObject
from core.igncore import IgnCore
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.setting_service import SettingService
from core.setting_types import BooleanSettingType
from core.text import Text
from modules.core.accounting.services.access_service import AccessService


# noinspection SqlCaseVsIf,SqlResolve,PyMethodMayBeStatic


@instance()
class AccountService:
    MAIN_CHANGED_EVENT_TYPE = "main_changed"
    MEMBER_LOGON = "member_logon"
    MEMBER_LOGOFF = "member_logoff"

    def __init__(self):
        self.logger = Logger("Accounting")

    # noinspection PyAttributeOutsideInit
    def inject(self, registry):
        self.bot: IgnCore = registry.get_instance("bot")
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.util = registry.get_instance("util")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.access_service: AccessService = registry.get_instance("access_service")
        self.db: DB = registry.get_instance("db")
        self.event_service = registry.get_instance("event_service")
        self.pork: PorkService = registry.get_instance("pork_service")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.text: Text = registry.get_instance("text")
        self.leader = registry.get_instance("leader_controller", is_optional=True)

    def pre_start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS account ("
                     "char_id int(11) NOT NULL,"
                     "points int(11) NOT NULL default 0,"
                     "member int(2) NOT NULL DEFAULT 0,"
                     "created int(255) NOT NULL DEFAULT 0,"
                     "disabled int(2) NOT NULL DEFAULT 0,"
                     "main int(11) NOT NULL DEFAULT char_id,"
                     "subtile_spam int(2) NOT NULL DEFAULT 0,"
                     "auto_invite int(2) NOT NULL DEFAULT 0,"
                     "raid_invite int(2) NOT NULL DEFAULT 1,"
                     "raid_spam int(2) NOT NULL DEFAULT 1,"
                     "news_spam int(2) NOT NULL DEFAULT 1,"
                     "discord_id bigint(30) UNSIGNED NULL DEFAULT 0,"
                     "discord_handle varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
                     "discord_invite text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
                     "discord_joined int(2) NOT NULL DEFAULT 0,"
                     "logon text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
                     "logoff text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
                     "last_seen int(11) NOT NULL DEFAULT 0,"
                     "last_updated int(11) NOT NULL DEFAULT 0,"
                     "PRIMARY KEY (char_id) USING BTREE,"
                     "INDEX char_id(char_id) USING BTREE,"
                     "INDEX main(main) USING BTREE,"
                     "INDEX disabled(disabled) USING BTREE,"
                     "INDEX char_id_2(char_id, disabled) USING BTREE,"
                     "INDEX discord_id(discord_id) USING BTREE)")

        self.db.exec(
            "CREATE TABLE IF NOT EXISTS raid_log ("
            "raid_id INT PRIMARY KEY AUTO_INCREMENT, "
            "raid_name VARCHAR(255) NOT NULL, "
            "started_by BIGINT NOT NULL, "
            "raid_start INT NOT NULL, "
            "raid_end INT NOT NULL)")

        self.db.exec(
            "CREATE TABLE IF NOT EXISTS raid_log_participants ("
            "raid_id INT NOT NULL, "
            "raider_id BIGINT NOT NULL, "
            "accumulated_points INT DEFAULT 0, "
            "left_raid INT, "
            "was_kicked INT, "
            "was_kicked_reason VARCHAR(500))")

        self.db.exec("CREATE TABLE IF NOT EXISTS points_presets ("
                     "preset_id INT PRIMARY KEY AUTO_INCREMENT, "
                     "name VARCHAR(50) NOT NULL, "
                     "points INT DEFAULT 1, "
                     "UNIQUE(name));")
        self.db.exec("CREATE TABLE IF NOT EXISTS pending_accounts ("
                     "main INT(11) NOT NULL, "
                     "alt INT(11) NOT NULL, "
                     "reason TEXT DEFAULT '', "
                     "recommender INT(11) NOT NULL , "
                     "time int(255) NOT NULL,"
                     "answered int(2) default 0 not null);")

        self.db.exec("CREATE TABLE IF NOT EXISTS account_log ("
                     "log_id INTEGER PRIMARY KEY AUTO_INCREMENT, "
                     "char_id BIGINT NOT NULL, "
                     "type VARCHAR(32), "
                     "delta INT NOT NULL DEFAULT 0, "
                     "leader_id BIGINT NOT NULL, "
                     "reason VARCHAR(255), "
                     "created_at INTEGER NOT NULL, "
                     "INDEX char_id (char_id), "
                     "INDEX leader (leader_id), "
                     "INDEX created(created_at));")

        self.db.exec("CREATE TABLE IF NOT EXISTS ranks ("
                     "main int(11) NOT NULL, "
                     "`rank` varchar(32) not null, "
                     "INDEX main(main, `rank`) USING BTREE)")

        self.db.exec("CREATE TABLE IF NOT EXISTS org_bots(char_id int primary key not null, org_id int not null)")

        self.event_service.register_event_type(self.MAIN_CHANGED_EVENT_TYPE)
        self.event_service.register_event_type(self.MEMBER_LOGON)
        self.event_service.register_event_type(self.MEMBER_LOGOFF)
        self.setting_service.register(self.module_name, "is_alliance_bot", False, BooleanSettingType(),
                                      "Is this bot used as an alliancebot")
        self.setting_service.register(self.module_name, "alt_verification", False, BooleanSettingType(),
                                      "alts require admin verification")
        # Default preferences
        self.setting_service.register(self.module_name, "pref_autoinvite", False, BooleanSettingType(),
                                      "Default Value for the auto invite preference")
        self.setting_service.register(self.module_name, "pref_raidinvite", True, BooleanSettingType(),
                                      "Default Value for the raid invite (Massinvite) preference")
        self.setting_service.register(self.module_name, "pref_raidspam", True, BooleanSettingType(),
                                      "Default Value for the raid spam (Mass Message) preference")
        self.setting_service.register(self.module_name, "pref_newsspam", True, BooleanSettingType(),
                                      "Default Value for the news spam (News on logon) preference")
        v = self.setting_service.get_value("pref_raidspam")

        # Ranks
        if self.setting_service.get_value("is_alliance_bot") == "1":
            self.access_service.register_access_level("officer", 80, self.check_officer)
            self.access_service.register_access_level("general", 70, self.check_general)
            self.access_service.register_access_level("president", 60, self.check_president)
            self.access_service.register_access_level("council", 40, self.check_council)
        self.access_service.register_access_level("member", 90, self.check_member)
        self.access_service.register_access_level("leader", 50, self.check_leader)
        self.access_service.register_access_level("moderator", 30, self.check_moderator)
        self.access_service.register_access_level("admin", 20, self.check_admin)
        self.access_service.register_access_level("superadmin", 10, self.check_superadmin)

    def get_main(self, char_id) -> DictObject:
        alts = self.get_alts(char_id)
        return alts[0] if alts else self.db.query_single("select * from player where char_id=?", [char_id])

    def get_alts(self, char_id) -> List[DictObject]:
        return self.db.query(
            "SELECT p.*, a.* from account a left join player p on a.char_id = p.char_id where "
            "main=(SELECT main from account where char_id=?) ORDER BY a.main = a.char_id desc, p.level desc, p.name DESC",
            [char_id])

    acc_cache = {}

    def get_account(self, char_id) -> DictObject:
        # The way the caching is handled does not come without side effects:
        # For example, after purging an account,
        # you could still update it (i.e. add points, alts, ...)
        # which would result in log entries,
        # without an associated account
        if char_id not in self.acc_cache.keys():
            out = self.db.query_single(
                "SELECT a.*, p.* from account a left join player p on a.char_id = p.char_id where "
                "a.char_id=(SELECT main from account where char_id=?) "
                "and a.char_id not in (SELECT char_id from org_bots)",
                [char_id]) or DictObject({})

            self.acc_cache[char_id] = out
            self.bot.job_scheduler.delayed_job(lambda x: self.acc_cache.pop(char_id), 5)
        else:
            out = self.acc_cache.get(char_id)
        return out

    def get_entry(self, char_id) -> DictObject:
        return self.db.query_single("SELECT a.*, p.* from account a left join player p on a.char_id = p.char_id "
                                    "where a.char_id=? and a.char_id not in (SELECT char_id from org_bots)",
                                    [char_id]) or DictObject({})

    def set_logon(self, char_id, logon="") -> DictObject:
        return self.db.exec("UPDATE account SET logon=? where char_id=?",
                            [logon, char_id])

    def set_logoff(self, char_id, logoff="") -> DictObject:
        return self.db.exec("UPDATE account SET logoff=? where char_id=?",
                            [logoff, char_id])

    def add_pending_alt(self, main, alt) -> [str, bool]:
        data = self.check_alt(alt)
        if data:
            return data
        acc = self.get_account(main)
        if acc:
            main = acc.main
            self.pork.load_character_info(alt, skeleton_only=True)
            self.db.exec("INSERT INTO pending_accounts(main, alt, recommender, time) VALUES(?, ?, ?, ?)",
                         [acc.main, alt, acc.main, time.time()])
            self.add_log(main, 'system',
                         f'Requested to add <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
                         f' as an alt',
                         acc.main)
            return ["success", True]

    def add_pending_account(self, main, user, reason) -> [str, bool]:
        data = self.check_alt(user)
        if data:
            return data
        acc = self.get_account(main)
        if acc:
            main = acc.main
            self.pork.load_character_info(user, skeleton_only=True)
            self.db.exec("INSERT INTO pending_accounts(main, alt, recommender, reason, time) VALUES(?, ?, ?, ?, ?)",
                         [user, user, acc.main, reason, time.time()])
            self.add_log(main, 'system',
                         f'Recommended <highlight>{self.character_service.resolve_char_to_name(user)}</highlight>'
                         f' as raider, reason: {reason}',
                         acc.main)
            return ["success", True]

    def check_alt(self, alt):
        alt_alts = self.get_alts(alt) or []
        if len(alt_alts) > 1:
            for alts in alt_alts:
                if alts.char_id == alt:
                    if alts.main != alts.char_id:
                        return ["another_main", False]
        alt_main = self.get_account(alt)
        if alt_main:
            if alt_main.main != alt:
                return ["already_main", False]
        if self.is_pending(alt):
            return ['pending_alt', False]

    def is_pending(self, char_id) -> DictObject or bool:
        return self.db.query_single("SELECT * from pending_accounts where alt=? and answered = 0", [char_id]) or False

    def add_alt(self, sender, alt, approve=False) -> [str, bool]:
        alt_alts = self.get_alts(alt) or []
        if alt_alts:
            if len(alt_alts) > 1:
                return ["another_main", False]
            if alt_main := alt_alts[0]:
                if alt_main.main != alt:
                    return ["already_main", False]
        acc = self.get_account(sender)
        if acc:
            main = acc.main
            self.pork.load_character_info(alt, skeleton_only=True)
            member = -1 if acc.disabled == 1 or acc.member == -1 else 0
            if self.db.exec("INSERT IGNORE INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?)",
                            [alt, main, time.time(), member]) == 0:
                self.db.exec("UPDATE account set main=?, created=? where char_id=?", [main, time.time(), alt])
            self.event_service.fire_event(self.MAIN_CHANGED_EVENT_TYPE,
                                          DictObject({"old_main_id": alt, "new_main_id": main}))
            if not approve:
                self.add_log(sender, 'system',
                             f'Added <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
                             f' as an alt',
                             acc.main)
            # Only add as member if he's neither banned, nor unregistered
            if member != -1:
                self.buddy_service.add_buddy(alt, "member")
            return ["success", True]
        self.db.exec("INSERT IGNORE INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?)",
                     [sender, sender, time.time(), -1])
        self.db.exec("INSERT INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?) ON DUPLICATE KEY UPDATE "
                     "main=VALUE(main), discord_id=0, discord_handle='', discord_invite='', discord_joined=0",
                     [alt, sender, time.time(), -1])
        if not approve:
            self.add_log(sender, 'system',
                         f'Added <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight> as an alt',
                         sender)
        # Main does not exist, do not add as member
        # self.buddy_service.add_buddy(alt, "member")
        return ["success", True]

    def remove_alt(self, sender, alt) -> [str, bool]:
        if not self.db.query(
                "SELECT * FROM account where char_id=? and main=(SELECT main from account where char_id=?)",
                [alt, sender]):
            return ["not_alt", False]
        alts = self.get_alts(sender)
        for row in alts:
            if row.char_id == alt and row.main == alt:
                return ["remove_main", False]
            if row.char_id == alt and row.main == alts[0].char_id:
                self.db.exec("UPDATE account set main=? where char_id=?", [alt, alt])
                self.add_log(sender, 'system',
                             f'Removed <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
                             f' from his alts.',
                             sender)
                return ["success", True]
        else:
            return [""]

    def set_as_main(self, sender) -> [str, bool]:
        alts = self.get_alts(sender)

        if len(alts) < 2:
            return ["not_an_alt", False]
        elif alts[0].char_id == sender:
            return ["already_main", False]
        else:
            self.db.exec("UPDATE account set main=? where main=(SELECT main from account where char_id=?)",
                         [sender, sender])
            self.event_service.fire_event(self.MAIN_CHANGED_EVENT_TYPE,
                                          DictObject({"old_main_id": alts[0].char_id,
                                                      "new_main_id": sender}))
            return ["success", True]

    def get_orgs(self) -> list:
        # With the broader usage of the "simple_checks" (Check for ban, org membership, ....)
        # We might need to cache the orgs matching the filters.
        # As these dont change as frequently as the account [if at all],
        # Saving them for longer should be safe.
        if not hasattr(self, "orgs"):
            try:
                def del_orgs(_):
                    del self.orgs

                self.orgs = [x["org_id"] for x in self.db.query("SELECT * from orgs", [])]
                self.bot.job_scheduler.delayed_job(del_orgs, 60)
                return self.orgs
            # The table "orgs" does not exist, which means the bot is not running the onlinebot modules.
            # Cache lifetime is infinity, as the bot usually wont leave its org while its online.
            except SqlException:
                self.orgs = [self.bot.public_channel_service.org_id]
                return self.orgs
        return self.orgs

    def get_org_names(self) -> list:
        if not hasattr(self, "org_names"):
            try:
                def del_orgs(_):
                    del self.org_names

                self.orgs = [x["org_name"] for x in self.db.query("SELECT * from orgs o "
                                                                  "LEFT JOIN all_orgs a on o.org_id = ao.org_id", [])]
                self.bot.job_scheduler.delayed_job(del_orgs, 60)
                return self.org_names
            except SqlException:
                self.org_names = [self.bot.public_channel_service.org_name]
                return self.org_names
        return self.org_names

    def create_users(self, users, disable=False) -> int:
        # Default preferences
        pref_autoinvite = self.setting_service.get_value("pref_autoinvite")
        pref_raidinvite = self.setting_service.get_value("pref_raidinvite")
        pref_raidspam = self.setting_service.get_value("pref_raidspam")
        pref_newsspam = self.setting_service.get_value("pref_newsspam")
        if type(users) == list and len(users) > 0:
            with self.db.pool.get_connection() as conn:
                with conn.cursor() as cur:
                    if disable:
                        cur.executemany(
                            "INSERT IGNORE INTO account(char_id, main, member, disabled, last_updated, created, "
                            "auto_invite, raid_invite, raid_spam, news_spam) "
                            f"VALUES(?, ?, ?, 1, ?, ?, {pref_autoinvite}, {pref_raidinvite}, "
                            f"{pref_raidspam}, {pref_newsspam}) ON DUPLICATE KEY UPDATE "
                            "member=VALUE(member), last_updated=VALUE(last_updated), disabled=1",
                            users)
                        return cur.rowcount
                    cur.executemany(
                        "INSERT IGNORE INTO account(char_id, main, member, last_updated, "
                        "created, auto_invite, raid_invite, raid_spam, news_spam) "
                        f"VALUES(?, ?, ?, ?, ?, {pref_autoinvite}, {pref_raidinvite}, "
                        f"{pref_raidspam}, {pref_newsspam}) ON DUPLICATE KEY UPDATE "
                        "member=VALUE(member), last_updated=VALUE(last_updated)",
                        users)
                    return cur.rowcount

    def account_disable(self, char_id) -> bool:
        return True if self.db.exec(
            "UPDATE account set disabled=1 where main in (SELECT main from account where char_id=?)",
            [char_id]) else False

    def account_enable(self, char_id) -> bool:
        return True if self.db.exec(
            "UPDATE account set disabled=0 where main in (SELECT main from account where char_id=?)",
            [char_id]) else False

    def account_add_member(self, char_id) -> bool:
        return True if self.db.exec(
            "UPDATE account set member=0 where main in (SELECT main from account where char_id=?)",
            [char_id]) else False

    def remove_members(self, users) -> None:
        if type(users) == list and len(users) > 0:
            with self.db.pool.get_connection() as conn:
                with conn.cursor() as cur:
                    cur.executemany("UPDATE account set member=-1 where char_id=? and ?", users)

    def check_member(self, char_id) -> bool:
        account = self.get_account(char_id) or {}
        if account.get('disabled', 1) == 1:
            return False
        if account.get('member', -1) == -1:
            return False
        if self.setting_service.get_value("is_alliance_bot") == "0":
            return True
        if account.get('org_id', 0) not in self.get_orgs():
            return False
        return True

    def check_president(self, char_id) -> bool:
        if self.setting_service.get_value("is_alliance_bot") == "0":
            return False
        account = self.get_account(char_id) or {}
        if not self.simple_checks(account):
            return False
        if account.get('org_rank_id', -1) == 0:
            return True
        return False

    def check_general(self, char_id) -> bool:
        if self.setting_service.get_value("is_alliance_bot") == "0":
            return False
        account = self.get_account(char_id) or {}
        if not self.simple_checks(account):
            return False
        if self.get_rank_count(char_id) != 7:
            return False
        if account.get('org_rank_id', -1) <= 1:
            return True
        return False

    def check_officer(self, char_id) -> bool:
        if self.setting_service.get_value("is_alliance_bot") == "0":
            return False
        account = self.get_account(char_id) or {}
        if not self.simple_checks(account):
            return False
        if self.get_rank_count(char_id) != 7:
            return False
        if account.get('org_rank_id', -1) <= 2:
            return True
        return False

    def check_council(self, char_id) -> bool:
        if self.setting_service.get_value("is_alliance_bot") == "0":
            return False
        if self.simple_checks(self.get_account(char_id)):
            return "council" in self.get_ranks(char_id)

    def check_leader(self, char_id) -> bool:
        if self.leader and self.leader.leader:
            if self.get_account(self.leader.leader.char_id).main == char_id:
                return True
        if self.simple_checks(self.get_account(char_id)):
            return "leader" in self.get_ranks(char_id)

    def check_moderator(self, char_id) -> bool:
        if self.simple_checks(self.get_account(char_id)):
            return "moderator" in self.get_ranks(char_id)

    def check_admin(self, char_id) -> bool:
        if self.simple_checks(self.get_account(char_id)):
            return "admin" in self.get_ranks(char_id)

    def check_superadmin(self, char_id) -> int:
        return char_id in self.bot.superadmin

    def get_ranks(self, char_id) -> List[DictObject]:
        return [x["rank"] for x in
                self.db.query(" SELECT rank FROM ranks where main = (SELECT main from account where char_id=? limit 1)",
                              [char_id])] or []

    def get_all_admins(self) -> List[DictObject]:
        return self.get_by_group("admin")

    def get_all_moderators(self) -> List[DictObject]:
        return self.get_by_group("moderator")

    def get_all_councils(self) -> List[DictObject]:
        return self.get_by_group("council")

    def get_all_leaders(self) -> List[DictObject]:
        return self.get_by_group("leader")

    def get_all_members(self, online_only=False) -> List[DictObject]:
        return self.db.query(
            f"SELECT p.*, a.*, CASE when o.char_id IS NOT NULL then 1 ELSE 0 end as online from account a "
            f"LEFT JOIN player p ON a.char_id=p.char_id "
            f"LEFT JOIN (SELECT * FROM online WHERE bot=?) o ON a.char_id=o.char_id "
            f"WHERE a.char_id NOT IN (SELECT char_id from org_bots) "
            f"and a.disabled = 0 {'and o.char_id is not null' if online_only else ''} "
            f"group by a.char_id order by a.main, a.main=a.char_id desc, p.level desc, p.ai_level desc",
            [self.bot.get_char_id()])

    def get_by_group(self, group) -> List[DictObject]:
        return self.db.query("""SELECT  CASE when rank = 'admin' then 0
                                when rank = 'moderator' then 1
                                when rank = 'council' then 2
                                when rank = 'leader' then 3
                                ELSE 99 END AS rank_id, 
                                CASE when o.char_id IS NOT NULL 
                                then 1 
                                ELSE 0 end as online, 
                                p.*, a.* FROM ranks r 
                                LEFT JOIN account a ON r.main=a.main
                                LEFT JOIN player p ON a.char_id=p.char_id
                                LEFT JOIN online o ON a.char_id=o.char_id
                                WHERE r.rank=?
                                ORDER BY rank_id, a.main desc, a.main=a.char_id DESC, p.name """, [group])

    def get_group_tag(self, string) -> str or bool:
        string = string.lower()
        if string in ["adm", "admins", "admin", "administrator", "administrators"]:
            return "admin"
        elif string in ["mod", "mods", "moderator", "moderators"]:
            return "moderator"
        elif string in ["cnc", "council", "councilors"]:
            return "council"
        elif string in ["rl", "leader", "leaders", "raidleader", "raidleaders", "rls"]:
            return "leader"
        elif string in ["all", "full", "everyone"]:
            return "all"
        else:
            return False

    def add_log(self, char_id, log_type, message, leader, delta=0) -> None:
        main = self.get_main(char_id).char_id
        self.db.exec("INSERT INTO account_log(char_id, type, delta, leader_id, reason, created_at) "
                     "VALUES (?, ?, ?, ?, ?, ?)",
                     [main, log_type, delta, leader, message, time.time()])

    def get_logs(self, user=None, log_type=None, limit=25) -> List[DictObject]:
        if not user:
            if not log_type:
                return self.db.query("SELECT * FROM account_log "
                                     "where type != 'admin' order by log_id desc LIMIT ? ", [limit])
            else:
                return self.db.query("SELECT * FROM account_log "
                                     "where type=? order by log_id desc LIMIT ?", [log_type, limit])
        else:
            if not log_type:
                return self.db.query("SELECT * FROM account_log "
                                     "where char_id=? and type != 'admin' order by log_id desc LIMIT ? ", [user, limit])
            else:
                return self.db.query("SELECT * FROM account_log "
                                     "where char_id=? and type=? order by log_id desc LIMIT ?", [user, log_type, limit])

    def get_log_by_id(self, log_id) -> DictObject:
        return self.db.query_single("SELECT * FROM account_log where log_id=? ", [log_id])

    def format_entry(self, entry) -> str:
        msg = f"<grey>[{self.util.format_datetime(entry.created_at)}] " \
              f"[{self.text.make_tellcmd('D', f'account log id {entry.log_id}')}]</grey> "
        entry.type = entry.type.lower()
        if entry.type == "points":
            entry.reason = entry.reason.replace('"', "'")
            msg += f"<white>{f'<green>+{entry.delta}P</green>' if entry.delta >= 0 else f'<red>{entry.delta}P</red>'}" \
                   f" by <notice>{self.character_service.resolve_char_to_name(entry.leader_id)}</notice>" \
                   f" for <notice>{entry.reason}</notice></white>"
        elif entry.type == "loot":
            msg += f"<white>Won Item: {entry.reason}</white>"
        elif entry.type == "raid":
            msg += f"<white>{entry.reason}</white>"
        elif entry.type == "public":
            msg += f"<white>{entry.reason}</white>"
        elif entry.type == "admin":
            msg += f"<white>Notice from " \
                   f"<notice>{self.character_service.resolve_char_to_name(entry.leader_id)}</notice>: " \
                   f"{entry.reason}</white>"
        elif entry.type == "system":
            msg += f"<yellow>{entry.reason}</yellow>"

        return msg + "\n"

    def add_pts(self, char_id, points, reason, leader) -> None:
        self.db.exec("UPDATE account set points = points+? where char_id=(select main from account where char_id=?)",
                     [points, char_id])
        self.add_log(char_id, 'points', reason, leader, delta=points)

    def rem_pts(self, char_id, points, reason, leader) -> None:
        if points < 0:
            points = -points
        self.db.exec("UPDATE account set points = points-? where char_id=(select main from account where char_id=?)",
                     [points, char_id])
        if account := self.acc_cache.get(char_id, None):
            account.points -= points
        self.add_log(char_id, 'points', reason, leader, delta=-points)

    def add_rank(self, char_id, rank) -> int:
        return self.db.exec("INSERT INTO ranks(main, `rank`) VALUES(?, ?)", [char_id, rank])

    def del_rank(self, char_id, rank) -> int:
        return self.db.exec("DELETE FROM ranks where main=? and rank =?", [char_id, rank])

    @timerevent(budatime="12h", description="Delete dead ranks")
    def clear_raid(self, _, _1):
        count = self.db.exec("DELETE FROM ranks where main not in (SELECT main from account)")
        if count > 0:
            self.logger.info(f"Purged {count} dead ranks")
        count = self.db.exec(
            "delete from ranks where main in(select r.main from ranks r "
            "left join account a on r.main=a.char_id "
            "where a.member=-1)")
        if count > 0:
            self.logger.info(f"Purged {count} ranks; caused by: main no longer in the alliance")

    @event(event_type="main_changed", description="Fix ranks")
    def fix_ranks(self, _, data):
        self.db.exec("UPDATE ranks set main=? where main=?", [data.new_main_id, data.old_main_id])
        self.db.exec("UPDATE account_log set char_id=? where char_id=?", [data.new_main_id, data.old_main_id])
        self.db.exec("UPDATE account_log set leader_id=? where leader_id=?", [data.new_main_id, data.old_main_id])

    @event(event_type="buddy_logon", description="Member logon manager")
    def member_login(self, _, data):
        buddy = self.buddy_service.get_buddy(data.char_id)
        if buddy:
            if self.bot.is_ready():
                if "member" in buddy['types'] or "org_member" in buddy['types']:
                    if main := self.get_account(data.char_id):
                        self.event_service.fire_event(self.MEMBER_LOGON, DictObject({'account': main, 'packet': data}))
            else:
                if main := self.get_account(data.char_id):
                    self.event_service.fire_event(self.MEMBER_LOGON, DictObject({'account': main, 'packet': data}))

    @event(event_type="buddy_logoff", description="Member logoff manager")
    def member_logout(self, _, data):
        if self.bot.is_ready():
            buddy = self.buddy_service.get_buddy(data.char_id)
            if buddy:
                if "member" in buddy['types'] or "org_member" in buddy['types']:
                    if main := self.get_account(data.char_id):
                        self.event_service.fire_event(self.MEMBER_LOGOFF, DictObject({'account': main, 'packet': data}))

    def get_rank_count(self, char_id):
        return self.db.query_single(
            "SELECT MAX(org_rank_id)+1 AS count FROM player WHERE org_id=(SELECT org_id FROM player where char_id=?)",
            [char_id]).count

    def simple_checks(self, account):
        if account.get('disabled', 1) == 1:
            return False
        if account.get('member', -1) == -1:
            return False
        if self.setting_service.get_value("is_alliance_bot") == "1":
            if account.get('org_id', 0) not in self.get_orgs():
                return False
        return True
