##############################################################################
#
# Copyright (c) 2007 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import os
import unittest
import unittest.mock

import zope.configuration.xmlconfig
from zope.component import provideUtility
from zope.i18n import MessageFactory
from zope.i18n.interfaces import ITranslationDomain
from zope.testing.cleanup import CleanUp

from z3c.pt import pagetemplate
from z3c.pt.pagetemplate import PageTemplateFile
from z3c.pt.pagetemplate import ViewPageTemplateFile


class Setup(CleanUp):
    def setUp(self):
        CleanUp.setUp(self)
        import z3c.pt

        zope.configuration.xmlconfig.file("configure.zcml", z3c.pt)

        domain = self._i18n_domain = unittest.mock.Mock()
        provideUtility(domain, ITranslationDomain, name="test")


class TestPageTemplate(Setup, unittest.TestCase):
    def test_string_function(self):
        template = pagetemplate.PageTemplate(
            """<div tal:content="python:string('a string')" />"""
        )
        result = template.render()
        self.assertEqual(result, "<div>a string</div>")

    def test_nocall_function(self):
        class Call:
            def __call__(self):
                raise AssertionError("Should not be called")

            def __str__(self):
                return "Not Called"

        arg = {"call": Call()}
        template = pagetemplate.PageTemplate(
            """<div tal:content="python:nocall('arg/call')" />"""
        )
        result = template.render(arg=arg)
        self.assertEqual(result, "<div>Not Called</div>")

    def test_translate_message(self):
        template = pagetemplate.PageTemplate(
            """<div i18n:domain="test" i18n:target="string:test"
            data-options="${python: m}"></div>"""
        )
        self._i18n_domain.translate.return_value = "world"
        result = template.render(m=MessageFactory("test")("hello"))
        self.assertIn("world", result)

    def test_translate_non_message(self):
        template = pagetemplate.PageTemplate(
            """<div i18n:domain="test" i18n:target="string:test"
            data-options="${python: p}"></div>"""
        )
        p = [1, 2, 3]
        self._i18n_domain.translate.side_effect = AssertionError()
        result = template.render(p=p)
        self.assertIn(repr(p), result)

    def test_structure(self):
        template = pagetemplate.PageTemplate(
            "${structure: python: '&lt;div&gt;Hello world&lt;/div&gt;'}"
        )
        result = template.render()
        self.assertEqual(result, "<div>Hello world</div>")


class TestPageTemplateFile(Setup, unittest.TestCase):
    def test_nocall(self):
        template = PageTemplateFile("nocall.pt")

        def dont_call():
            raise AssertionError("Should not be called")

        result = template(callable=dont_call)
        self.assertIn(repr(dont_call), result)

    def test_exists(self):
        template = PageTemplateFile("exists.pt")

        def dont_call():
            raise AssertionError("Should not be called")

        result = template(callable=dont_call)
        self.assertIn("ok", result)

    def test_false_attribute(self):
        template = PageTemplateFile("false.pt")
        result = template()
        self.assertIn("False", result)

    def test_boolean_attribute(self):
        template = PageTemplateFile("boolean.pt")
        result = template()
        self.assertNotIn("False", result)
        self.assertIn('checked="checked"', result)

    def test_path(self):
        template = PageTemplateFile("path.pt")

        class Context:
            dummy_wysiwyg_support = True

        context = Context()
        template = template.__get__(context, Context)

        result = template(editor="dummy")
        self.assertIn("supported", result)
        self.assertIn("some path", result)


