import contextlib
import doctest
import importlib.metadata
import logging
import tempfile
from os.path import basename
from string import Template as Niks

from django.conf import settings
from django.core.files import File
from django.template import Context, Template
from django.test import TestCase, override_settings
from bs4 import BeautifulSoup
from PIL import Image, ImageDraw

import ocyan.main.templateloaders
import ocyan.main.templatetags.ocyan_main
from ocyan.main.template import OcyanDjangoTemplates

logger = logging.getLogger()

TEST_TEMPLATE = Niks("""
{% load ocyan_thumbnail %}
{% $tag pic "200" slug="$slug" alt="dummy" classes="dummy henk" %}
""")


def load_tests(loader, tests, ignore):  # pylint: disable=W0613
    tests.addTests(doctest.DocTestSuite(ocyan.main.templateloaders))
    tests.addTests(doctest.DocTestSuite(ocyan.main.templatetags.ocyan_main))
    return tests


class OcyanMainTestCase(TestCase):
    params = {
        "NAME": "",
        "DIRS": [],
        "APP_DIRS": [],
        "OPTIONS": {"loaders": ["henk", "jos"]},
    }

    def test_cached_template_loaders_non_debug(self):
        # In non-debug we should use django.template.loaders.cached.Loader
        self.params["OPTIONS"]["debug"] = False
        backend = OcyanDjangoTemplates(params=self.params)
        self.assertEqual(
            backend.engine.loaders,
            [("django.template.loaders.cached.Loader", ["henk", "jos"])],
        )

    def test_cached_template_loaders_debug(self):
        # In debug we should *not* use django.template.loaders.cached.Loader
        self.params["OPTIONS"]["debug"] = True
        backend = OcyanDjangoTemplates(params=self.params)
        self.assertEqual(backend.engine.loaders, ["henk", "jos"])

    def test_use_ocyan_backend(self):
        """Make sure we use *our* backend, so we'll get caching"""
        for definition in settings.TEMPLATES:
            if not definition["BACKEND"].endswith("DjangoTemplates"):
                continue
            self.assertEqual(
                definition["BACKEND"], "ocyan.main.template.OcyanDjangoTemplates"
            )

    def test_distribution_metadata(self):
        self.assertEqual(importlib.metadata.version("ocyan.main"), "2.0.1")

    def test_namespace_uses_stdlib_implementation(self):
        import ocyan

        self.assertNotIn("pkg_resources", ocyan.__dict__)


@contextlib.contextmanager
def dummy_image(text, static=False):
    with tempfile.NamedTemporaryFile(
        dir=settings.TEST_STATIC_SOURCE if static else settings.MEDIA_ROOT,
        suffix=".png",
    ) as thefile:
        img = Image.new("RGB", (800, 400), color=(73, 109, 137))

        d = ImageDraw.Draw(img)
        d.text((10, 10), text, fill=(255, 255, 0))

        img.save(thefile, "PNG")
        thefile.seek(0)
        yield File(thefile.file, thefile.name)


@override_settings(
    THUMBNAIL_DEBUG=True,
    CACHES={"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}},
)
class OcyanThumbnailTestCase(TestCase):
    maxDiff = None

    def assert_picture_markup(
        self, result, *, lazy=False, owl=False, width="200", height="100"
    ):
        soup = BeautifulSoup(result, "html.parser")
        picture = soup.find("picture")
        self.assertIsNotNone(picture)

        source_name = "data-src" if lazy else "source"
        source_attribute = "data-srcset" if owl else "srcset"
        sources = picture.find_all(source_name)
        self.assertEqual(len(sources), 2)
        self.assertEqual(sources[0].get("type"), "image/webp")
        for source in sources:
            srcset = source.get(source_attribute, "")
            self.assertTrue(srcset.startswith("/media/cache/"))
            self.assertEqual(srcset.count(","), 2)

        image_name = "data-img" if lazy else "img"
        image_attribute = "data-src" if owl else "src"
        image = picture.find(image_name)
        self.assertIsNotNone(image)
        self.assertTrue(image.get(image_attribute, "").startswith("/media/cache/"))
        self.assertEqual(image.get("width"), width)
        self.assertEqual(image.get("height"), height)
        self.assertEqual(image.get("alt"), "dummy")
        self.assertTrue({"dummy", "henk"}.issubset(set(image.get("class", []))))

        if lazy:
            self.assertIn("lazy", picture.get("class", []))
        if owl:
            self.assertIn("owl-lazy", image.get("class", []))

    def test_picture(self):
        with dummy_image("picture") as pic:
            tpl = Template(TEST_TEMPLATE.substitute(tag="picture", slug="picture"))
            ctx = Context({"pic": pic})
            result = tpl.render(ctx)
            pic.close()

        self.assert_picture_markup(result)

    def test_lazy_picture(self):
        with dummy_image("lazy_picture") as pic:
            tpl = Template(
                TEST_TEMPLATE.substitute(tag="lazy_picture", slug="lazy_picture")
            )
            ctx = Context({"pic": pic})
            result = tpl.render(ctx)

        self.assert_picture_markup(result, lazy=True)

    def test_responsive_image(self):
        with dummy_image("responsive_image") as pic:
            tpl = Template(
                """{% load ocyan_thumbnail %}{% responsive_image pic slug="responsive_image" alt="dummy" classes="dummy henk" %}"""
            )
            ctx = Context({"pic": pic})
            result = tpl.render(ctx)

        self.assert_picture_markup(result, width="800", height="400")

    def test_lazy_responsive_image(self):
        with dummy_image("lazy_responsive_image") as pic:
            tpl = Template(
                """{% load ocyan_thumbnail %}{% lazy_responsive_image pic slug="lazy_responsive_image" alt="dummy" classes="dummy henk" %}"""
            )
            ctx = Context({"pic": pic})
            result = tpl.render(ctx)

        self.assert_picture_markup(result, lazy=True, width="800", height="400")

    def test_lazy_owl_picture(self):
        with dummy_image("lazy_owl_picture") as pic:
            tpl = Template(
                TEST_TEMPLATE.substitute(
                    tag="lazy_owl_picture", slug="lazy_owl_picture"
                )
            )
            ctx = Context({"pic": pic})
            result = tpl.render(ctx)

        self.assert_picture_markup(result, owl=True)

    def test_static_picture(self):
        with dummy_image("static_picture", static=True) as pic:
            tpl = Template(
                TEST_TEMPLATE.substitute(tag="static_picture", slug="static_picture")
            )
            ctx = Context({"pic": basename(pic.name)})
            result = tpl.render(ctx)

        self.assert_picture_markup(result)

    def test_static_lazy_picture(self):
        with dummy_image("static_lazy_picture", static=True) as pic:
            tpl = Template(
                TEST_TEMPLATE.substitute(
                    tag="static_lazy_picture", slug="static_lazy_picture"
                )
            )
            ctx = Context({"pic": basename(pic.name)})
            result = tpl.render(ctx)

        self.assert_picture_markup(result, lazy=True)
