# coding: utf-8
from __future__ import unicode_literals

import datetime

from django.test import SimpleTestCase, override_settings
from django.test.utils import requires_tz_support
from django.utils import timezone, translation
from jinja2 import Environment, TemplateSyntaxError
from jinja2.ext import Extension

from jdj_tags.extensions import (DjangoCompat, DjangoCsrf, DjangoI18n, DjangoL10n, DjangoNow,
                                 DjangoStatic, DjangoUrl)

try:
    from unittest import mock
except ImportError:
    import mock


class DjangoCsrfTest(SimpleTestCase):
    def setUp(self):
        self.env = Environment(extensions=[DjangoCsrf])
        self.template = self.env.from_string("{% csrf_token %}")

    def test_token(self):
        context = {'csrf_token': 'a_csrf_token'}
        expected = '<input type="hidden" name="csrfmiddlewaretoken" value="a_csrf_token" />'

        self.assertEqual(expected, self.template.render(context))

    def test_empty_token(self):
        context1 = {}
        context2 = {'csrf_token': 'NOTPROVIDED'}

        self.assertEqual('', self.template.render(context1))
        self.assertEqual('', self.template.render(context2))


class DjangoI18nTestBase(SimpleTestCase):
    @staticmethod
    def _gettext(message):
        return '{} - translated'.format(message)

    @staticmethod
    def _pgettext(context, message):
        return 'alt translated'

    @staticmethod
    def _ngettext(singular, plural, number):
        return 'count translated'

    @staticmethod
    def _npgettext(context, singular, plural, number):
        return 'count alt translated'

    def setUp(self):
        gettext_patcher = mock.patch('jdj_tags.extensions.ugettext', side_effect=self._gettext)
        pgettext_patcher = mock.patch('jdj_tags.extensions.pgettext', side_effect=self._pgettext)
        ngettext_patcher = mock.patch('jdj_tags.extensions.ungettext', side_effect=self._ngettext)
        npgettext_patcher = mock.patch('jdj_tags.extensions.npgettext', side_effect=self._npgettext)

        self.gettext = gettext_patcher.start()
        self.pgettext = pgettext_patcher.start()
        self.ngettext = ngettext_patcher.start()
        self.npgettext = npgettext_patcher.start()

        self.addCleanup(gettext_patcher.stop)
        self.addCleanup(pgettext_patcher.stop)
        self.addCleanup(ngettext_patcher.stop)
        self.addCleanup(npgettext_patcher.stop)

        self.env = Environment(extensions=[DjangoI18n])


class DjangoI18nTransTest(DjangoI18nTestBase):
    def test_simple_trans(self):
        template1 = self.env.from_string("{% trans 'Hello World' %}")
        template2 = self.env.from_string(
            "{% trans 'Hello World' context 'some context' %}"
        )
        template3 = self.env.from_string("{{ _('Hello World') }}")
        template4 = self.env.from_string("{{ gettext('Hello World') }}")
        template5 = self.env.from_string("{{ pgettext('some context', 'Hello World') }}")

        self.assertEqual('Hello World - translated', template1.render())
        self.gettext.assert_called_with('Hello World')
        self.assertEqual('alt translated', template2.render())
        self.pgettext.assert_called_with('some context', 'Hello World')
        self.assertEqual('Hello World - translated', template3.render())
        self.gettext.assert_called_with('Hello World')
        self.assertEqual('Hello World - translated', template4.render())
        self.gettext.assert_called_with('Hello World')
        self.assertEqual('alt translated', template5.render())
        self.pgettext.assert_called_with('some context', 'Hello World')

    def test_noop(self):
        template = self.env.from_string("{% trans 'Hello World' noop %}")

        self.assertEqual('Hello World', template.render())
        self.gettext.assert_not_called()

    def test_as_var(self):
        template = self.env.from_string(
            "{% trans 'Hello World' as myvar %}My var is: {{ myvar }}!"
        )

        self.assertEqual('My var is: Hello World - translated!', template.render())
        self.gettext.assert_called_with('Hello World')

    def test_noop_as_var(self):
        template1 = self.env.from_string(
            "{% trans 'Hello World' noop as myvar %}My var is: {{ myvar }}!"
        )
        template2 = self.env.from_string(
            "{% trans 'Hello World' as myvar noop %}My var is: {{ myvar }}!"
        )

        expected_str = 'My var is: Hello World!'

        self.assertEqual(expected_str, template1.render())
        self.gettext.assert_not_called()
        self.assertEqual(expected_str, template2.render())
        self.gettext.assert_not_called()

    def test_errors(self):
        template1 = "{% trans 'Hello World' foo %}"
        template2 = "{% trans 'Hello World' noop context 'some context' %}"
        template3 = "{% trans 'Hello World' context 'some context' noop %}"

        error_messages = [
            (template1, "expected 'noop', 'context' or 'as'"),
            (template2, "noop translation can't have context"),
            (template3, "noop translation can't have context"),
        ]

        for template, msg in error_messages:
            with self.assertRaisesMessage(TemplateSyntaxError, msg):
                self.env.from_string(template)