class TestViewPageTemplateFile(Setup, unittest.TestCase):
    def test_provider(self):
        class Context:
            pass

        class Request:
            response = None

        class View:
            __call__ = ViewPageTemplateFile("provider.pt")

        # Test binding descriptor behaviour.
        self.assertIsInstance(View.__call__, ViewPageTemplateFile)

        from zope.contentprovider.interfaces import ITALNamespaceData
        from zope.interface import Interface
        from zope.interface import directlyProvides
        from zope.interface import implementer
        from zope.schema import Field

        class ITestProvider(Interface):
            context = Field("Provider context.")

        directlyProvides(ITestProvider, ITALNamespaceData)
        assert ITALNamespaceData.providedBy(ITestProvider)

        @implementer(ITestProvider)
        class Provider:
            def __init__(self, *args):
                data.extend(list(args))

            def update(self):
                data.extend("updated")

            def render(self):
                return f"<![CDATA[ {data!r}, {self.__dict__!r}]]>"

        view = View()
        data = []

        from zope.component import provideAdapter
        from zope.contentprovider.interfaces import IContentProvider
        from zope.interface import implementedBy

        provideAdapter(
            Provider,
            (
                implementedBy(Context),
                implementedBy(Request),
                implementedBy(View),
            ),
            IContentProvider,
            name="content",
        )

        context = Context()
        request = Request()

        result = view(context=context, request=request)
        self.assertIn(repr(data), result)
        self.assertIn(repr({"context": context}), result)


class TestOpaqueDict(unittest.TestCase):
    def test_getitem(self):
        import operator

        d = {}
        od = pagetemplate.OpaqueDict(d)
        with self.assertRaises(KeyError):
            operator.itemgetter("key")(od)

        d["key"] = 42
        self.assertEqual(od["key"], 42)

    def test_len(self):
        d = {}
        od = pagetemplate.OpaqueDict(d)
        self.assertEqual(0, len(od))

        d["key"] = 42
        self.assertEqual(1, len(od))

    def test_repr(self):
        d = {}
        od = pagetemplate.OpaqueDict(d)
        self.assertEqual("{...} (0 entries)", repr(od))

        d["key"] = 42
        self.assertEqual("{...} (1 entries)", repr(od))


class TestBaseTemplate(unittest.TestCase):
    def test_negotiate_fails(self):
        class I18N:
            request = None

            def negotiate(self, request):
                self.request = request
                raise Exception("This is caught")

        i18n = I18N()
        orig_i18n = pagetemplate.i18n
        pagetemplate.i18n = i18n
        try:
            template = pagetemplate.BaseTemplate("<html />")
            request = "strings are allowed"
            template.render(request=request)
            self.assertIs(i18n.request, request)
        finally:
            pagetemplate.i18n = orig_i18n

    def test_translate_mv(self):
        template = pagetemplate.BaseTemplate(
            """
        <html>
          <body metal:use-macro="m" />
        </html>
        """
        )

        class Macro:
            translate = None

            def include(self, stream, econtext, *args, **kwargs):
                self.translate = econtext["translate"]

        macro = Macro()
        template.render(m=macro)

        self.assertIsNone(macro.translate(pagetemplate.MV))


class TestBaseTemplateFile(unittest.TestCase):
    def test_init_with_path(self):

        here = os.path.abspath(os.path.dirname(__file__))

        template = pagetemplate.BaseTemplateFile("view.pt", path=here)

        self.assertEqual(template.filename, os.path.join(here, "view.pt"))


class TestBoundPageTemplate(unittest.TestCase):

    def test_setattr(self):
        bound = pagetemplate.BoundPageTemplate(None, None)
        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
            bound.__self__ = 42

    def test_repr(self):
        # It requires the 'filename' attribute
        class Template:
            filename = "file.pt"

        bound = pagetemplate.BoundPageTemplate(Template(), "render")
        self.assertEqual(
            "<z3c.pt.tests.test_templates.BoundTemplate 'file.pt'>",
            repr(bound),
        )

    def test_attributes(self):
        func = object()
        bound = pagetemplate.BoundPageTemplate(self, func)
        self.assertIs(self, bound.im_self)
        self.assertIs(self, bound.__self__)
        self.assertIs(func, bound.im_func)
        self.assertIs(func, bound.__func__)
