import json
import os
import re

from core.chat_blob import ChatBlob
from core.command_param_types import Any, Int, NamedParameters
from core.decorators import instance, command
from core.logger import Logger


@instance()
class RecipeController:
    def __init__(self):
        self.logger = Logger(__name__)

        self.recipe_name_regex = re.compile(r"(\d+)\.(txt|json)")
        self.recipe_item_regex = re.compile(r"#L \"([^\"]+)\" \"([\d+]+)\"")
        self.recipe_link_regex = re.compile(r"#L \"([^\"]+)\" \"([^\"]+)\"")

    def inject(self, registry):
        self.db = registry.get_instance("db")
        self.text = registry.get_instance("text")
        self.items_controller = registry.get_instance("items_controller")
        self.command_alias_service = registry.get_instance("command_alias_service")

    def start(self):
        self.command_alias_service.add_alias("r", "recipe")
        self.command_alias_service.add_alias("tradeskill", "recipe")

        self.db.exec("CREATE TABLE IF NOT EXISTS recipe ("
                     "id INT NOT NULL PRIMARY KEY, "
                     "name VARCHAR(50) NOT NULL, "
                     "author VARCHAR(50) NOT NULL, "
                     "recipe TEXT NOT NULL, "
                     "dt INT NOT NULL DEFAULT 0)")
        self.db.create_view("recipe")
        recipe_dir = os.path.dirname(os.path.realpath(__file__)) + "/recipes/"
        recipes = self.db.query("SELECT id, dt FROM recipe")

        for file in os.listdir(recipe_dir):
            if file.startswith("_"):
                continue

            m = self.recipe_name_regex.match(file)
            if m:
                recipe_id = m.group(1)
                file_type = m.group(2)
                dt = int(os.path.getmtime(recipe_dir + file))

                recipe = self.find_recipe(recipe_id, recipes)
                if recipe:
                    recipes.remove(recipe)
                    if recipe.dt >= dt:
                        continue

                self.update_recipe(recipe_dir, recipe_id, file_type, dt)
            else:
                raise Exception(f"Unknown recipe format for '{file}'")

    @command(command="recipe", params=[Int("recipe_id")], access_level="member", description="Show a recipe")
    def recipe_show_cmd(self, _, recipe_id):
        recipe = self.get_recipe(recipe_id)
        if not recipe:
            return f"Could not find recipe with ID <highlight>{recipe_id:d}</highlight>."

        return self.format_recipe(recipe)

    @command(command="recipe",
             params=[Any("search"), NamedParameters(["page"])],
             access_level="member",
             description="Search for a recipe")
    def recipe_search_cmd(self, _, search, named_params):
        page = int(named_params.page or "1")
        page_size = 30
        offset = (page - 1) * page_size
        search = self.text.strip_html_tags(search)
        data = self.db.query("SELECT * FROM recipe "
                             "WHERE name LIKE ? "
                             "or recipe LIKE ? "
                             "ORDER BY name",
                             [f"%{search}%", f"%{search}%"])
        count = len(data)
        paged_data = data[offset:offset + page_size]
        if count == 1:
            return self.format_recipe(data[0])
        blob = ""

        if count > page_size:
            if page > 1 and len(paged_data) > 0:
                blob += "   " + self.text.make_chatcmd(f"«« Page {page - 1:d}", self.get_chat_command(search, page - 1))
            if offset + page_size < len(data):
                blob += "   Page " + str(page)
                blob += "   " + self.text.make_chatcmd(f"Page {page + 1:d} »»", self.get_chat_command(search, page + 1))
            blob += "\n\n"

        for row in paged_data:
            blob += self.text.make_tellcmd(row.name, f"recipe {row.id:d}") + "\n"

        return ChatBlob(
            f"Recipes Matching '{search}' ({offset + 1:d} - {min(offset + page_size, count):d} of {count:d})", blob)

    def get_recipe(self, recipe_id):
        return self.db.query_single("SELECT * FROM recipe WHERE id = ?", [recipe_id])

    def format_recipe(self, recipe):
        blob = f"Recipe ID: <highlight>{recipe.id:d}</highlight>\n"
        blob += f"Author: <highlight>{recipe.author or 'Unknown'}</highlight>\n\n"
        blob += self.format_recipe_text(recipe.recipe)

        return ChatBlob(f"Recipe for '{recipe.name}'", blob)

    def format_recipe_text(self, recipe_text):
        recipe_text = recipe_text.replace("\\n", "\n")
        recipe_text = self.recipe_item_regex.sub(self.lookup_item, recipe_text)
        recipe_text = self.recipe_link_regex.sub("<a href='chatcmd://\\2'>\\1</a>", recipe_text)
        return recipe_text

    def lookup_item(self, m):
        name = m.group(1)
        item_id = m.group(2)

        item = self.items_controller.get_by_item_id(item_id)
        if item:
            return self.text.make_item(item.lowid, item.highid, item.highql, item.name)
        else:
            return name

    def get_chat_command(self, search, page):
        return f"/tell <myname> recipe {search} --page={page:d}"

    def find_recipe(self, recipe_id, recipes):
        for row in recipes:
            if str(row.id) == recipe_id:
                return row
        return None

    def update_recipe(self, recipe_dir, recipe_id, _, dt):
        with open(recipe_dir + recipe_id + ".json", mode="r", encoding="UTF-8") as f:
            recipe = json.load(f)

        name = recipe["name"]
        author = recipe["author"]

        if "raw" in recipe:
            content = recipe["raw"]
        else:
            content = self.format_json_recipe(recipe_id, recipe)
        self.db.exec("REPLACE INTO recipe (id, name, author, recipe, dt) "
                     "VALUES (?, ?, ?, ?, ?)",
                     [recipe_id, name, author, content, dt])

    def format_json_recipe(self, recipe_id, recipe):
        items = {}
        for i in recipe["items"]:
            item = self.items_controller.get_by_item_id(i["item_id"], i.get("ql"))
            if not item:
                raise Exception(f"Could not find recipe item '{i['item_id']:d}' for recipe id {recipe_id}")

            item.ql = i.get("ql") or (item.highql if i["item_id"] == item.highid else item.lowql)
            items[i["alias"]] = item

        content = ""

        ingredients = items.copy()
        for step in recipe["steps"]:
            del ingredients[step["result"]]

        content += self.format_ingredients(ingredients.items())
        content += "\n"
        content += self.format_steps(items, recipe["steps"])

        if "details" in recipe:
            content += self.format_details(recipe["details"])

        return content

    def format_ingredients(self, ingredients):
        content = "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Ingredients</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        for _, ingredient in ingredients:
            content += self.text.make_image(ingredient["icon"]) + "<tab>"
            content += self.text.make_item(ingredient["lowid"], ingredient["highid"], ingredient["ql"],
                                           ingredient["name"]) + "\n"

        return content

    def format_steps(self, items, steps):
        content = ""
        content += "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Recipe</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        for step in steps:
            source = items[step["source"]]
            target = items[step["target"]]
            result = items[step["result"]]
            content += f"<a href='itemref://{source['lowid']:d}/{source['highid']:d}/{source['ql']:d}'>" \
                       f"{self.text.make_image(source['icon'])}</a>" + ""
            content += "<font color=#FFFFFF><tab>+<tab></font> "
            content += f"<a href='itemref://{target['lowid']:d}/{target['highid']:d}/{target['ql']:d}'>" \
                       f"{self.text.make_image(target['icon'])}</a>" + ""
            content += "<font color=#FFFFFF><tab>=<tab></font> "
            content += f"<a href='itemref://{result['lowid']:d}/{result['highid']:d}/{result['ql']:d}'>" \
                       f"{self.text.make_image(result['icon'])}</a>"
            content += "\n<tab><tab>" + self.text.make_item(source["lowid"],
                                                            source["highid"], source["ql"], source["name"])
            content += "\n + <tab>" + self.text.make_item(target["lowid"],
                                                          target["highid"], target["ql"], target["name"])
            content += "\n = <tab>" + self.text.make_item(result["lowid"],
                                                          result["highid"], result["ql"], result["name"]) + "\n"

            if "skills" in step:
                content += f"<font color=#FFFF00>Skills: | {step['skills']} |</font>\n"
            content += "\n\n"

        return content

    def format_details(self, details):
        content = ""
        content += "<font color=#FFFF00>------------------------------</font>\n"
        content += "<font color=#FF0000>Details</font>\n"
        content += "<font color=#FFFF00>------------------------------</font>\n\n"

        last_type = ""
        for detail in details:
            if "item" in detail:
                last_type = "item"
                i = detail["item"]

                if "ql" in i:
                    item = self.items_controller.get_by_item_id(i["id"], i["ql"])
                else:
                    item = self.items_controller.get_by_item_id(i["id"])
                    item["ql"] = item["highql"]

                content += "<font color=#009B00>%s</font>" % \
                           self.text.make_item(item['lowid'],
                                               item['highid'],
                                               item['ql'],
                                               item['name'])

                if "comment" in i:
                    content += " - " + i["comment"]

                content += "\n"

            elif "text" in detail:
                if last_type == "item":
                    content += "\n"

                last_type = "text"
                content += f"<font color=#FFFFFF>{detail['text']}</font>\n"

        return content

    def convert_to_json(self, recipe_dir, recipe_id, file):
        with open(recipe_dir + file, mode="r", encoding="UTF-8") as f:
            lines = f.readlines()

        recipe = {
            "name": lines.pop(0).strip()[6:],
            "author": lines.pop(0).strip()[8:],
            "items": list(),
            "steps": list(),
            "details": list(),
            "raw": None
        }

        content = "".join(lines)
        items = {}

        matches = self.recipe_item_regex.findall(content)
        for item_name, item_id in matches:
            item = self.items_controller.get_by_item_id(item_id)
            if not item:
                self.logger.warning(f"Could not find recipe item '{item_id} - {item_name}' for recipe id {recipe_id}")
            else:
                items[item.highid] = {"alias": item.name, "item_id": item.highid}

        recipe["items"].extend(items.values())
        recipe["raw"] = content

        with open(recipe_dir + recipe_id + ".json", mode="w", encoding="UTF-8") as f:
            f.write(json.dumps(recipe, indent=4))

        # delete file
        os.remove(recipe_dir + file)

        return True
