import base64
import os
import re
from io import BytesIO

from django.core.files.uploadedfile import InMemoryUploadedFile
from django.urls import reverse
from PIL import Image
from wiki.core.plugins import registry as plugin_registry
from wiki.models import URLPath
from wiki.plugins.images import models
from wiki.plugins.images.wiki_plugin import ImagePlugin

from ...base import ArticleWebTestUtils
from ...base import DjangoClientTestBase
from ...base import RequireRootArticleMixin
from ...base import wiki_override_settings


class ImageTests(
    RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
):
    def setUp(self):
        super().setUp()
        self.article = self.root_article
        # A black 1x1 gif
        self.test_data = "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="

    def _create_gif_filestream_from_base64(self, str_base64, **kwargs):
        """
        Helper function to create filestream for upload.

        Parameters :
        strData : str, test string data

        Optional Arguments :
        filename : str, Defaults to 'test.txt'
        """
        filename = kwargs.get("filename", "test.gif")
        data = base64.b64decode(str_base64)
        filedata = BytesIO(data)
        filestream = InMemoryUploadedFile(
            filedata, None, filename, "image", len(data), None
        )
        return filestream

    def _create_test_image(self, path):
        # Get the form index
        plugin_index = -1
        for cnt, plugin_instance in enumerate(plugin_registry.get_sidebar()):
            if isinstance(plugin_instance, ImagePlugin):
                plugin_index = cnt
                break
        self.assertGreaterEqual(
            plugin_index, 0, msg="Image plugin not activated"
        )
        base_edit_url = reverse("wiki:edit", kwargs={"path": path})
        url = base_edit_url + f"?f=form{plugin_index:d}"
        filestream = self._create_gif_filestream_from_base64(self.test_data)
        response = self.client.post(
            url,
            {
                "unsaved_article_title": self.article.current_revision.title,
                "unsaved_article_content": self.article.current_revision.content,
                "image": filestream,
                "images_save": "1",
            },
        )
        self.assertRedirects(response, base_edit_url)

    def test_index(self):
        url = reverse("wiki:images_index", kwargs={"path": ""})
        response = self.client.get(
            url,
        )
        self.assertContains(response, "Images")

    def test_upload(self):
        """
        Tests that simple file upload uploads correctly
        Uploading a file should preserve the original filename.
        Uploading should not modify file in any way.
        """
        self._create_test_image("")
        # Check the object was created.
        image = models.Image.objects.get()
        image_revision = image.current_revision.imagerevision
        self.assertEqual(image_revision.get_filename(), "test.gif")
        self.assertEqual(
            image_revision.image.file.read(), base64.b64decode(self.test_data)
        )

    def get_article(self, cont, image):
        urlpath = URLPath.create_urlpath(
            URLPath.root(), "html_image", title="TestImage", content=cont
        )
        if image:
            self._create_test_image(urlpath.path)
        return urlpath.article.render()

    def test_image_missing(self):
        output = self.get_article("[image:1]", False)
        expected = (
            '<figure class="thumbnail"><a href="">'
            '<div class="caption"><em>Image not found</em></div>'
            '</a><figcaption class="caption"></figcaption></figure>'
        )
        self.assertEqual(output, expected)

    def test_image_default(self):
        output = self.get_article("[image:1]", True)
        image_rev = models.Image.objects.get().current_revision.imagerevision
        expected = re.compile(
            r'<figure class="thumbnail">'
            r'<a href="' + re.escape(image_rev.image.url) + '">'
            r'<img src="/?cache/.*\.jpg" alt="test\.gif">'
            r'</a><figcaption class="caption"></figcaption></figure>'
        )
        self.assertRegex(output, expected)

    def test_image_large_right(self):
        output = self.get_article("[image:1 align:right size:large]", True)
        image_rev = models.Image.objects.get().current_revision.imagerevision
        expected = re.compile(
            r'<figure class="thumbnail float-right">'
            r'<a href="' + re.escape(image_rev.image.url) + '">'
            r'<img src="/?cache/.*\.jpg" alt="test\.gif"></a>'
            r'<figcaption class="caption"></figcaption></figure>'
        )
        self.assertRegex(output, expected)

    def test_image_orig(self):
        output = self.get_article("[image:1 size:orig]", True)
        image_rev = models.Image.objects.get().current_revision.imagerevision
        expected = (
            '<figure class="thumbnail">'
            '<a href="' + image_rev.image.url + '">'
            '<img src="' + image_rev.image.url + '" alt="test.gif"></a>'
            '<figcaption class="caption"></figcaption></figure>'
        )
        self.assertEqual(output, expected)

    # https://gist.github.com/guillaumepiot/817a70706587da3bd862835c59ef584e
    def generate_photo_file(self):
        file = BytesIO()
        image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
        image.save(file, "gif")
        file.name = "test.gif"
        file.seek(0)
        return file

    def test_add_revision(self):
        self._create_test_image(path="")
        image = models.Image.objects.get()
        before_edit_rev = image.current_revision.revision_number

        response = self.client.post(
            reverse(
                "wiki:images_add_revision",
                kwargs={
                    "article_id": self.root_article,
                    "image_id": image.pk,
                    "path": "",
                },
            ),
            data={"image": self.generate_photo_file()},
        )
        self.assertRedirects(
            response, reverse("wiki:edit", kwargs={"path": ""})
        )
        image = models.Image.objects.get()
        self.assertEqual(models.Image.objects.count(), 1)
        self.assertEqual(
            image.current_revision.previous_revision.revision_number,
            before_edit_rev,
        )

    def test_delete_restore_revision(self):
        self._create_test_image(path="")
        image = models.Image.objects.get()
        before_edit_rev = image.current_revision.revision_number

        response = self.client.get(
            reverse(
                "wiki:images_delete",
                kwargs={
                    "article_id": self.root_article,
                    "image_id": image.pk,
                    "path": "",
                },
            ),
        )
        self.assertRedirects(
            response, reverse("wiki:images_index", kwargs={"path": ""})
        )
        image = models.Image.objects.get()
        self.assertEqual(models.Image.objects.count(), 1)
        self.assertEqual(
            image.current_revision.previous_revision.revision_number,
            before_edit_rev,
        )
        self.assertIs(image.current_revision.deleted, True)

        # RESTORE
        before_edit_rev = image.current_revision.revision_number
        response = self.client.get(
            reverse(
                "wiki:images_restore",
                kwargs={
                    "article_id": self.root_article,
                    "image_id": image.pk,
                    "path": "",
                },
            ),
        )
        self.assertRedirects(
            response, reverse("wiki:images_index", kwargs={"path": ""})
        )
        image = models.Image.objects.get()
        self.assertEqual(models.Image.objects.count(), 1)
        self.assertEqual(
            image.current_revision.previous_revision.revision_number,
            before_edit_rev,
        )
        self.assertFalse(image.current_revision.deleted)

    def test_purge(self):
        """
        Tests that an image is really purged
        """
        self._create_test_image(path="")
        image = models.Image.objects.get()
        image_revision = image.current_revision.imagerevision
        f_path = image_revision.image.file.name

        self.assertIs(os.path.exists(f_path), True)

        response = self.client.post(
            reverse(
                "wiki:images_purge",
                kwargs={
                    "article_id": self.root_article,
                    "image_id": image.pk,
                    "path": "",
                },
            ),
            data={"confirm": True},
        )
        self.assertRedirects(
            response, reverse("wiki:images_index", kwargs={"path": ""})
        )
        self.assertEqual(models.Image.objects.count(), 0)
        self.assertIs(os.path.exists(f_path), False)

    def test_add_revision_purge_image(self):
        """
        Tests that an image with more than one revision is really purged
        """
        # use another test to stage this one
        self.test_add_revision()

        image = models.Image.objects.get()
        image_revision = image.current_revision.imagerevision
        f_path = image_revision.image.file.name

        self.assertIs(os.path.exists(f_path), True)

        response = self.client.post(
            reverse(
                "wiki:images_purge",
                kwargs={
                    "article_id": self.root_article,
                    "image_id": image.pk,
                    "path": "",
                },
            ),
            data={"confirm": True},
        )
        self.assertRedirects(
            response, reverse("wiki:images_index", kwargs={"path": ""})
        )
        self.assertEqual(models.Image.objects.count(), 0)
        self.assertIs(os.path.exists(f_path), False)

    @wiki_override_settings(ACCOUNT_HANDLING=True)
    def test_login_on_revision_add(self):
        self._create_test_image(path="")
        self.client.logout()
        image = models.Image.objects.get()
        url = reverse(
            "wiki:images_add_revision",
            kwargs={
                "article_id": self.root_article,
                "image_id": image.pk,
                "path": "",
            },
        )
        response = self.client.post(
            url, data={"image": self.generate_photo_file()}
        )
        self.assertRedirects(
            response, "{}?next={}".format(reverse("wiki:login"), url)
        )
