import pytest

from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import PrefixLoader
from jinja2 import Template
from jinja2 import TemplateAssertionError
from jinja2 import TemplateNotFound
from jinja2 import TemplateSyntaxError


@pytest.mark.regression
class TestCorner:
    def test_assigned_scoping(self, env):
        t = env.from_string(
            """
        {%- for item in (1, 2, 3, 4) -%}
            [{{ item }}]
        {%- endfor %}
        {{- item -}}
        """
        )
        assert t.render(item=42) == "[1][2][3][4]42"

        t = env.from_string(
            """
        {%- for item in (1, 2, 3, 4) -%}
            [{{ item }}]
        {%- endfor %}
        {%- set item = 42 %}
        {{- item -}}
        """
        )
        assert t.render() == "[1][2][3][4]42"

        t = env.from_string(
            """
        {%- set item = 42 %}
        {%- for item in (1, 2, 3, 4) -%}
            [{{ item }}]
        {%- endfor %}
        {{- item -}}
        """
        )
        assert t.render() == "[1][2][3][4]42"

    def test_closure_scoping(self, env):
        t = env.from_string(
            """
        {%- set wrapper = "<FOO>" %}
        {%- for item in (1, 2, 3, 4) %}
            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
            {{- wrapper() }}
        {%- endfor %}
        {{- wrapper -}}
        """
        )
        assert t.render() == "[1][2][3][4]<FOO>"

        t = env.from_string(
            """
        {%- for item in (1, 2, 3, 4) %}
            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
            {{- wrapper() }}
        {%- endfor %}
        {%- set wrapper = "<FOO>" %}
        {{- wrapper -}}
        """
        )
        assert t.render() == "[1][2][3][4]<FOO>"

        t = env.from_string(
            """
        {%- for item in (1, 2, 3, 4) %}
            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
            {{- wrapper() }}
        {%- endfor %}
        {{- wrapper -}}
        """
        )
        assert t.render(wrapper=23) == "[1][2][3][4]23"


