from functools import partial

from django.test import TestCase
from django.utils.safestring import SafeString

from wagtail.admin import compare
from wagtail.core.blocks import StreamValue
from wagtail.images import get_image_model
from wagtail.images.tests.utils import get_test_image_file
from wagtail.tests.testapp.models import (
    AdvertWithCustomPrimaryKey, EventCategory, EventPage, EventPageSpeaker,
    HeadCountRelatedModelUsingPK, SimplePage, SnippetChooserModelWithCustomPrimaryKey,
    StreamPage, TaggedPage)


class TestFieldComparison(TestCase):
    comparison_class = compare.FieldComparison

    def test_hasnt_changed(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="Content"),
            SimplePage(content="Content"),
        )

        self.assertTrue(comparison.is_field)
        self.assertFalse(comparison.is_child_relation)
        self.assertEqual(comparison.field_label(), "Content")
        self.assertEqual(comparison.htmldiff(), 'Content')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertFalse(comparison.has_changed())

    def test_has_changed(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="Original content"),
            SimplePage(content="Modified content"),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">Modified content</span>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_htmldiff_escapes_value(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content='Original content'),
            SimplePage(content='<script type="text/javascript">doSomethingBad();</script>'),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">&lt;script type=&quot;text/javascript&quot;&gt;doSomethingBad();&lt;/script&gt;</span>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)


class TestTextFieldComparison(TestFieldComparison):
    comparison_class = compare.TextFieldComparison

    # Only change from FieldComparison is the HTML diff is performed on words
    # instead of the whole field value.
    def test_has_changed(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="Original content"),
            SimplePage(content="Modified content"),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_from_none_to_value_only_shows_addition(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content=None),
            SimplePage(content="Added content")
        )

        self.assertEqual(comparison.htmldiff(), '<span class="addition">Added content</span>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_from_value_to_none_only_shows_deletion(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="Removed content"),
            SimplePage(content=None)
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Removed content</span>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())


class TestRichTextFieldComparison(TestFieldComparison):
    comparison_class = compare.RichTextFieldComparison

    # Only change from FieldComparison is the HTML diff is performed on words
    # instead of the whole field value.
    def test_has_changed(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="Original content"),
            SimplePage(content="Modified content"),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    # Only change from FieldComparison is that this comparison disregards HTML tags
    def test_has_changed_html(self):
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content="<b>Original</b> content"),
            SimplePage(content="Modified <i>content</i>"),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_htmldiff_escapes_value(self):
        # Need to override this one as the HTML tags are stripped by RichTextFieldComparison
        comparison = self.comparison_class(
            SimplePage._meta.get_field('content'),
            SimplePage(content='Original content'),
            SimplePage(content='<script type="text/javascript">doSomethingBad();</script>'),
        )

        self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">doSomethingBad();</span>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)


class TestStreamFieldComparison(TestCase):
    comparison_class = compare.StreamFieldComparison

    def test_hasnt_changed(self):
        field = StreamPage._meta.get_field('body')

        comparison = self.comparison_class(
            field,
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
            ])),
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
            ])),
        )

        self.assertTrue(comparison.is_field)
        self.assertFalse(comparison.is_child_relation)
        self.assertEqual(comparison.field_label(), "Body")
        self.assertEqual(comparison.htmldiff(), '<div class="comparison__child-object">Content</div>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertFalse(comparison.has_changed())

    def test_has_changed(self):
        field = StreamPage._meta.get_field('body')

        comparison = self.comparison_class(
            field,
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Original content", '1'),
            ])),
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Modified content", '1'),
            ])),
        )

        self.assertEqual(comparison.htmldiff(), '<div class="comparison__child-object"><span class="deletion">Original</span><span class="addition">Modified</span> content</div>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_add_block(self):
        field = StreamPage._meta.get_field('body')

        comparison = self.comparison_class(
            field,
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
            ])),
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
                ('text', "New Content", '2'),
            ])),
        )

        self.assertEqual(comparison.htmldiff(), '<div class="comparison__child-object">Content</div>\n<div class="comparison__child-object addition">New Content</div>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_delete_block(self):
        field = StreamPage._meta.get_field('body')

        comparison = self.comparison_class(
            field,
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
                ('text', "Content Foo", '2'),
                ('text', "Content Bar", '3'),
            ])),
            StreamPage(body=StreamValue(field.stream_block, [
                ('text', "Content", '1'),
                ('text', "Content Bar", '3'),
            ])),
        )

        self.assertEqual(comparison.htmldiff(), '<div class="comparison__child-object">Content</div>\n<div class="comparison__child-object deletion">Content Foo</div>\n<div class="comparison__child-object">Content Bar</div>')
        self.assertIsInstance(comparison.htmldiff(), SafeString)
        self.assertTrue(comparison.has_changed())

    def test_edit_block(self):
        field = StreamPage._meta.get_field('body')

        comparison = self.comparison_class(
            field,