from gi.repository import GObject, GLib

import datetime
import logging
import os
import re
import shutil
from typing import Optional
import unicodedata

try:
    import pypandoc
except ModuleNotFoundError:
    pypandoc = None

from iotas.html_generator import HtmlGenerator
from iotas.note import Note
from iotas.pdf_exporter import PdfExporter


class Exporter(GObject.Object):

    __gsignals__ = {
        # Out format, out path
        "finished": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
        # Out format and reason
        "failed": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
    }

    def __init__(
        self, pdf_exporter: PdfExporter, html_generator: HtmlGenerator, app_data_path: str
    ) -> None:
        super().__init__()
        self.__pdf_exporter = pdf_exporter
        self.__pdf_exporter.set_callbacks(
            self.__on_pdf_export_finished, self.__on_pdf_export_failed
        )
        self.__html_generator = html_generator
        self.__app_data_path = app_data_path
        self.__active = False
        self.__in_error = False

    def export(
        self,
        note: Note,
        out_format: str,
        file_extension: str,
        supporting_tex: bool,
        user_location: Optional[str] = None,
    ) -> None:
        """Export note.

        :param Note note: Note to render
        :param str out_format: Export format
        :param str file_extension: File extension
        :param bool supporting_tex: TeX support
        :param str user_location: User chosen export location
        """
        self.__note = note
        self.__out_format = out_format
        self.__supporting_tex = supporting_tex
        if user_location:
            self.__location = user_location
        else:
            # Running with limited permissions in container, export to exports dir inside container
            # with automatic filename
            export_dir = os.path.join(GLib.get_user_data_dir(), "iotas", "exports")
            filename = self.build_default_filename(
                note, out_format, file_extension, add_timestamp=True
            )
            self.__location = os.path.join(export_dir, filename)
            if not os.path.exists(export_dir):
                try:
                    os.mkdir(export_dir)
                except OSError as e:
                    logging.warning(f"Failed to export {out_format} to {self.__location}: %s", e)
                    self.emit("failed", self.__out_format, e)
                    return

        if out_format == "pdf":
            self.__export_pdf()
        elif out_format == "md":
            self.__export_md()
        elif out_format == "html":
            self.__export_html()
        else:
            logging.info(f"Asking pandoc to export to {out_format}")
            self.__export_pandoc(out_format)

    def build_default_filename(
        self, note: Note, out_format: str, file_extension: str, add_timestamp: bool = False
    ) -> str:
        """Build an export filename for the note.

        :param Note note: Note to render
        :param str out_format: Export format
        :param str file_extension: File extension
        :param bool add_timestamp: Whether to prefix timestamp
        :return: Filename
        :rtype: str
        """
        filename = self.__sanitise_title_for_filename(note.title)
        if add_timestamp:
            ts = datetime.datetime.now()
            filename = ts.strftime("%Y-%m-%dT%H:%M:%S") + " " + filename
        if out_format != "html":
            filename += "." + file_extension
        return filename

    @GObject.Property(type=bool, default=False)
    def active(self) -> bool:
        return self.__active

    @active.setter
    def set_active(self, value: bool) -> None:
        self.__active = value

    def __on_pdf_export_finished(self) -> None:
        self.__active = False
        self.emit("finished", "pdf", self.__location)

    def __on_pdf_export_failed(self, error: str) -> None:
        self.__active = False
        self.emit("failed", "pdf", error)

    def __export_pandoc(self, out_format: str) -> None:
        self.__active = True
        if pypandoc:
            try:
                pypandoc.convert_text(
                    self.__note.content, out_format, format="gfm", outputfile=self.__location
                )
            except (RuntimeError, OSError) as e:
                logging.warning(f"Failed to export {out_format} to {self.__location}: %s", e)
                self.emit("failed", self.__out_format, str(e))
            else:
                logging.info(f"Exported {self.__out_format} to {self.__location}")
                self.emit("finished", self.__out_format, self.__location)
        else:
            logging.warning(
                f"Failed to export {out_format} to {self.__location}: "
                "Pypandoc is not installed on your system."
            )
            self.emit("failed", self.__out_format, "Pypandoc is not installed on your system.")
        self.__active = False

    def __export_pdf(self) -> None:
        self.__active = True
        self.__pdf_exporter.export(self.__note, self.__location)

    def __export_md(self) -> None:
        self.__active = True

        try:
            with open(self.__location, "w") as f:
                f.write(self.__note.content)
        except OSError as e:
            self.emit("failed", self.__out_format, e)
            logging.warning("Failed to export MD: %s", e)
            self.__active = False
            return

        self.__active = False
        logging.info(f"Exported {self.__out_format} to {self.__location}")
        self.emit("finished", self.__out_format, self.__location)

    def __export_html(self) -> None:
        self.__active = True

        (content, _) = self.__html_generator.generate(
            self.__note, searching=False, export_format="html"
        )

        if os.path.exists(self.__location):
            logging.debug(f"Removing existing {self.__location}")
            try:
                shutil.rmtree(self.__location)
            except OSError as e:
                self.emit("failed", self.__out_format, e)
                logging.warning("Failed to replace dir for HTML export: %s", e)
                self.__active = False
                return

        try:
            os.mkdir(self.__location)
        except OSError as e:
            self.emit("failed", self.__out_format, e)
            logging.warning("Failed to make dir for HTML export: %s", e)
            self.__active = False
            return

        index_filename = os.path.join(self.__location, "index.html")

        try:
            with open(index_filename, "w") as f:
                f.write(content)
        except OSError as e:
            self.emit("failed", self.__out_format, e)
            logging.warning("Failed to export HTML: %s", e)
            self.__active = False
            return

        css_dir = os.path.join(self.__location, "css")

        try:
            os.mkdir(css_dir)
        except OSError as e:
            self.emit("failed", self.__out_format, e)
            logging.warning("Failed to export HTML: %s", e)
            self.__active = False
            return

        dest_file = os.path.join(css_dir, os.path.basename(HtmlGenerator.RESOURCE_CSS_PATH))

        try:
            shutil.copyfile(f"{self.__app_data_path}/{HtmlGenerator.RESOURCE_CSS_PATH}", dest_file)
        except OSError as e:
            self.emit("failed", self.__out_format, e)
            logging.warning("Failed to export HTML: %s", e)
            self.__active = False
            return

        if self.__supporting_tex:
            dest_file = os.path.join(
                css_dir, os.path.basename(HtmlGenerator.RESOURCE_KATEX_CSS_PATH)
            )

            try:
                shutil.copyfile(
                    f"{self.__app_data_path}/{HtmlGenerator.RESOURCE_KATEX_CSS_PATH}", dest_file
                )
            except OSError as e:
                self.emit("failed", self.__out_format, e)
                logging.warning("Failed to export HTML: %s", e)
                self.__active = False
                return

            js_dir = os.path.join(self.__location, "js")

            try:
                os.mkdir(js_dir)
            except OSError as e:
                self.emit("failed", self.__out_format, e)
                logging.warning("Failed to export HTML: %s", e)
                self.__active = False
                return

            dest_file = os.path.join(js_dir, os.path.basename(HtmlGenerator.RESOURCE_KATEX_JS_PATH))

            try:
                shutil.copyfile(
                    f"{self.__app_data_path}/{HtmlGenerator.RESOURCE_KATEX_JS_PATH}", dest_file
                )
            except OSError as e:
                self.emit("failed", self.__out_format, e)
                logging.warning("Failed to export HTML: %s", e)
                self.__active = False
                return

        self.__active = False
        logging.info(f"Exported {self.__out_format} to {self.__location}")
        self.emit("finished", self.__out_format, self.__location)

    def __sanitise_title_for_filename(self, title: str) -> str:
        """For synced notes the server has already done the sanitising for us. This is for
        local-only instances.
        """
        value = unicodedata.normalize("NFKC", str(title))
        return re.sub(r"[^\w\s\.-]", "", value).strip()
