# SPDX-FileCopyrightText: 2022 Chris Heywood
#
# SPDX-License-Identifier: GPL-3.0-or-later

from gi.repository import GLib

import os
import pytest
import shutil
from typing import Callable, Optional
import unittest
import warnings

from markdown_it.token import Token
from markdown_it.utils import EnvType, OptionsDict

import pypandoc

from iotas.attachment import Attachment
import iotas.attachment_helpers
from iotas.default_note_exporter import DefaultNoteExporter
from iotas.html_generator import HtmlGenerator
from iotas.note import Note
from iotas.pdf_exporter import PdfExporter


def get_output_dir() -> str:
    file_dir = os.path.dirname(__file__)
    return os.path.join(file_dir, os.pardir, "testing-tmp")


def get_attachments_dir() -> str:
    return os.path.join(get_output_dir(), "user-attachments")


iotas.attachment_helpers.get_attachments_dir = get_attachments_dir

warnings.filterwarnings("ignore", "version")


class _MockPdfExporter(PdfExporter):

    note: Note
    location: str

    def set_callbacks(self, finished_callback: Callable, error_callback: Callable) -> None:
        """Set functions to be called upon export result.

        :param Callable finished_callback: Finished callback
        :param Callable error_callback: Error callback
        """
        pass

    def export(self, note: Note, location: str) -> None:
        """Export PDF of note.

        :param Note note: Note to export
        :param str location: Destination location
        """
        self.note = note
        self.location = location


class MockHtmlGenerator(HtmlGenerator):
    """HTML generator mocked."""

    def generate(
        self,
        note: Note,
        tokens: list[Token],
        render_func: Callable[[list[Token], OptionsDict, EnvType], str],
        parser_options: OptionsDict,
        searching: bool,
        export_format: Optional[str],
        scroll_position: Optional[float] = None,
    ) -> str:
        """Generator HTML for note.

        :param Note note: Note to render
        :param list[Token] tokens: Parser tokens
        :param Callable[[list[Token], OptionsDict, EnvType], str] render_fun: Render function
        :param OptionsDict parser_options: Parser options
        :param bool searching: Whether search CSS should be included
        :param Optional[str] export_format: Export format, if using
        :param Optional[float] scroll_position: Position to scroll to
        :return: Generated HTML
        :rtype: str
        """
        return self.get_output(note)

    def generate_user_stylesheet(self, searching: bool) -> str:
        """Generate part of stylesheet based on state (configuration etc).

        :param bool searching: Whether searching
        :return: stylesheet
        :rtype: str
        """
        return ""

    def update_font_family(self, family: str) -> None:
        """Update the font family.

        :param str family: New font family
        """
        pass

    def get_output(self, note: Note) -> str:
        """Get the mocked output.

        :param Note note: The note
        :return: Generated content:
        :rtype: str
        """
        return f"{note.title}|{note.content}"