class DjangoI18nBlocktransTest(DjangoI18nTestBase):
    def test_simple(self):
        template1 = self.env.from_string('{% blocktrans %}Translate me!{% endblocktrans %}')
        template2 = self.env.from_string(
            "{% blocktrans context 'foo' %}Translate me!{% endblocktrans %}"
        )

        self.assertEqual('Translate me! - translated', template1.render())
        self.gettext.assert_called_with('Translate me!')
        self.assertEqual('alt translated', template2.render())
        self.pgettext.assert_called_with('foo', 'Translate me!')

    def test_trimmed(self):
        template = self.env.from_string("""{% blocktrans trimmed %}
                Translate
                me!
            {% endblocktrans %}""")

        self.assertEqual('Translate me! - translated', template.render())
        self.gettext.assert_called_with('Translate me!')

    def test_with(self):
        template1 = self.env.from_string(
            "{% blocktrans with foo=bar %}Trans: {{ foo }}{% endblocktrans %}"
        )
        template2 = self.env.from_string(
            "{% blocktrans with foo=bar spam=eggs %}Trans: {{ foo }} and "
            "{{ spam }}{% endblocktrans %}"
        )
        template3 = self.env.from_string(
            "{{ foo }} {% blocktrans with foo=bar %}Trans: {{ foo }}"
            "{% endblocktrans %} {{ foo }}"
        )
        template4 = self.env.from_string(
            "{% blocktrans with foo=bar|upper %}Trans: {{ foo }}{% endblocktrans %}"
        )

        self.assertEqual(
            'Trans: barvar - translated',
            template1.render({'bar': 'barvar'})
        )
        self.gettext.assert_called_with('Trans: %(foo)s')
        self.assertEqual(
            'Trans: barvar and eggsvar - translated',
            template2.render({'bar': 'barvar', 'eggs': 'eggsvar'})
        )
        self.gettext.assert_called_with('Trans: %(foo)s and %(spam)s')
        self.assertEqual(
            'foovar Trans: barvar - translated foovar',
            template3.render({'foo': 'foovar', 'bar': 'barvar'})
        )
        self.gettext.assert_called_with('Trans: %(foo)s')
        self.assertEqual(
            'Trans: BARVAR - translated',
            template4.render({'bar': 'barvar'})
        )
        self.gettext.assert_called_with('Trans: %(foo)s')

    def test_global_var(self):
        template = self.env.from_string("{% blocktrans %}Trans: {{ foo }}{% endblocktrans %}")

        self.assertEqual(
            'Trans: foovar - translated',
            template.render({'foo': 'foovar'})
        )
        self.gettext.assert_called_with('Trans: %(foo)s')

    def test_count(self):
        template1 = self.env.from_string(
            "{% blocktrans count counter=foo %}Singular{% plural %}"
            "Plural {{ counter }}{% endblocktrans %}"
        )
        template2 = self.env.from_string(
            """{% blocktrans trimmed count counter=foo %}
                Singular
            {% plural %}
                Plural {{ counter }}
            {% endblocktrans %}"""
        )
        template3 = self.env.from_string(
            "{% blocktrans count counter=foo context 'mycontext' %}"
            "Singular{% plural %}Plural {{ counter }}{% endblocktrans %}"
        )

        self.assertEqual('count translated', template1.render({'foo': 123}))
        self.ngettext.assert_called_with('Singular', 'Plural %(counter)s', 123)
        self.assertEqual('count translated', template2.render({'foo': 123}))
        self.ngettext.assert_called_with('Singular', 'Plural %(counter)s', 123)
        self.assertEqual('count alt translated', template3.render({'foo': 123}))
        self.npgettext.assert_called_with('mycontext', 'Singular', 'Plural %(counter)s', 123)

    def test_count_finalize(self):
        def finalize(value):
            # force convert to string
            return '{}'.format(value)

        self.env.finalize = finalize

        template = self.env.from_string(
            "{% blocktrans count counter=foo %}Singular{% plural %}"
            "Plural {{ counter }}{% endblocktrans %}"
        )
        self.assertEqual('count translated', template.render({'foo': 123}))
        self.ngettext.assert_called_with('Singular', 'Plural %(counter)s', 123)

    def test_count_override_counter(self):
        template = self.env.from_string(
            "{{ my_counter }} "
            "{% blocktrans count my_counter=foo %}Singular {{ my_counter }}"
            "{% plural %}Plural {{ my_counter }}{% endblocktrans %}"
        )
        context = {'my_counter': 123, 'foo': 456}
        self.assertEqual('123 count translated', template.render(context))
        self.ngettext.assert_called_with('Singular %(my_counter)s', 'Plural %(my_counter)s', 456)

    def test_as_var(self):
        template = self.env.from_string(
            "{% blocktrans asvar foo %}Translate me!{% endblocktrans %}"
            "Here comes the translation: {{ foo }}"
        )
        self.assertEqual(
            'Here comes the translation: Translate me! - translated', template.render()
        )
        self.gettext.assert_called_with('Translate me!')

    @override_settings(USE_L10N=True)
    def test_finalize_vars(self):
        def finalize(s):
            if s == 123:
                return 'finalized 123'
            else:
                return s
        self.env.finalize = finalize
        template = self.env.from_string("{% blocktrans %}{{ foo }}{% endblocktrans %}")

        self.assertEqual('finalized 123 - translated', template.render({'foo': 123}))

    def test_errors(self):
        template1 = "{% blocktrans %}foo{% plural %}bar{% endblocktrans %}"
        template2 = "{% blocktrans count counter=10 %}foo{% endblocktrans %}"

        error_messages = [
            (template1, 'used plural without specifying count'),
            (template2, 'plural form not found'),
        ]

        for template, msg in error_messages:
            with self.assertRaisesMessage(TemplateSyntaxError, msg):
                self.env.from_string(template)


