from hashlib import md5
from importlib.resources import files
from io import StringIO
from plone import api
from plone.app.testing import login
from plone.app.testing.interfaces import SITE_OWNER_NAME
from plone.app.textfield.value import RichTextValue
from plone.protect.authenticator import createToken
from ploneintranet import api as pi_api
from ploneintranet.docconv.client.decorators import force_synchronous_pdf
from ploneintranet.docconv.client.handlers import ffmpegthumbnailer
from ploneintranet.docconv.client.testing import IntegrationTestCase
from ploneintranet.testing import dummy_file
from unittest import mock
from zope.annotation import IAnnotations
from zope.globalrequest import setRequest

import unittest


class TestDocconvViews(IntegrationTestCase):
    """Test views for ploneintranet.docconv.client"""

    def setUp(self):
        """Custom shared utility setup for tests."""
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        login(self.portal.aq_parent, SITE_OWNER_NAME)

    def get_blob(self):
        """Returns a blob with a previewable pdf"""
        return dummy_file(filename="test.pdf")

    def get_blob_doc(self):
        """Returns a blob with a previewable doc"""
        return dummy_file(filename="test.docx")

    def get_request(self, params={}):
        """Return a fresh request"""
        request = self.request.clone()
        request.form.update(params)
        # Keep standard output clean
        request.response.stdout = StringIO()
        return request

    def get_preview_hashes_for(self, obj):
        """Gets the preview blob md5 hashes

        This is used to verify if the previews have been regenerated
        """
        annotations = IAnnotations(obj)
        blobs = list(annotations["ploneintranet.docconv.client"]["blob_files"].values())

        hashes = []
        for blob in blobs:
            with blob.open() as f:
                hashes.append(md5(f.read()).hexdigest())
        return hashes

    def test_regenerate_views_csrf(self):
        """Check if the view correctly requires the CSRF _authenticator"""
        view = api.content.get_view(
            "generate-pdf-for-contents", self.portal, self.get_request()
        )
        self.assertIn("requires confirmation", view())

        view = api.content.get_view(
            "generate-pdf-for-contents-async", self.portal, self.get_request()
        )
        self.assertIn("requires confirmation", view())

    def test_regenerate_sync_view(self):
        """
        Test the views that regenerate the previews synchronously
        """
        file1 = api.content.create(self.portal, id="file1", type="File")
        # We have no file => no previews
        self.assertFalse(pi_api.previews.has_previews(file1))

        # We add a file => we want previews!
        file1.file = self.get_blob()
        view = api.content.get_view(
            "generate-pdf-for-contents",
            self.portal,
            self.get_request({"_authenticator": createToken()}),
        )
        view()
        self.assertTrue(pi_api.previews.has_previews(file1))

    @force_synchronous_pdf
    def test_regenerate_async_view(self):
        """
        Test the views that regenerate the pdf asynchronously

        We need the force_synchronous_pdf decorator
        for testing purposes
        """
        file2 = api.content.create(self.portal, id="file2", type="File")
        # We have no file => no previews
        self.assertFalse(pi_api.previews.has_previews(file2))

        # We add a file => we want previews!
        file2.file = self.get_blob()
        view = api.content.get_view(
            "generate-pdf-for-contents",
            self.portal,
            self.get_request({"_authenticator": createToken()}),
        )
        view()
        self.assertTrue(pi_api.previews.has_previews(file2))

    def test_force_regenerate_sync_view(self):
        """
        Test we can regenerate existing previews
        """
        file3 = api.content.create(
            self.portal, id="file3", type="File", file=self.get_blob_doc()
        )
        # the time of the original preview generation
        original_ids = self.get_preview_hashes_for(file3)
        # Calling the view without the generate parameter will not force
        # The preview regeneration
        view = api.content.get_view(
            "generate-pdf-for-contents",
            self.portal,
            self.get_request({"_authenticator": createToken()}),
        )
        view()
        self.assertEqual(self.get_preview_hashes_for(file3), original_ids)

        # Adding a truish regenerate value to the request will
        view = api.content.get_view(
            "generate-pdf-for-contents",
            self.portal,
            self.get_request({"_authenticator": createToken(), "purge": True}),
        )
        setRequest(view.request)
        view()
        self.assertNotEqual(self.get_preview_hashes_for(file3), original_ids)

    def test_pdf_view(self):
        """
        Test the PDF view that serves the PDF version of HTML content
        """
        document = api.content.create(self.portal, id="Doc1", type="Document")
        raw_text = "¯\\_(ツ)_//¯<br/>"
        richtext = RichTextValue(
            raw=raw_text, mimeType="text/plain", outputMimeType="text/x-html-safe"
        )
        document.text = richtext
        # Note that generate_pdf is optimised to it already checks if
        # there is new data in the request. So just notifying ObjectModified
        # will not trigger preview generation.
        # For this test, I call it manually.
        pi_api.previews.generate_pdf(document)
        view = document.restrictedTraverse("pdf")
        self.assertTrue(view.has_pdf())
        self.assertTrue(len(view.get_pdf()) > 0)

    def test_poster_jpg(self):
        obj = api.content.create(
            self.portal, id="video", type="File", file=dummy_file("test.mp4")
        )
        view = api.content.get_view("poster.jpg", obj, self.request.clone())
        self.assertEqual(
            view(),
            "http://nohost/plone/++theme++ploneintranet.theme/generated/media/logos/plone-intranet-square.svg",  # noqa: E501
        )

    @unittest.skipUnless(
        ffmpegthumbnailer, "Requires the ffmpegthumbnailer command to be present"
    )
    def test_generate_poster(self):
        obj = api.content.create(
            self.portal, id="video", type="File", file=dummy_file("test.mp4")
        )
        # Initially we serve a default preview
        serve_view = api.content.get_view("poster.jpg", obj, self.request.clone())
        self.assertEqual(
            serve_view(),
            "http://nohost/plone/++theme++ploneintranet.theme/generated/media/logos/plone-intranet-square.svg",  # noqa: E501
        )

        # But we can generate one
        generate_view = api.content.get_view(
            "generate-poster", obj, self.request.clone()
        )
        # Actually we fake, we trust the command will work
        self.assertEqual(generate_view.generate_poster(), "OK")

        # We reset the caches
        serve_view.request = self.request.clone()
        self.assertAlmostEqual(len(serve_view()), 17232)
        self.assertSetEqual(
            set(serve_view.request.response.headers),
            {
                "accept-ranges",
                "last-modified",
                "content-disposition",
                "content-length",
                "content-type",
            },
        )

    def test_generate_lead_image(self):
        obj = api.content.create(
            self.portal,
            id="video-link",
            type="Link",
            remoteUrl="https://example.com/test.mp4",
        )
        self.assertIsNone(getattr(obj, "image", None))

        # Get the view that generates the lead image
        view = api.content.get_view("generate-lead-image", obj, self.request.clone())
        # Mock the command and the generated file
        view.command = mock.Mock()
        view.arguments[-1] = str(files("ploneintranet.tests") / "plone.jpg")

        self.assertEqual(view(), "OK")
        self.assertEqual(view.command.call_count, 1)
        self.assertEqual(obj.image.filename, "poster.jpg")
        self.assertEqual(obj.image.contentType, "image/jpeg")
        self.assertEqual(obj.image._from_url, "https://example.com/test.mp4")
        self.assertEqual(obj.image.getSize(), 5131)

        # If called again the lead image is not generated again
        self.assertEqual(view(), "OK")
        self.assertEqual(view.command.call_count, 1)

        # Unless we change the remoteUrl
        obj.remoteUrl = "https://example.com/test.avi"
        view()
        self.assertEqual(view.command.call_count, 2)
        self.assertEqual(obj.image._from_url, "https://example.com/test.avi")

        # If the image has no _from_url we expect it was intentionally
        # uploaded by the user so we do not try the autogeneration
        delattr(obj.image, "_from_url")
        view()
        self.assertEqual(view.command.call_count, 2)