class Test(unittest.TestCase):

    exporter: DefaultNoteExporter
    pdf_exporter: _MockPdfExporter

    def test_export_md_attachments(self) -> None:
        exporter = self.__reset()
        note = Note(new_note=True)
        note.id = 1
        note.content = "![](image.png)"
        note.title = "Test Note Title"

        out_path = self.__prepare_output_dir()

        self.__create_attachment(note, "image.png")

        dir_path = os.path.join(out_path, "Test Note Title")
        file_path = os.path.join(dir_path, "Test Note Title.md")
        self.__call_and_wait(
            exporter.export,
            note,
            out_format="md",
            file_extension="md",
            supporting_tex=False,
            allow_missing_images=False,
            user_location=dir_path,
        )
        self.assertTrue(os.path.exists(file_path))
        with open(file_path, "r") as f:
            contents = f.read()
        self.assertEqual(contents, "![](attachments/image.png)")

        attachment_path = os.path.join(dir_path, "attachments", "image.png")
        self.assertTrue(os.path.exists(attachment_path))

        self.__clean_output_dir()

    def test_export_md_no_attachments(self) -> None:
        exporter = self.__reset()
        note = Note(new_note=True)
        note.id = 1
        note.content = "content"
        note.title = "Test Note Title"

        out_path = self.__prepare_output_dir()

        file_path = os.path.join(out_path, "Test Note Title.md")
        self.__call_and_wait(
            exporter.export,
            note,
            out_format="md",
            file_extension="md",
            supporting_tex=False,
            allow_missing_images=False,
            user_location=file_path,
        )
        self.assertTrue(os.path.exists(file_path))
        with open(file_path, "r") as f:
            contents = f.read()
        self.assertEqual(contents, note.content)

        self.__clean_output_dir()

    def test_export_mock_html(self) -> None:
        exporter = self.__reset()
        note = Note(new_note=True)
        note.id = 1
        note.content = "content\n\n![](image.png)"
        note.title = "Test Note Title"

        out_path = self.__prepare_output_dir()

        self.__create_attachment(note, "image.png")

        dir_path = os.path.join(out_path, "Test Note Title")
        self.__call_and_wait(
            exporter.export,
            note,
            out_format="html",
            file_extension="html",
            supporting_tex=False,
            allow_missing_images=False,
            user_location=dir_path,
        )
        index_path = os.path.join(dir_path, "index.html")
        self.assertTrue(os.path.exists(index_path))
        with open(index_path, "r") as f:
            contents = f.read()
        self.assertTrue(f"{note.content}" in contents)
        self.assertTrue(f"{note.title}" in contents)

        attachment_path = os.path.join(dir_path, "attachments", "image.png")
        self.assertTrue(os.path.exists(attachment_path))

        self.__clean_output_dir()

    def test_mock_export_pdf(self) -> None:
        exporter = self.__reset()
        note = Note(new_note=True)
        note.content = "content"
        note.title = "Test Note Title"

        out_path = self.__prepare_output_dir()

        file_path = os.path.join(out_path, "Test Note Title.pdf")
        exporter.export(
            note,
            out_format="pdf",
            file_extension="pdf",
            supporting_tex=False,
            allow_missing_images=False,
            user_location=file_path,
        )
        self.assertEqual(self.pdf_exporter.note, note)
        self.assertEqual(self.pdf_exporter.location, file_path)

        self.__clean_output_dir()

    def test_export_odt(self) -> None:
        # TODO Skipped during CI for https://gitlab.gnome.org/World/iotas/-/issues/260
        if "DISPLAY" not in os.environ:
            return pytest.skip("DISPLAY not set")

        exporter = self.__reset()
        note = Note(new_note=True)
        note.content = "content"
        note.title = "Test Note Title"

        out_path = self.__prepare_output_dir()

        file_path = os.path.join(out_path, "Test Note Title.odt")
        self.__call_and_wait(
            exporter.export,
            note,
            out_format="odt",
            file_extension="odt",
            supporting_tex=False,
            user_location=file_path,
        )
        self.assertTrue(os.path.exists(file_path))
        with open(file_path, "rb") as f:
            contents = f.read()

        md = pypandoc.convert_text(contents, to="md", format="odt")
        self.assertTrue(note.content == md.strip())

        self.__clean_output_dir()

    def __reset(self) -> DefaultNoteExporter:
        self.pdf_exporter = _MockPdfExporter()
        html_generator = MockHtmlGenerator()
        # data_path = "/mock/data/path"
        data_path = "data"
        self.exporter = DefaultNoteExporter(self.pdf_exporter, html_generator, data_path)
        self.exporter.connect("finished", self.__on_finished)
        self.exporter.connect("failed", self.__on_failed)
        return self.exporter

    def __prepare_output_dir(self) -> str:
        out_path = get_output_dir()
        if os.path.exists(out_path):
            shutil.rmtree(out_path)
        os.makedirs(out_path)
        return out_path

    def __clean_output_dir(self) -> None:
        out_path = get_output_dir()
        if os.path.exists(out_path):
            shutil.rmtree(out_path)

    def __create_attachment(self, note: Note, path: str) -> bool:
        os.makedirs(get_attachments_dir())
        attachment = Attachment()
        attachment.note_id = note.id
        attachment.path = path
        return iotas.attachment_helpers.write_attachment_to_disk(attachment, b"bytes")

    def __call_and_wait(self, call: Callable, *args, **kwargs) -> None:
        context = GLib.MainContext.default()
        self.success = False
        self.awaiting_signal = True
        call(*args, **kwargs)
        while self.awaiting_signal:
            context.iteration(False)

    def __on_finished(self, *args) -> None:
        self.awaiting_signal = False
        self.success = True

    def __on_failed(self, _obj: DefaultNoteExporter, error) -> None:
        print(f"Failed: {error}")
        self.awaiting_signal = False
