import json
import re
import time
from threading import Thread

import datetime
import requests

from core.buddy_service import BuddyService
from core.cache_service import CacheService
from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Const, Any
from core.command_request import CommandRequest
from core.db import DB
from core.decorators import instance, command, timerevent, event
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.org_pork_service import OrgPorkService
from core.lookup.pork_service import PorkService
from core.message_hub_service import MessageHubService
from core.text import Text
from core.igncore import IgnCore
from core.util import Util
from modules.core.accounting.services.access_service import AccessService
# noinspection PyAttributeOutsideInit,SpellCheckingInspection,DuplicatedCode
from modules.core.accounting.services.account_service import AccountService
from modules.onlinebot.online.org_alias_controller import OrgAliasController


# noinspection DuplicatedCode
@instance()
class OrgController:
    threads = {}
    letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
               "v", "w", "x", "y", "z", "others", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    pork_uri = "https://people.anarchy-online.com/people/lookup/orgs.html?l=%s"
    single_org_uri = "https://people.anarchy-online.com/org/stats/d/5/name/%d/basicstats.xml?data_type=json"

    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.pork: PorkService = registry.get_instance("pork_service")
        self.org_pork: OrgPorkService = registry.get_instance("org_pork_service")
        self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
        self.buddy_service: BuddyService = registry.get_instance("buddy_service")
        self.access_service: AccessService = registry.get_instance("access_service")
        self.relay_hub_service: MessageHubService = registry.get_instance("message_hub_service")
        self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
        self.account_service: AccountService = registry.get_instance("account_service")
        self.cache: CacheService = registry.get_instance("cache_service")
        self.org_alias: OrgAliasController = registry.get_instance("org_alias_controller")

    def start(self):
        self.db.exec("CREATE TABLE IF NOT EXISTS orgs(org_id int primary key not null)")

        self.command_alias_service.add_alias("org", "orgs")

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

    @command(command="orgs", params=[Const("add"), Any("Organisation")], access_level="admin",
             description="Add an org to the online list", sub_command="moderate")
    def orgs_add_any(self, sender, _, org):
        return self.orgs_add(org, sender)

    def orgs_add(self, search, sender):
        orgs = self.org_pork.find_org(search)
        if len(orgs) == 1:
            orgs = orgs[0]
            if self.db.exec("REPLACE INTO orgs(org_id) VALUES(?)",
                            [orgs.org_id]) == 1:
                sender.reply(f"Adding the organisation <highlight>{orgs.org_name}<end> to the roster...")
                org_adder = Thread(name=orgs.org_id, target=self.fetch_single,
                                   args=(orgs.org_id, orgs.org_name, sender))
                self.threads[orgs.org_id] = org_adder
                org_adder.start()
            else:
                return f"The organisation <highlight>{orgs.org_name}<end> is in the roster already."
        elif len(orgs) == 0:
            return f"No org with the name <highlight>{search}<end> was found on PoRK."
        else:
            blob = "Your search had multiple results; please pick an org:<br>"
            for org in orgs:
                blob += f'[{self.text.make_chatcmd("Add", f"/tell <myname> orgs add {org.org_id}")}]' \
                        f'[{self.text.make_chatcmd("More", f"/tell <myname> org info {org.org_id}")}]' \
                        f' <highlight>{org.org_name}<end> (<highlight>{org.org_id}<end>) ' \
                        f'<{org.faction.lower()}>{org.faction}<end> [<highlight>{org.member_count}<end> members]' \
                        f'<br><pagebreak>'
            return ChatBlob("Pick an Org", blob)

    @command(command="orgs", params=[Const("rem"), Any("Organisation")], access_level="admin",
             description="Remove an org from the online list", sub_command="moderate")
    def orgs_rem_any(self, _, _1, org):
        return self.orgs_rem(org)

    def orgs_rem(self, search):
        orgs = self.org_pork.find_org(search)

        if len(orgs) == 1:
            orgs = orgs[0]
            if self.db.exec("DELETE FROM orgs where org_id = ?", [orgs.org_id]) == 1:
                org_remover = Thread(name=orgs.org_id, target=self.remove_single, args=(orgs.org_id, orgs.org_name))
                self.threads[orgs.org_id] = org_remover
                org_remover.start()
                return f"Removed the organisation <highlight>{orgs.org_name}<end> from the roster."
            else:
                return f"The organisation <highlight>{orgs.org_name}<end> is not on the roster list."
        elif len(orgs) == 0:
            return f"The organisation <highlight>{search}<end> is not on the roster list."
        else:
            blob = "Your search had multiple results; please pick an org:<br>"
            for org in orgs:
                blob += f'[{self.text.make_chatcmd("Remove", f"/tell <myname> orgs remove {org.org_id}")}]' \
                        f'[{self.text.make_chatcmd("More", f"/tell <myname> org info {org.org_id}")}]' \
                        f' <highlight>{org.org_name}<end> (<highlight>{org.org_id}<end>) ' \
                        f'<{org.faction.lower()}>{org.faction}<end> [<highlight>{org.member_count}<end> members]' \
                        f'<br><pagebreak>'
            return ChatBlob("Pick an Org", blob)

    @command(command="orgs", params=[Const("list", is_optional=True)], access_level="member",
             description="View all orgs on the online list", sub_command="online_info")
    def orgs_list(self, sender: CommandRequest, _):
        head = "<header>Organisations registered as Members<end>"
        blob = ""
        for org in self.db.query("SELECT o.*, a.*, oa.org_alias from orgs o "
                                 "left join all_orgs a on o.org_id = a.org_id "
                                 "left join org_alias oa on o.org_id = oa.org_id order by a.org_name"):
            org = DictObject(org)
            blob += "- %s%s%s<highlight>%s<end> (%d) with <highlight>%d<end> members\n" % (
                "[<highlight>" + self.text.make_chatcmd("Info", "/tell <myname> orgs info %d" % org.org_id,
                                                        style="style='text-decoration:none'") + "</highlight>] ",
                "[<red>" + self.text.make_chatcmd("Remove", "/tell <myname> orgs rem %d" % org.org_id,
                                                  style="style='text-decoration:none'") + "</red>] " if
                sender.sender.access_level["level"] <= 10 else "",
                f"[<highlight>{self.org_alias.get_alias(org.org_id)}</highlight>] ",
                org.org_name, org.org_id,
                self.db.query_single("SELECT member_count from all_orgs where org_id=?", [org.org_id]).member_count)

        return ChatBlob(head, blob)

    @timerevent(budatime="48h", description="pull list of all orgs from PoRK")
    def discover_orgs(self, _, _1):
        def discover():
            start = time.time()
            self.logger.info("Fetching global orgdata..")
            count = 0
            data = []

            for letter in self.letters:
                result = requests.get(self.pork_uri % letter)
                # noinspection RegExpRepeatedSpace
                matches = re.findall("""<tr>
               <td align="left">
                 <a href="//people.anarchy-online.com/org/stats/d/5/name/(\d+)">
                   (.+)</a></td>
               <td align="right">(\d+)</td>
               <td align="right">(\d+)</td>
               <td align="left">(\w+)</td>
               <td align="left">(\w+)</td>
               <td align="left" class="dim">RK5</td>
             </tr>""", result.text)
                for match in matches:
                    data.append((int(match[0]), match[1], int(match[2]), match[4], start))
                    count += 1
                self.logger.info(f"Batch {letter} done!")

            with self.db.pool.get_connection() as conn:
                with conn.cursor() as cur:
                    cur.executemany(
                        "INSERT INTO all_orgs(org_id, org_name, member_count, faction, last_seen) "
                        "VALUES(?, ?, ?, ?, ?) "
                        "ON DUPLICATE KEY UPDATE "
                        "org_name=VALUE(org_name), "
                        "member_count=VALUE(member_count), "
                        "last_seen=VALUE(last_seen)",
                        data)
            self.db.exec("DELETE FROM all_orgs where last_seen < ?", [time.time() - 2 * 24 * 60 * 60])
            self.logger.info(f"Successfully fetched {count} orgs in {time.time() - start:.2f} seconds.")
            self.threads.pop('orgdiscover', None)

        if "orgdiscover" not in self.threads.keys():
            thread = Thread(name="orgdiscover", target=discover, daemon=True)
            self.threads["orgdiscover"] = thread
            thread.start()

    @timerevent(budatime="24h", description="Pull data for our own orgs")
    def fetch_orgs(self, _, _1):
        def discover():
            start = time.time()
            self.logger.info("Fetching orgdata..")
            output = []
            data = []
            accounts = []
            ours = self.db.query("SELECT o.org_id, a.org_name from orgs o "
                                 "left join all_orgs a on o.org_id = a.org_id order by lower(a.org_name)")
            for org in ours:
                result = requests.get(self.single_org_uri % org.org_id).json()
                if result:
                    self.cache.store('org_roster', f"{org.org_id}.5.json", json.dumps(result))
                else:
                    result = json.loads(self.cache.retrieve('org_roster', f"{org.org_id}.5.json").data)

                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"],
                                     start, start))
                    if not self.buddy_service.get_buddy(char_info["CHAR_INSTANCE"]):
                        self.buddy_service.add_buddy(char_info["CHAR_INSTANCE"], "member")
                        output.append(DictObject({"action": "JOIN",
                                                  "name": char_info['NAME'],
                                                  "org_name": result[0]["NAME"],
                                                  "org_id": result[0]["ORG_INSTANCE"],
                                                  "level": char_info["LEVELX"],
                                                  "ai_level": char_info["ALIENLEVEL"],
                                                  "ranks": 0}))
                self.logger.info(f"Organisation {org.org_name} has been updated.")
            self.account_service.create_users(accounts)
            if len(data) > 1:
                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)
                            conn.commit()
            self.db.exec("UPDATE account set member = -1 "
                         "where char_id NOT IN "
                         "(select char_id from player where org_id in (select org_id from orgs)) and member > 0")
            self.db.exec("DELETE FROM ranks where main not in (select main from account where member > -1)")
            players = self.db.query("SELECT a.char_id, p.* FROM account a "
                                    "left join player p on a.char_id=p.char_id "
                                    "where (a.last_updated < ? and member >1) or "
                                    "(p.org_id NOT IN (select org_id from orgs) and a.member>1) ",
                                    [time.time() - 24 * 60 * 60])
            accounts = []
            for player in players:
                bonus = None
                player = DictObject(player)
                count = 0
                if self.buddy_service.remove_buddy(player.char_id, "member"):
                    count = self.db.exec("DELETE FROM ranks where main=?", [player.char_id])
                    accounts.append((player.char_id, 1))
                    bonus = "LEAVE"
                new_data = self.pork.request_char_info(player.name, player.dimension)
                if new_data and new_data.char_id == player.char_id:
                    self.pork.save_character_info(new_data)
                else:
                    bonus = "DEL"
                    accounts.append((player.char_id, 1))
                if bonus:
                    output.append(DictObject({"action": bonus,
                                              "name": player.name,
                                              "org_name": player.org_name,
                                              "org_id": player.org_id,
                                              "level": player.level,
                                              "ai_level": player.ai_level,
                                              "ranks": count}))
            self.account_service.remove_members(accounts)
            self.log(output, time.time() - start)
            self.logger.info(
                f"Successfully fetched {len(data)} players from {len(ours)} orgs "
                f"in {time.time() - start:.2f} seconds. - ")
            del self.threads['roster']

        if "roster" not in self.threads.keys():
            thread = Thread(name="roster", target=discover, daemon=True)
            self.threads["roster"] = thread
            thread.start()

    def fetch_single(self, org_id, org_name, sender: object):
        start = time.time()
        data = []
        accounts = []
        self.logger.info("Fetching orgdata..")
        count = 0
        result = requests.get(self.single_org_uri % org_id).json()
        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"],
                             start, start))
            self.buddy_service.add_buddy(char_info['CHAR_INSTANCE'], "member")
            count += 1
        with self.db.pool.get_connection() as conn:
            with conn.cursor() as cur:
                cur.executemany("REPLACE 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 "
                                "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", data)
        self.account_service.create_users(accounts)
        self.logger.info(f"Organisation {org_name} added!")
        sender.reply(f"<highlight>{org_name}</highlight> has been added to the roster. "
                     f"Runtime: {time.time() - start:.2f} seconds.")

        self.logger.info(f"Successfully fetched {count} players in {time.time() - start} seconds.")
        del self.threads[org_id]

    def remove_single(self, org_id, org_name):
        members = self.db.query("SELECT * from player where org_id=?", [org_id])
        for member in members:
            self.buddy_service.remove_buddy(member.char_id, "member")
        self.db.exec("UPDATE account set member=-1 where char_id in"
                     " (SELECT char_id from player where org_id=?)", [org_id])
        self.db.exec("DELETE FROM online where char_id in (SELECT char_id from player where org_id=?)", [org_id])
        self.logger.info(f"Organisation {org_name} removed!")
        del self.threads[org_id]

    def log(self, blob, duration):
        out = []
        s = []
        current = ""

        for entry in blob:
            s.append(f"[{self.alias_controller.get_alias(entry.org_id)}] [{entry.action}] {entry.name} "
                     f"({entry.level}/{entry.ai_level}) {'[R-P]' if entry.ranks > 0 else ''}\n")
        s = sorted(s)
        if len(s) > 0:
            s.append(f"\nRuntime: {duration:.2f} seconds.")
        for entry in s:
            if len(current) > 1500:
                out.append(current)
                current = ""
            current += entry
        if len(current) > 10:
            out.append(current)
        if len(out) > 0:
            y = 0
            for x in out:
                y += 1
                blob = ChatBlob(f"Recent changes - {datetime.date.today()} - ({y}/{len(out)})", x, embed=True)
                self.relay_hub_service.send_message("member_logger", None, blob, blob)