@pytest.mark.regression
class TestBug:
    def test_keyword_folding(self, env):
        env = Environment()
        env.filters["testing"] = lambda value, some: value + some
        assert (
            env.from_string("{{ 'test'|testing(some='stuff') }}").render()
            == "teststuff"
        )

    def test_extends_output_bugs(self, env):
        env = Environment(
            loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"})
        )

        t = env.from_string(
            '{% if expr %}{% extends "parent.html" %}{% endif %}'
            "[[{% block title %}title{% endblock %}]]"
            "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}"
        )
        assert t.render(expr=False) == "[[title]](1)(2)(3)"
        assert t.render(expr=True) == "((title))"

    def test_urlize_filter_escaping(self, env):
        tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
        assert (
            tmpl.render() == '<a href="http://www.example.org/&lt;foo" rel="noopener">'
            "http://www.example.org/&lt;foo</a>"
        )

    def test_loop_call_loop(self, env):
        tmpl = env.from_string(
            """

        {% macro test() %}
            {{ caller() }}
        {% endmacro %}

        {% for num1 in range(5) %}
            {% call test() %}
                {% for num2 in range(10) %}
                    {{ loop.index }}
                {% endfor %}
            {% endcall %}
        {% endfor %}

        """
        )

        assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5

    def test_weird_inline_comment(self, env):
        env = Environment(line_statement_prefix="%")
        pytest.raises(
            TemplateSyntaxError,
            env.from_string,
            "% for item in seq {# missing #}\n...% endfor",
        )

    def test_old_macro_loop_scoping_bug(self, env):
        tmpl = env.from_string(
            "{% for i in (1, 2) %}{{ i }}{% endfor %}"
            "{% macro i() %}3{% endmacro %}{{ i() }}"
        )
        assert tmpl.render() == "123"

    def test_partial_conditional_assignments(self, env):
        tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}")
        assert tmpl.render(a=23) == "23"
        assert tmpl.render(b=True) == "42"

    def test_stacked_locals_scoping_bug(self, env):
        env = Environment(line_statement_prefix="#")
        t = env.from_string(
            """\
# for j in [1, 2]:
#   set x = 1
#   for i in [1, 2]:
#     print x
#     if i % 2 == 0:
#       set x = x + 1
#     endif
#   endfor
# endfor
# if a
#   print 'A'
# elif b
#   print 'B'
# elif c == d
#   print 'C'
# else
#   print 'D'
# endif
    """
        )
        assert t.render(a=0, b=False, c=42, d=42.0) == "1111C"

    def test_stacked_locals_scoping_bug_twoframe(self, env):
        t = Template(
            """
            {% set x = 1 %}
            {% for item in foo %}
                {% if item == 1 %}
                    {% set x = 2 %}
                {% endif %}
            {% endfor %}
            {{ x }}
        """
        )
        rv = t.render(foo=[1]).strip()
        assert rv == "1"

    def test_call_with_args(self, env):
        t = Template(
            """{% macro dump_users(users) -%}
        <ul>
          {%- for user in users -%}
            <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
          {%- endfor -%}
          </ul>
        {%- endmacro -%}

        {% call(user) dump_users(list_of_user) -%}
          <dl>
            <dl>Realname</dl>
            <dd>{{ user.realname|e }}</dd>
            <dl>Description</dl>
            <dd>{{ user.description }}</dd>
          </dl>
        {% endcall %}"""
        )

        assert [
            x.strip()
            for x in t.render(
                list_of_user=[
                    {
                        "username": "apo",
                        "realname": "something else",
                        "description": "test",
                    }
                ]
            ).splitlines()
        ] == [
            "<ul><li><p>apo</p><dl>",
            "<dl>Realname</dl>",
            "<dd>something else</dd>",
            "<dl>Description</dl>",
            "<dd>test</dd>",
            "</dl>",
            "</li></ul>",
        ]

    def test_empty_if_condition_fails(self, env):
        pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}")
        pytest.raises(
            TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}"
        )
        pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}")

    def test_recursive_loop_compile(self, env):
        Template(
            """
            {% for p in foo recursive%}
                {{p.bar}}
                {% for f in p.fields recursive%}
                    {{f.baz}}
                    {{p.bar}}
                    {% if f.rec %}
                        {{ loop(f.sub) }}
                    {% endif %}
                {% endfor %}
            {% endfor %}
            """
        )
        Template(
            """
            {% for p in foo%}
                {{p.bar}}
                {% for f in p.fields recursive%}
                    {{f.baz}}
                    {{p.bar}}
                    {% if f.rec %}
                        {{ loop(f.sub) }}
                    {% endif %}
                {% endfor %}
            {% endfor %}
            """
        )

    def test_else_loop_bug(self, env):
        t = Template(
            """
            {% for x in y %}
                {{ loop.index0 }}
            {% else %}
                {% for i in range(3) %}{{ i }}{% endfor %}
            {% endfor %}
        """
        )
        assert t.render(y=[]).strip() == "012"

    def test_correct_prefix_loader_name(self, env):
        env = Environment(loader=PrefixLoader({"foo": DictLoader({})}))
        with pytest.raises(TemplateNotFound) as e:
            env.get_template("foo/bar.html")

        assert e.value.name == "foo/bar.html"

    def test_contextfunction_callable_classes(self, env):
        from jinja2.utils import contextfunction

        class CallableClass:
            @contextfunction
            def __call__(self, ctx):
                return ctx.resolve("hello")

        tpl = Template("""{{ callableclass() }}""")
        output = tpl.render(callableclass=CallableClass(), hello="TEST")
        expected = "TEST"

        assert output == expected

    def test_block_set_with_extends(self):
        env = Environment(
            loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"})
        )
        t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}')
        assert t.render() == "[42]"

    def test_nested_for_else(self, env):
        tmpl = env.from_string(
            "{% for x in y %}{{ loop.index0 }}{% else %}"
            "{% for i in range(3) %}{{ i }}{% endfor %}"
            "{% endfor %}"
        )
        assert tmpl.render() == "012"

    def test_macro_var_bug(self, env):
        tmpl = env.from_string(
            """
        {% set i = 1 %}
        {% macro test() %}
            {% for i in range(0, 10) %}{{ i }}{% endfor %}
        {% endmacro %}{{ test() }}
        """
        )
        assert tmpl.render().strip() == "0123456789"

    def test_macro_var_bug_advanced(self, env):
        tmpl = env.from_string(
            """
        {% macro outer() %}
            {% set i = 1 %}
            {% macro test() %}
                {% for i in range(0, 10) %}{{ i }}{% endfor %}
            {% endmacro %}{{ test() }}
        {% endmacro %}{{ outer() }}
        """
        )
        assert tmpl.render().strip() == "0123456789"

    def test_callable_defaults(self):
        env = Environment()
        env.globals["get_int"] = lambda: 42
        t = env.from_string(
            """
        {% macro test(a, b, c=get_int()) -%}
             {{ a + b + c }}
        {%- endmacro %}
        {{ test(1, 2) }}|{{ test(1, 2, 3) }}
        """
        )
        assert t.render().strip() == "45|6"

    def test_macro_escaping(self):
        env = Environment(
            autoescape=lambda x: False, extensions=["jinja2.ext.autoescape"]
        )
        template = "{% macro m() %}<html>{% endmacro %}"
        template += "{% autoescape true %}{{ m() }}{% endautoescape %}"
        assert env.from_string(template).render()

    def test_macro_scoping(self, env):
        tmpl = env.from_string(
            """
        {% set n=[1,2,3,4,5] %}
        {% for n in [[1,2,3], [3,4,5], [5,6,7]] %}

        {% macro x(l) %}
          {{ l.pop() }}
          {% if l %}{{ x(l) }}{% endif %}
        {% endmacro %}

        {{ x(n) }}

        {% endfor %}
        """
        )
        assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5]

    def test_scopes_and_blocks(self):
        env = Environment(
            loader=DictLoader(
                {
                    "a.html": """
                {%- set foo = 'bar' -%}
                {% include 'x.html' -%}
            """,
                    "b.html": """
                {%- set foo = 'bar' -%}
                {% block test %}{% include 'x.html' %}{% endblock -%}
                """,
                    "c.html": """
                {%- set foo = 'bar' -%}
                {% block test %}{% set foo = foo
                    %}{% include 'x.html' %}{% endblock -%}
            """,
                    "x.html": """{{ foo }}|{{ test }}""",
                }
            )
        )

        a = env.get_template("a.html")
        b = env.get_template("b.html")
        c = env.get_template("c.html")

        assert a.render(test="x").strip() == "bar|x"
        assert b.render(test="x").strip() == "bar|x"
        assert c.render(test="x").strip() == "bar|x"

    def test_scopes_and_include(self):
        env = Environment(
            loader=DictLoader(
                {
                    "include.html": "{{ var }}",
                    "base.html": '{% include "include.html" %}',
                    "child.html": '{% extends "base.html" %}{% set var = 42 %}',
                }
            )
        )
        t = env.get_template("child.html")
        assert t.render() == "42"

    def test_caller_scoping(self, env):
        t = env.from_string(
            """
        {% macro detail(icon, value) -%}
          {% if value -%}
            <p><span class="fa fa-fw fa-{{ icon }}"></span>
                {%- if caller is undefined -%}
                    {{ value }}
                {%- else -%}
                    {{ caller(value, *varargs) }}
                {%- endif -%}</p>
          {%- endif %}
        {%- endmacro %}


        {% macro link_detail(icon, value, href) -%}
          {% call(value, href) detail(icon, value, href) -%}
            <a href="{{ href }}">{{ value }}</a>
          {%- endcall %}
        {%- endmacro %}
        """
        )

        assert t.module.link_detail("circle", "Index", "/") == (
            '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>'
        )

    def test_variable_reuse(self, env):
        t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}")
        assert t.render(x={"y": [0, 1, 2]}) == "012"

        t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}")
        assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2"

        t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}")
        assert t.render(x={"y": [0, 1, 2]}) == "012"

    def test_double_caller(self, env):
        t = env.from_string(
            "{% macro x(caller=none) %}[{% if caller %}"
            "{{ caller() }}{% endif %}]{% endmacro %}"
            "{{ x() }}{% call x() %}aha!{% endcall %}"
        )
        assert t.render() == "[][aha!]"

    def test_double_caller_no_default(self, env):
        with pytest.raises(TemplateAssertionError) as exc_info:
            env.from_string(
                "{% macro x(caller) %}[{% if caller %}"
                "{{ caller() }}{% endif %}]{% endmacro %}"
            )
        assert exc_info.match(
            r'"caller" argument must be omitted or ' r"be given a default"
        )

        t = env.from_string(
            "{% macro x(caller=none) %}[{% if caller %}"
            "{{ caller() }}{% endif %}]{% endmacro %}"
        )
        with pytest.raises(TypeError) as exc_info:
            t.module.x(None, caller=lambda: 42)
        assert exc_info.match(
            r"\'x\' was invoked with two values for the " r"special caller argument"
        )

    def test_macro_blocks(self, env):
        t = env.from_string(
            "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}"
        )
        assert t.render() == "x"

    def test_scoped_block(self, env):
        t = env.from_string(
            "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}"
            "{{ x }}{% endblock %}{% endwith %}"
        )
        assert t.render() == "2"

    def test_recursive_loop_filter(self, env):
        t = env.from_string(
            """
        <?xml version="1.0" encoding="UTF-8"?>
        <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
          {%- for page in [site.root] if page.url != this recursive %}
          <url><loc>{{ page.url }}</loc></url>
          {{- loop(page.children) }}
          {%- endfor %}
        </urlset>
        """
        )
        sm = t.render(
            this="/foo",
            site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}},
        )
        lines = [x.strip() for x in sm.splitlines() if x.strip()]
        assert lines == [
            '<?xml version="1.0" encoding="UTF-8"?>',
            '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
            "<url><loc>/</loc></url>",
            "<url><loc>/bar</loc></url>",
            "</urlset>",
        ]

    def test_empty_if(self, env):
        t = env.from_string("{% if foo %}{% else %}42{% endif %}")
        assert t.render(foo=False) == "42"

    def test_subproperty_if(self, env):
        t = env.from_string(
            "{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}"
        )
        assert (
            t.render(
                object1={"subproperty1": "value"}, object2={"subproperty2": "value"}
            )
            == "42"
        )

    def test_set_and_include(self):
        env = Environment(
            loader=DictLoader(
                {
                    "inc": "bar",
                    "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}',
                }
            )
        )
        assert env.get_template("main").render() == "foobar"

    def test_loop_include(self):
        env = Environment(
            loader=DictLoader(
                {
                    "inc": "{{ i }}",
                    "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}',
                }
            )
        )
        assert env.get_template("main").render() == "123"

    def test_grouper_repr(self):
        from jinja2.filters import _GroupTuple

        t = _GroupTuple("foo", [1, 2])
        assert t.grouper == "foo"
        assert t.list == [1, 2]
        assert repr(t) == "('foo', [1, 2])"
        assert str(t) == "('foo', [1, 2])"

    def test_custom_context(self, env):
        from jinja2.runtime import Context

        class MyContext(Context):
            pass

        class MyEnvironment(Environment):
            context_class = MyContext

        loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'})
        env = MyEnvironment(loader=loader)
        assert env.get_template("test").render(foobar="test") == "test"

    def test_legacy_custom_context(self, env):
        from jinja2.runtime import Context, missing

        class MyContext(Context):
            def resolve(self, name):
                if name == "foo":
                    return 42
                return super().resolve(name)

        x = MyContext(env, parent={"bar": 23}, name="foo", blocks={})
        assert x._legacy_resolve_mode
        assert x.resolve_or_missing("foo") == 42
        assert x.resolve_or_missing("bar") == 23
        assert x.resolve_or_missing("baz") is missing

    def test_recursive_loop_bug(self, env):
        tmpl = env.from_string(
            "{%- for value in values recursive %}1{% else %}0{% endfor -%}"
        )
        assert tmpl.render(values=[]) == "0"

    def test_markup_and_chainable_undefined(self):
        from jinja2 import Markup
        from jinja2.runtime import ChainableUndefined

        assert str(Markup(ChainableUndefined())) == ""
