import json
import time

import requests

from core.aochat import server_packets
from core.buddy_service import BuddyService
from core.cache_service import CacheService
from core.chat_blob import ChatBlob
from core.command_param_types import Int
from core.db import DB
from core.decorators import instance, command, event, timerevent
from core.dict_object import DictObject
from core.event_service import EventService
from core.igncore import IgnCore
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.org_pork_service import OrgPorkService
from core.lookup.pork_service import PorkService
from core.public_channel_service import PublicChannelService
from core.util import Util
from modules.core.accounting.services.account_service import AccountService


@instance()
class OrgRosterController:
    ORG_BUDDY_TYPE = "org_member"
    ORG_ACCESS_LEVEL = "org_member"

    MODE_ADD_AUTO = "add_auto"
    MODE_REM_AUTO = "rem_auto"
    MODE_ADD_MANUAL = "add_manual"
    MODE_REM_MANUAL = "rem_manual"

    ORG_MEMBER_LOGON_EVENT = "org_member_logon"
    ORG_MEMBER_LOGOFF_EVENT = "org_member_logoff"
    ORG_MEMBER_REMOVED_EVENT = "org_member_removed"

    LEFT_ORG = [508, 45978487]
    KICKED_FROM_ORG = [508, 37093479]
    INVITED_TO_ORG = [508, 173558247]
    KICKED_INACTIVE_FROM_ORG = [508, 20908201]
    KICKED_ALIGNMENT_CHANGED = [501, 181448347]
    JOINED_ORG = [508, 5146599]

    def __init__(self):
        self.readd_cache = []
        self.logger = Logger(__name__)

    def inject(self, registry):
        self.bot: IgnCore = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.public_channel_service: PublicChannelService = registry.get_instance("public_channel_service")
        self.access_service = registry.get_instance("access_service")
        self.org_pork_service: OrgPorkService = registry.get_instance("org_pork_service")
        self.pork: PorkService = registry.get_instance("pork_service")
        self.util: Util = registry.get_instance("util")
        self.event_service: EventService = registry.get_instance("event_service")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.cache: CacheService = registry.get_instance("cache_service")
        self.account_service: AccountService = registry.get_instance("account_service")

    def pre_start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS org_activity ("
                     "id int primary key AUTO_INCREMENT, "
                     "message varchar(255) NOT NULL, "
                     "time int not null)")
        self.event_service.register_event_type(self.ORG_MEMBER_LOGON_EVENT)
        self.event_service.register_event_type(self.ORG_MEMBER_LOGOFF_EVENT)

        self.access_service.register_access_level(self.ORG_ACCESS_LEVEL, 60, self.check_org_member)
        self.bot.register_packet_handler(server_packets.BuddyRemoved.id, self.handle_remove)

    def check_org_member(self, char_id):
        return (self.account_service.get_account(char_id) or {}).get("member", 0) == self.public_channel_service.org_id

    @event("connect", "Adds all members to buddylist")
    def connect(self, _, _1):
        query = self.db.query("SELECT char_id, member from account where member IN (?, 0)",
                              [self.public_channel_service.org_id])
        if query:
            for player in query:
                if player.member == self.public_channel_service.org_id:
                    self.buddy_service.add_buddy(player.char_id, "org_member")
                else:
                    self.buddy_service.add_buddy(player.char_id, "member")

    @event("buddy_logon", "fire orgmember logon event")
    def orgmember_logon(self, _, data):
        account = self.account_service.get_entry(data.char_id)
        if not account:
            return
        if account.member == self.public_channel_service.org_id:
            self.event_service.fire_event(self.ORG_MEMBER_LOGON_EVENT, DictObject(
                {'account': account, 'char_info': self.pork.get_character_info(data.char_id), 'packet': data}))

    @event("buddy_logoff", "fire orgmember logoff event")
    def orgmember_logoff(self, _, data):
        account = self.account_service.get_entry(data.char_id)
        if not account:
            return
        if account.member == self.public_channel_service.org_id:
            self.event_service.fire_event(self.ORG_MEMBER_LOGOFF_EVENT,
                                          DictObject({'account': account, 'packet': data}))

    @command(command="orgmembers", params=[], access_level="admin",
             description="Show the list of org members")
    def org_members_cmd(self, _):
        data = self.db.query(
            "SELECT * from account a left join player p on a.char_id = p.char_id where member=? order by p.name",
            [self.public_channel_service.org_id])
        blob = ""
        for row in data:
            blob += f"{self.text.zfill(row.level, 220)}/<green>{self.text.zfill(row.ai_level, 30)}</green> " \
                    f"{row.name} ({row.org_rank_name})\n"

        return ChatBlob(f"Orgmembers ({len(data)})", blob)

    @command(command="orgactivity", params=[Int('count', is_optional=True)], access_level="org_member",
             description="Show the most recent org activity")
    def org_activity(self, _, count):
        data = self.db.query("SELECT * from org_activity order by id desc LIMIT ?", [count or 25])

        return ChatBlob(f"Recent Activities ({len(data)})", "\n".join(
            [f'<grey>[{self.util.format_datetime(x.time)}]</grey> {x.message}' for x in data]))

    @timerevent("24h", "Update the orgroster on changes")
    def update_roster(self, _, _1):
        self.bot.send_org_message("Updating roster...")
        cache = self.cache.retrieve('org_roster', f"{self.public_channel_service.org_id}.5.json")
        if cache:
            if cache.last_modified < time.time() - 16 * 60 * 60:
                result = requests.get(self.org_pork_service.get_pork_url(5, self.public_channel_service.org_id)).json()
                if result:
                    self.cache.store('org_roster', f"{self.public_channel_service.org_id}.5.json", json.dumps(result))
                else:
                    result = json.loads(cache.data)
            else:
                result = json.loads(cache.data)
        else:
            result = requests.get(self.org_pork_service.get_pork_url(5, self.public_channel_service.org_id)).json()
            if result:
                self.cache.store('org_roster', f"{self.public_channel_service.org_id}.5.json", json.dumps(result))
        data = []
        accounts = []
        for char_info in result[1]:
            data.append((char_info["CHAR_INSTANCE"], char_info["NAME"], char_info["FIRSTNAME"],
                         char_info["LASTNAME"], char_info["LEVELX"], char_info["BREED"],
                         char_info["SEX"], result[0]["SIDE_NAME"], char_info["PROF"],
                         char_info["PROF_TITLE"], char_info["DEFENDER_RANK_TITLE"], char_info["ALIENLEVEL"],
                         result[0]["ORG_INSTANCE"], result[0]["NAME"], char_info["RANK_TITLE"],
                         char_info["RANK"], char_info["CHAR_DIMENSION"], char_info["HEADID"],
                         0, char_info["PVPTITLE"], "roster", int(time.time())))

            accounts.append((char_info["CHAR_INSTANCE"], char_info["CHAR_INSTANCE"], result[0]["ORG_INSTANCE"],
                             time.time(), time.time()))
            if buddy := self.buddy_service.get_buddy(char_info['CHAR_INSTANCE']):
                if "org_member" in buddy['types']:
                    continue
            self.buddy_service.add_buddy(char_info['CHAR_INSTANCE'], "org_member")

        with self.db.lock:
            with self.db.pool.get_connection() as conn:
                with conn.cursor() as cur:
                    cur.executemany("INSERT INTO player(char_id, name, first_name, last_name, level, "
                                    "breed, gender, faction, profession, profession_title, ai_rank, "
                                    "ai_level, org_id, org_name, org_rank_name, org_rank_id, dimension, "
                                    "head_id, pvp_rating, pvp_title, source, last_updated) "
                                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
                                    "ON DUPLICATE KEY UPDATE first_name=VALUE(first_name), last_name=VALUE(last_name), "
                                    "level=VALUE(level), breed=VALUE(breed), gender=VALUE(gender), "
                                    "faction=VALUE(faction), profession=VALUE(profession), "
                                    "profession_title=VALUE(profession_title), ai_rank=VALUE(ai_rank), "
                                    "ai_level=VALUE(ai_level), org_name=VALUE(org_name), org_id=VALUE(org_id), "
                                    "org_rank_name=VALUE(org_rank_name), org_rank_id=VALUE(org_rank_id), "
                                    "source=VALUE(source), last_updated=VALUE(last_updated)", data)
                    self.account_service.create_users(accounts)
                    users = self.db.query('SELECT * from account where member=? and last_updated < ?',
                                          [self.public_channel_service.org_id, time.time() - 23 * 60 * 60])
                    for user in users:
                        self.buddy_service.remove_buddy(user.char_id, 'org_member')
                    cur.execute("UPDATE account set member=-1 where member=? and last_updated < ?",
                                [self.public_channel_service.org_id, time.time() - 23 * 60 * 60])
                    cur.execute("UPDATE account set member=-1 where member NOT IN (0, ?, -1)",
                                [self.public_channel_service.org_id])
                conn.commit()
        self.bot.send_org_message('Successfully updated the roster.')

    @event(PublicChannelService.ORG_MSG_EVENT, "Update org roster when characters join or leave", is_hidden=True)
    def org_msg_event(self, _, event_data):
        ext_msg = event_data.extended_message
        log = False
        if [ext_msg.category_id, ext_msg.instance_id] == self.LEFT_ORG:
            log = True
            self.process_org_msg(ext_msg.params[0], self.MODE_REM_MANUAL, ext_msg)
        elif [ext_msg.category_id, ext_msg.instance_id] == self.KICKED_FROM_ORG:
            log = True
            self.process_org_msg(ext_msg.params[1], self.MODE_REM_MANUAL, ext_msg.params[0])
        elif [ext_msg.category_id, ext_msg.instance_id] == self.INVITED_TO_ORG:
            log = True
            self.process_org_msg(ext_msg.params[1], self.MODE_ADD_MANUAL, ext_msg.params[0])
        elif [ext_msg.category_id, ext_msg.instance_id] == self.KICKED_INACTIVE_FROM_ORG:
            log = True
            self.process_org_msg(ext_msg.params[1], self.MODE_REM_MANUAL, ext_msg.params[0])
        elif [ext_msg.category_id, ext_msg.instance_id] == self.KICKED_ALIGNMENT_CHANGED:
            log = True
            self.process_org_msg(ext_msg.params[0], self.MODE_REM_MANUAL)
        elif [ext_msg.category_id, ext_msg.instance_id] == self.JOINED_ORG:
            log = True
            self.process_org_msg(ext_msg.params[0], self.MODE_ADD_MANUAL)
        # This just writes everything to the log.... also tower messages, and so on. mainly for debugging reasons,
        # may change in the future.
        # noinspection SqlInsertValues
        if log:
            self.db.exec("INSERT INTO org_activity (message, time) VALUES(?, ?)", [ext_msg.get_message(), time.time()])

    def process_org_msg(self, char_name, new_mode, actee=None):
        # TODO: errors out on newly created chars, which do not show up on PoRK yet.
        # self.pork.get_character_info(char_name)
        char_id = self.character_service.resolve_char_to_id(char_name)
        org_member = self.get_org_member(char_id)
        self.process_update(char_id, org_member.member if org_member else None, new_mode, actee)

    def get_org_member(self, char_id):
        return self.db.query_single("SELECT char_id, member FROM account WHERE char_id = ?", [char_id])

    def process_update(self, char_id, old_mode, new_mode, actee):
        if old_mode == new_mode:
            return
        if new_mode in [self.MODE_REM_MANUAL, self.MODE_REM_AUTO]:
            self.bot.send_org_message(f"<highlight>{self.character_service.get_char_name(char_id)}</highlight> "
                                      f"left the org.")
            main = self.account_service.get_main(char_id)
            self.account_service.add_log(main.char_id, "system", f'Left the org '
                                                                 f'{self.public_channel_service.get_org_name()} with '
                                                                 f'{self.character_service.get_char_name(char_id)}',
                                         main.char_id)
            self.account_service.create_users(users=[(char_id, char_id, -1, time.time(), time.time())])
            self.update_buddylist(char_id, self.MODE_REM_AUTO)
        if new_mode in [self.MODE_ADD_MANUAL, self.MODE_ADD_AUTO]:
            self.account_service.create_users(users=[(char_id, char_id,
                                                      self.public_channel_service.org_id,
                                                      time.time(),
                                                      time.time())])
            main = self.account_service.get_main(char_id)
            self.account_service.add_log(char_id, "system", f'Joined the org '
                                                            f'{self.public_channel_service.get_org_name()} with '
                                                            f'{self.character_service.get_char_name(char_id)}',
                                         main.char_id)
            self.bot.send_org_message(f"Please Welcome "
                                      f"<highlight>{self.character_service.get_char_name(char_id)}</highlight>!!! ")
            self.update_buddylist(char_id, self.MODE_ADD_AUTO)

    def update_buddylist(self, char_id, mode):
        if mode in [self.MODE_ADD_MANUAL, self.MODE_ADD_AUTO]:
            if not self.buddy_service.get_buddy(char_id):
                self.buddy_service.add_buddy(char_id, self.ORG_BUDDY_TYPE)
                return
            self.readd_cache.append(char_id)
            self.buddy_service.remove_buddy(char_id, "member")
        else:
            self.buddy_service.remove_buddy(char_id, self.ORG_BUDDY_TYPE)

    def handle_remove(self, conn, packet):
        if packet.char_id in self.readd_cache:
            self.readd_cache.remove(packet.char_id)
            self.buddy_service.add_buddy(packet.char_id, self.ORG_BUDDY_TYPE)