@override_settings(USE_L10N=True, USE_TZ=True)
class DjangoL10nTest(SimpleTestCase):
    @requires_tz_support
    def test_localize(self):
        env = Environment(extensions=[DjangoL10n])
        template = env.from_string("{{ foo }}")
        context1 = {'foo': 1.23}
        date = datetime.datetime(2000, 10, 1, 14, 10, 12, tzinfo=timezone.utc)
        context2 = {'foo': date}

        translation.activate('en')
        self.assertEqual('1.23', template.render(context1))

        translation.activate('de')
        self.assertEqual('1,23', template.render(context1))

        translation.activate('es')
        timezone.activate('America/Argentina/Buenos_Aires')
        self.assertEqual('1 de Octubre de 2000 a las 11:10', template.render(context2))

        timezone.activate('Europe/Berlin')
        self.assertEqual('1 de Octubre de 2000 a las 16:10', template.render(context2))

        translation.activate('de')
        self.assertEqual('1. Oktober 2000 16:10', template.render(context2))

        timezone.activate('America/Argentina/Buenos_Aires')
        self.assertEqual('1. Oktober 2000 11:10', template.render(context2))

    def test_existing_finalize(self):
        finalize_mock = mock.Mock(side_effect=lambda s: s)

        class TestExtension(Extension):
            def __init__(self, environment):
                environment.finalize = finalize_mock

        env = Environment(extensions=[TestExtension, DjangoL10n])
        template = env.from_string("{{ foo }}")

        translation.activate('de')
        self.assertEqual('1,23', template.render({'foo': 1.23}))
        finalize_mock.assert_called_with(1.23)


