import functools
from typing import Callable, Tuple, List, Union, ItemsView

import pytest

from helpers.md_and_html_convertor import MdAndHTMLConvertorLowLevel, MdAndHTMLConvertor


def _test_helper(function: Callable[[str], str], text_in: str, expected_output: str):
    function_output = function(text_in)
    assert function_output == expected_output, f"Test: {text_in} |\nOutput: \t\t{function_output} |\nExpected: \t{expected_output}"


def swap_input_with_result(data: Union[List[Tuple[str, str]], ItemsView[str, str]]) -> List[Tuple[str, str]]:
    return [(x[1], x[0]) for x in data]


# The following tests are according to spec, but won't work (that same way they don't work in scan2report).
# "***test***": "***test***",
# "****": "****",
# "**test": "**test"
# "**test**test": "**test**test"

# '\ue022' = MdAndHTMLConvertor._HTML_TO_RESERVED_CHARS["&"]

FUNC_SCAN2REPORT_CUSTOM_ESCAPED_TO_PWNDOC = MdAndHTMLConvertor.custom_md_to_pwndoc_html

FUNC_PWNDOC_TO_SCAN2REPORT_CUSTOM_ESCAPED = MdAndHTMLConvertor.html_to_custom_pwndoc_html
# FUNC_PWNDOC_TO_SCAN2REPORT_NATIVE = MdAndHTMLConvertorSimplerInterface.pwndoc_template_to_scan2report_template_native

FUNC_CUSTOM_ESCAPE_ONLY = MdAndHTMLConvertorLowLevel.custom_escape_html
FUNC_CUSTOM_UNESCAPE_ONLY = MdAndHTMLConvertorLowLevel.reverse_custom_escaping_of_html
# FUNC_MD_TO_HTML = MdAndHTMLConvertor.md_to_html


class TestLowLevelFunctions:
    @staticmethod
    def test_custom_unescaping():
        _test_helper(FUNC_CUSTOM_UNESCAPE_ONLY, '\ue022', '&')

    @staticmethod
    def test_custom_escaping():
        _test_helper(FUNC_CUSTOM_ESCAPE_ONLY, '&', '\ue022')

    @staticmethod
    @pytest.mark.parametrize("test_input, expected", [
        ("*test*", "*test*"),
        ("**test**", "<strong>test</strong>"),
        pytest.param("***test***", "<strong>test</strong>", marks=pytest.mark.xfail(reason="Md to HTML conversion is problematic.")),
        ("__test__", "<em>test</em>"),
        ("BEFORE\n\nAFTER", "BEFORE<br><br>AFTER"),
        ("test", "test"),
    ])
    def test_md_to_html(test_input: str, expected: str):
        _test_helper(MdAndHTMLConvertorLowLevel.md_to_html, test_input, expected)

    @staticmethod
    @pytest.mark.parametrize("test_input, expected", [
        ("test", "<p>test</p>"),
        ("<em>test</em>", "<p><em>test</em></p>"),
        ("<p>test</p>", "<p>test</p>"),
        ("**test**", "<p>**test**</p>"),
        ("<p>&amp;</p>", "<p>&amp;</p>"),
        ("BEFORE<br>AFTER", "<p>BEFORE<br>AFTER</p>"),
        pytest.param(FUNC_CUSTOM_ESCAPE_ONLY('<img alt="ALT_EXAMPLE" src="/test">'), '<img src="/test" alt="ALT_EXAMPLE">',
                     marks=pytest.mark.xfail(reason="The order of alt and src here is incorrect, pwndoc is sensitive to it. We don't fix order, just maintain it.")),
    ])
    def test_html_to_pwndoc_html(test_input: str, expected: str):
        _test_helper(MdAndHTMLConvertor.html_to_pwndoc_html, test_input, expected)

    @staticmethod
    @pytest.mark.parametrize("test_input", [
        "<p></p>",
        "<p>A</p><p>B</p>",
        "<p>A</p><img><p>B</p>",
        "<p>A<br></p><img><p>B</p>",
    ])
    def test_valid_html_is_not_modified(test_input: str):
        val = test_input

        for _ in range(10):
            val = MdAndHTMLConvertorLowLevel.html_to_pwndoc_html(val)

        assert val == test_input

    # todo: test html to md?


class TestHighLevelConversions:
    @staticmethod
    @pytest.mark.parametrize("test_input, expected", [
        ("", ""),
        ("**test**", "<p><strong>test</strong></p>"),
        ("<bold>test</bold>", "<p>&lt;bold&gt;test&lt;/bold&gt;</p>"),  # scan2report shouldn't contain raw HTML unless force_import_of_html_for_basic_tags is enabled
        ("&", "<p>&amp;</p>"),
        (FUNC_CUSTOM_ESCAPE_ONLY("<p>&</p>"), "<p>&amp;</p>"),  # BeautifulSoup: escapes all &, even if it's explicitly escaped. It's not intentional, but acceptable.
        (FUNC_CUSTOM_ESCAPE_ONLY('<img src="http://example.com/?a=a&arg=val">'), '<img src="http://example.com/?a=a&amp;arg=val">'),  # BeautifulSoup: & -> &amp; get modified even in URL. Browsers seem fine with it.
        (FUNC_CUSTOM_ESCAPE_ONLY('<img src="/test" alt="<ThisIsntATag>"/>'), '<img src="/test" alt="&lt;ThisIsntATag&gt;">'),         # BeautifulSoup: Src has to be before alt for pwndoc.
                                                                                                                                      # Also: I'm replacing "/>" to ">", as ">" should be escaped everywhere where it's not a tag.
        ("test\nEND", "<p>test<br>END</p>"),
        ("test\n\nEND", "<p>test</p><p>END</p>"),
        ("test\n\n\nEND", "<p>test</p><p>END</p>"),
        ("BEFORE\nMIDDLE\n\nAFTER", "<p>BEFORE<br>MIDDLE</p><p>AFTER</p>"),
    ])
    def test_scan2report_to_pwndoc(test_input: str, expected: str):
        _test_helper(FUNC_SCAN2REPORT_CUSTOM_ESCAPED_TO_PWNDOC, test_input, expected)

    @staticmethod
    @pytest.mark.parametrize("test_input", [
        "<p></p>",
        "<p><br></p>",
        "<p>BEFORE<br>MIDDLE<br>AFTER</p>",
        "<p>&amp;</p>",
        '<ul></ul>',
        '<p>before</p><img src="6352e52b5137a10013dd06f2" alt="example.jpg"><p>after</p>',
        '<p>BEFORE<br><br><br>AFTER</p>'  # if several <br> tags are inside <p> tag (or any other PwnDoc valid tag) it should not be touched
    ])
    def test_html_to_md_and_back(test_input: str):
        html = test_input

        md_text = FUNC_PWNDOC_TO_SCAN2REPORT_CUSTOM_ESCAPED(html)
        html_back = FUNC_SCAN2REPORT_CUSTOM_ESCAPED_TO_PWNDOC(md_text)
        assert html_back == html, f"\nTest (html): \t\t\t'{html}' |\nHTML -> MD: \t\t\t'{md_text}' |\nHTML -> MD -> HTML : \t'{html_back}'"

    # todo: test export to scan2report native
