import json
from unittest.mock import patch

from django.test import TestCase
from draftjs_exporter.dom import DOM
from draftjs_exporter.html import HTML as HTMLExporter

from wagtail.admin.rich_text.converters.contentstate import (
    ContentstateConverter,
    persist_key_for_block,
)
from wagtail.embeds.models import Embed


def content_state_equal(v1, v2, match_keys=False):
    "Test whether two contentState structures are equal, ignoring 'key' properties if match_keys=False"
    if type(v1) != type(v2):
        return False

    if isinstance(v1, dict):
        if set(v1.keys()) != set(v2.keys()):
            return False
        return all(
            (k == "key" and not match_keys)
            or content_state_equal(v, v2[k], match_keys=match_keys)
            for k, v in v1.items()
        )
    elif isinstance(v1, list):
        if len(v1) != len(v2):
            return False
        return all(
            content_state_equal(a, b, match_keys=match_keys) for a, b in zip(v1, v2)
        )
    else:
        return v1 == v2


class TestHtmlToContentState(TestCase):
    fixtures = ["test.json"]

    def assertContentStateEqual(self, v1, v2, match_keys=False):
        "Assert that two contentState structures are equal, ignoring 'key' properties if match_keys is False"
        self.assertTrue(
            content_state_equal(v1, v2, match_keys=match_keys),
            "%s does not match %s"
            % (json.dumps(v1, indent=4), json.dumps(v2, indent=4)),
        )

    def test_paragraphs(self):
        converter = ContentstateConverter(features=[])
        result = json.loads(
            converter.from_database_format(
                """
            <p data-block-key='00000'>Hello world!</p>
            <p data-block-key='00001'>Goodbye world!</p>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "Hello world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "Goodbye world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00001",
                        "entityRanges": [],
                    },
                ],
            },
            match_keys=True,
        )

    def test_unknown_block_becomes_paragraph(self):
        converter = ContentstateConverter(features=[])
        result = json.loads(
            converter.from_database_format(
                """
            <foo>Hello world!</foo>
            <foo>I said hello world!</foo>
            <p>Goodbye world!</p>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "Hello world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "I said hello world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "Goodbye world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_bare_text_becomes_paragraph(self):
        converter = ContentstateConverter(features=[])
        result = json.loads(
            converter.from_database_format(
                """
            before
            <p>paragraph</p>
            between
            <p>paragraph</p>
            after
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "before",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "paragraph",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "between",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "paragraph",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "after",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_ignore_unrecognised_tags_in_blocks(self):
        converter = ContentstateConverter(features=[])
        result = json.loads(
            converter.from_database_format(
                """
            <p>Hello <foo>frabjuous</foo> world!</p>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "Hello frabjuous world!",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_inline_styles(self):
        converter = ContentstateConverter(features=["bold", "italic"])
        result = json.loads(
            converter.from_database_format(
                """
            <p>You <b>do <em>not</em> talk</b> about Fight Club.</p>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [
                            {"offset": 4, "length": 11, "style": "BOLD"},
                            {"offset": 7, "length": 3, "style": "ITALIC"},
                        ],
                        "text": "You do not talk about Fight Club.",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_inline_styles_at_top_level(self):
        converter = ContentstateConverter(features=["bold", "italic"])
        result = json.loads(
            converter.from_database_format(
                """
            You <b>do <em>not</em> talk</b> about Fight Club.
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [
                            {"offset": 4, "length": 11, "style": "BOLD"},
                            {"offset": 7, "length": 3, "style": "ITALIC"},
                        ],
                        "text": "You do not talk about Fight Club.",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_inline_styles_at_start_of_bare_block(self):
        converter = ContentstateConverter(features=["bold", "italic"])
        result = json.loads(
            converter.from_database_format(
                """<b>Seriously</b>, stop talking about <i>Fight Club</i> already."""
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [
                            {"offset": 0, "length": 9, "style": "BOLD"},
                            {"offset": 30, "length": 10, "style": "ITALIC"},
                        ],
                        "text": "Seriously, stop talking about Fight Club already.",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_inline_styles_depend_on_features(self):
        converter = ContentstateConverter(features=["italic", "just-made-it-up"])
        result = json.loads(
            converter.from_database_format(
                """
            <p>You <b>do <em>not</em> talk</b> about Fight Club.</p>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [
                            {"offset": 7, "length": 3, "style": "ITALIC"}
                        ],
                        "text": "You do not talk about Fight Club.",
                        "depth": 0,
                        "type": "unstyled",
                        "key": "00000",
                        "entityRanges": [],
                    },
                ],
            },
        )

    def test_ordered_list(self):
        converter = ContentstateConverter(features=["h1", "ol", "bold", "italic"])
        result = json.loads(
            converter.from_database_format(
                """
            <h1 data-block-key='00000'>The rules of Fight Club</h1>
            <ol>
                <li data-block-key='00001'>You do not talk about Fight Club.</li>
                <li data-block-key='00002'>You <b>do <em>not</em> talk</b> about Fight Club.</li>
            </ol>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "The rules of Fight Club",
                        "depth": 0,
                        "type": "header-one",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "You do not talk about Fight Club.",
                        "depth": 0,
                        "type": "ordered-list-item",
                        "key": "00001",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [
                            {"offset": 4, "length": 11, "style": "BOLD"},
                            {"offset": 7, "length": 3, "style": "ITALIC"},
                        ],
                        "text": "You do not talk about Fight Club.",
                        "depth": 0,
                        "type": "ordered-list-item",
                        "key": "00002",
                        "entityRanges": [],
                    },
                ],
            },
            match_keys=True,
        )

    def test_nested_list(self):
        converter = ContentstateConverter(features=["h1", "ul"])
        result = json.loads(
            converter.from_database_format(
                """
            <h1 data-block-key='00000'>Shopping list</h1>
            <ul>
                <li data-block-key='00001'>Milk</li>
                <li data-block-key='00002'>
                    Flour
                    <ul>
                        <li data-block-key='00003'>Plain</li>
                        <li data-block-key='00004'>Self-raising</li>
                    </ul>
                </li>
                <li data-block-key='00005'>Eggs</li>
            </ul>
            """
            )
        )
        self.assertContentStateEqual(
            result,
            {
                "entityMap": {},
                "blocks": [
                    {
                        "inlineStyleRanges": [],
                        "text": "Shopping list",
                        "depth": 0,
                        "type": "header-one",
                        "key": "00000",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "Milk",
                        "depth": 0,
                        "type": "unordered-list-item",
                        "key": "00001",
                        "entityRanges": [],
                    },
                    {
                        "inlineStyleRanges": [],
                        "text": "Flour",
                        "depth": 0,
                        "type": "unordered-list-item",