class DjangoStaticTest(SimpleTestCase):
    @staticmethod
    def _static(path):
        return 'Static: {}'.format(path)

    def setUp(self):
        patcher = mock.patch('jdj_tags.extensions.django_static', side_effect=self._static)
        self.static = patcher.start()
        self.addCleanup(patcher.stop)

        self.env = Environment(extensions=[DjangoStatic])

    def test_simple(self):
        template = self.env.from_string("{% static 'static.png' %}")

        self.assertEqual('Static: static.png', template.render())
        self.static.assert_called_with('static.png')

    def test_as_var(self):
        template = self.env.from_string(
            "{% static 'static.png' as my_url %}My url is: {{ my_url }}!"
        )

        self.assertEqual('My url is: Static: static.png!', template.render())
        self.static.assert_called_with('static.png')


class DjangoNowTest(SimpleTestCase):
    @staticmethod
    def _now(tz=None):
        return datetime.datetime(2015, 7, 8, 10, 33, 25, tzinfo=tz)

    def setUp(self):
        patcher = mock.patch('jdj_tags.extensions.datetime')
        dt_mock = patcher.start()
        dt_mock.now.configure_mock(side_effect=self._now)
        self.addCleanup(patcher.stop)

        self.env = Environment(extensions=[DjangoNow])

    def test_simple(self):
        template = self.env.from_string("{% now 'Y-m-d' %}")
        expected = self._now().strftime('%Y-%m-%d')

        self.assertEqual(expected, template.render())

    def test_as_var(self):
        template = self.env.from_string(
            "{% now 'Y-m-d' as cur_date %}Current date is: {{ cur_date }}!"
        )
        expected = "Current date is: %s!" % self._now().strftime('%Y-%m-%d')

        self.assertEqual(expected, template.render())


