import re
import time
from xml.etree import ElementTree

import bbcode
import hjson
import requests

from core.chat_blob import ChatBlob
from core.command_param_types import Any, Const, Int
from core.decorators import instance, command
from core.dict_object import DictObject
from core.translation_service import TranslationService
from core.igncore import IgnCore


# noinspection PyUnusedLocal
@instance()
class AOUController:
    AOU_URL = "https://www.ao-universe.com/mobile/parser.php?bot=igncore"

    CACHE_GROUP = "aou"
    CACHE_MAX_AGE = 604800

    def __init__(self):
        self.guide_id_regex = re.compile(r"pid=(\d+)", re.IGNORECASE)

        # initialize bbcode parser
        self.parser = bbcode.Parser(install_defaults=False, newline="\n", replace_links=False, replace_cosmetic=False,
                                    drop_unrecognized=True)
        self.parser.add_simple_formatter("i", "<i>%(value)s</i>")
        self.parser.add_simple_formatter("b", "<highlight>%(value)s</highlight>")
        self.parser.add_simple_formatter("ts_ts", " + ", standalone=True)
        self.parser.add_simple_formatter("ts_ts2", " = ", standalone=True)
        self.parser.add_simple_formatter("ct", " | ", standalone=True)
        self.parser.add_simple_formatter("cttd", " | ", standalone=True)
        self.parser.add_simple_formatter("cttr", "\n | ", standalone=True)
        self.parser.add_simple_formatter("br", "\n", standalone=True)
        self.parser.add_formatter("img", self.bbcode_render_image)
        self.parser.add_formatter("url", self.bbcode_render_url)
        self.parser.add_formatter("item", self.bbcode_render_item)
        self.parser.add_formatter("itemname", self.bbcode_render_item)
        self.parser.add_formatter("itemicon", self.bbcode_render_item)
        self.parser.add_formatter("waypoint", self.bbcode_render_waypoint)

    def inject(self, registry):
        self.text = registry.get_instance("text")
        self.bot: IgnCore = registry.get_instance("bot")
        self.items_controller = registry.get_instance("items_controller")
        self.cache_service = registry.get_instance("cache_service")
        self.command_alias_service = registry.get_instance("command_alias_service")
        self.ts: TranslationService = registry.get_instance("translation_service")
        self.getresp = self.ts.get_response

    def start(self):
        self.command_alias_service.add_alias("title", "aou 11")
        self.command_alias_service.add_alias("totw", "macro aou 171|aou 172")
        self.command_alias_service.add_alias("som", "macro aou 169|aou 383")
        self.command_alias_service.add_alias("reck", "aou 629")
        self.command_alias_service.add_alias("pets", "aou 2")
        self.ts.register_translation("module/aou", self.load_aou_msg)

    def load_aou_msg(self):
        with open("modules/standard/aou/aou.msg", mode="r", encoding="utf-8") as f:
            return hjson.load(f)

    @command(command="aou", params=[Int("guide_id")], access_level="member",
             description="Show an AO-Universe guide")
    def aou_show_cmd(self, request, guide_id):
        guide_info = self.retrieve_guide(guide_id)

        if not guide_info:
            return self.getresp("module/aou", "no_guide_id", {"id": guide_id})

        obj = DictObject()
        obj.id = self.text.make_chatcmd(guide_info.id,
                                        f"/start https://www.ao-universe.com/main.php?site=knowledge&id="
                                        f"{guide_info.id}")
        obj.raw = self.text.make_chatcmd("Raw", "/start %s" % (self.AOU_URL + "&mode=view&id=" + str(guide_info.id)))
        obj.updated = guide_info.updated
        obj.profession = guide_info.profession
        obj.faction = guide_info.faction
        obj.level = guide_info.level
        obj.author = self.format_bbcode_code(guide_info.author)
        obj.aou = self.text.make_chatcmd("AO-Universe.com", "/start https://www.ao-universe.com")
        obj.text = self.format_bbcode_code(guide_info.text)

        return ChatBlob(guide_info.name, self.getresp("module/aou", "guide", {**obj}))

    @command(command="aou", params=[Const("all", is_optional=True), Any("search")], access_level="member",
             description="Search for an AO-Universe guides")
    def aou_search_cmd(self, request, include_all_matches, search):
        include_all_matches = include_all_matches or False

        r = requests.get(self.AOU_URL + "&mode=search&search=" + search, timeout=5)
        xml = ElementTree.fromstring(r.content)

        blob = ""
        count = 0
        for section in xml.iter("section"):
            category = self.get_category(section)
            found = False
            for guide in self.get_guides(section):
                if include_all_matches or self.check_matches(
                        category + " " + guide["name"] + " " + (guide["description"] or ""), search):
                    # don't show category unless we have at least one guide for it
                    if not found:
                        blob += "\n<header2>%s</header2>\n" % category
                        found = True

                    count += 1
                    blob += "%s - %s\n" % (
                        self.text.make_tellcmd(guide["name"], "aou %s" % guide["id"]), guide["description"])
        blob += "\n\nPowered by %s" % self.text.make_chatcmd("AO-Universe.com", "/start https://www.ao-universe.com")

        if count == 0:
            return self.getresp("module/aou", "no_guide_search", {"search": search})
        else:
            return ChatBlob(
                self.getresp("module/aou", "search_guide_title" + ("_all" if include_all_matches else ""),
                             {"search": search, "count": count}), blob)

    def retrieve_guide(self, guide_id):
        cache_key = "%d.xml" % guide_id

        t = int(time.time())

        # check cache for fresh value
        cache_result = self.cache_service.retrieve(self.CACHE_GROUP, cache_key)

        if cache_result and cache_result.last_modified > (t - self.CACHE_MAX_AGE):
            result = ElementTree.fromstring(cache_result.data)
        else:
            response = requests.get(self.AOU_URL + "&mode=view&id=" + str(guide_id), timeout=5)
            result = ElementTree.fromstring(response.content)

            if result.findall("./error"):
                result = None

            if result:
                # store result in cache
                self.cache_service.store(self.CACHE_GROUP, cache_key, ElementTree.tostring(result, encoding="unicode"))
            elif cache_result:
                # check cache for any value, even expired
                result = ElementTree.fromstring(cache_result.data)

        if result:
            return self.get_guide_info(result)
        else:
            return None

    def get_guide_info(self, xml):
        content = self.get_xml_child(xml, "section/content")
        return DictObject({
            "id": self.get_xml_child(content, "id").text,
            "category": self.get_category(self.get_xml_child(xml, "section")),
            "name": self.get_xml_child(content, "name").text,
            "updated": self.get_xml_child(content, "update").text,
            "profession": self.get_xml_child(content, "class").text,
            "faction": self.get_xml_child(content, "faction").text,
            "level": self.get_xml_child(content, "level").text,
            "author": self.get_xml_child(content, "author").text,
            "text": self.get_xml_child(content, "text").text
        })

    def check_matches(self, haystack, needle):
        haystack = haystack.lower()
        for n in needle.split():
            if n in haystack:
                return True
        return False

    def get_guides(self, section):
        result = []
        for guide in section.findall("./guidelist/guide"):
            result.append({"id": guide[0].text, "name": guide[1].text, "description": guide[2].text})
        return result

    def get_category(self, section):
        result = []
        for folder_names in section.findall("./folderlist/folder/name"):
            result.append(folder_names.text)
        return " - ".join(reversed(result))

    def get_xml_child(self, xml, child_tag):
        return xml.findall("./%s" % child_tag)[0]

    def format_bbcode_code(self, bbcode_str):
        return self.parser.format(bbcode_str)

    # BBCode formatters
    def bbcode_render_image(self, tag_name, value, options, parent, context):
        return self.text.make_chatcmd("Image", "/start https://www.ao-universe.com/" + value)

    def bbcode_render_url(self, tag_name, value, options, parent, context):
        url = options.get("url") or value
        guide_id_match = self.guide_id_regex.search(url)
        if guide_id_match:
            return self.text.make_tellcmd(value, "aou " + guide_id_match.group(1))
        else:
            return self.text.make_chatcmd(value, "/start " + url)

    def bbcode_render_item(self, tag_name, value, options, parent, context):
        item = self.items_controller.get_by_item_id(value)
        if not item:
            return "Unknown Item(%s)" % value
        else:
            include_icon = tag_name == "item" or tag_name == "itemicon"
            return self.text.format_item(item, with_icon=include_icon)

    def bbcode_render_waypoint(self, tag_name, value, options, parent, context):
        x_coord = options["x"]
        y_coord = options["y"]
        pf_id = options["pf"]

        return self.text.make_chatcmd("%s (%sx%s)" % (value, x_coord, y_coord),
                                      "/waypoint %s %s %s" % (x_coord, y_coord, pf_id))