class DjangoUrlTest(SimpleTestCase):
    @staticmethod
    def _reverse(name, *args, **kwargs):
        return 'Url for: {}'.format(name)

    def setUp(self):
        patcher = mock.patch('jdj_tags.extensions.reverse', side_effect=self._reverse)
        self.reverse = patcher.start()
        self.addCleanup(patcher.stop)

        self.env = Environment(extensions=[DjangoUrl])

    def test_simple(self):
        template = self.env.from_string("{% url 'my_view' %}")

        self.assertEqual('Url for: my_view', template.render())
        self.reverse.assert_called_with('my_view', args=(), kwargs={})

    def test_args(self):
        template1 = self.env.from_string("{% url 'my_view' 'foo' 'bar' %}")
        template2 = self.env.from_string("{% url 'my_view' arg1 'bar' %}")
        template3 = self.env.from_string("{% url 'my_view' arg1 arg2 %}")

        expected = 'Url for: my_view'
        call = mock.call('my_view', args=('foo', 'bar'), kwargs={})

        self.assertEqual(expected, template1.render())
        self.assertEqual(call, self.reverse.call_args)
        self.assertEqual(expected, template2.render({'arg1': 'foo'}))
        self.assertEqual(call, self.reverse.call_args)
        self.assertEqual(expected, template3.render({'arg1': 'foo', 'arg2': 'bar'}))
        self.assertEqual(call, self.reverse.call_args)

    def test_kwargs(self):
        template1 = self.env.from_string("{% url 'my_view' kw1='foo' kw2='bar' %}")
        template2 = self.env.from_string("{% url 'my_view' kw1=arg1 kw2='bar' %}")
        template3 = self.env.from_string("{% url 'my_view' kw1=arg1 kw2=arg2 %}")

        expected = 'Url for: my_view'
        call = mock.call('my_view', args=(), kwargs={'kw1': 'foo', 'kw2': 'bar'})

        self.assertEqual(expected, template1.render())
        self.assertEqual(call, self.reverse.call_args)
        self.assertEqual(expected, template2.render({'arg1': 'foo'}))
        self.assertEqual(call, self.reverse.call_args)
        self.assertEqual(expected, template3.render({'arg1': 'foo', 'arg2': 'bar'}))
        self.assertEqual(call, self.reverse.call_args)

    def test_dotted_expr(self):
        template1 = self.env.from_string("{% url 'my_view' foo.bar %}")
        template2 = self.env.from_string("{% url 'my_view' kw1=foo.bar %}")

        class Foo(object):
            pass

        foo = Foo()
        foo.bar = 'argument'

        self.assertEqual('Url for: my_view', template1.render({'foo': foo}))
        self.reverse.assert_called_with('my_view', args=('argument',), kwargs={})
        self.assertEqual('Url for: my_view', template2.render({'foo': foo}))
        self.reverse.assert_called_with('my_view', args=(), kwargs={'kw1': 'argument'})

    def test_as_var(self):
        template1 = self.env.from_string("{% url 'my_view' as my_url %}Url: {{ my_url }}")
        template2 = self.env.from_string(
            "{% url 'my_view' arg1 'bar' as my_url %}Url: {{ my_url }}"
        )
        template3 = self.env.from_string(
            "{% url 'my_view' kw1=arg1 kw2='bar' as my_url %}Url: {{ my_url }}"
        )

        expected = 'Url: Url for: my_view'

        self.assertEqual(expected, template1.render())
        self.reverse.assert_called_with('my_view', args=(), kwargs={})
        self.assertEqual(expected, template2.render({'arg1': 'foo'}))
        self.reverse.assert_called_with('my_view', args=('foo', 'bar'), kwargs={})
        self.assertEqual(expected, template3.render({'arg1': 'foo'}))
        self.reverse.assert_called_with('my_view', args=(), kwargs={'kw1': 'foo', 'kw2': 'bar'})

    def test_errors(self):
        template = "{% url 'my_view' kw1='foo' 123 %}"
        msg = "got 'integer', expected name for keyword argument"

        with self.assertRaisesMessage(TemplateSyntaxError, msg):
            self.env.from_string(template)


class DjangoCompatTest(SimpleTestCase):
    classes = ['DjangoCsrf', 'DjangoI18n', 'DjangoStatic', 'DjangoNow', 'DjangoUrl']

    class CalledParse(Exception):
        pass

    @classmethod
    def make_side_effect(cls, cls_name):
        def parse(self, parser):
            raise cls.CalledParse(cls_name)
        return parse

    def setUp(self):
        for class_name in self.classes:
            patcher = mock.patch(
                'jdj_tags.extensions.{}.parse'.format(class_name),
                side_effect=self.make_side_effect(class_name)
            )
            patcher.start()
            self.addCleanup(patcher.stop)

        self.env = Environment(extensions=[DjangoCompat])

    def test_compat(self):
        tags = [
            ('csrf_token', 'DjangoCsrf'),
            ('trans', 'DjangoI18n'),
            ('blocktrans', 'DjangoI18n'),
            ('static', 'DjangoStatic'),
            ('now', 'DjangoNow'),
            ('url', 'DjangoUrl'),
        ]

        for tag, class_name in tags:
            with self.assertRaisesMessage(self.CalledParse, class_name):
                self.env.from_string('{% ' + tag + ' %}')


if __name__ == '__main__':
    import unittest
    from django.apps import apps
    from django.conf import settings
    settings.configure()
    apps.populate(settings.INSTALLED_APPS)

    unittest.main()