"""Tests for Jinja2 helpers."""

import json
import unittest

import jinja2

from dotpromptz.helpers import (
    BUILTIN_FILTERS,
    BUILTIN_HELPERS,
    media,
    role,
    tojson_filter,
)


class TestDotpromptHelpers(unittest.TestCase):
    """Tests for the dotprompt helpers."""

    def setUp(self) -> None:
        self.env = jinja2.Environment(autoescape=False, undefined=jinja2.Undefined)
        self.env.globals.update(BUILTIN_HELPERS)
        self.env.filters.update(BUILTIN_FILTERS)

    def test_json_helper_basic_object(self) -> None:
        result = self.env.from_string('{{ data | tojson }}').render({'data': {'name': 'John', 'age': 30}})
        expected = {'name': 'John', 'age': 30}
        self.assertEqual(json.loads(result), expected)

    def test_json_helper_with_indent(self) -> None:
        result = self.env.from_string('{{ data | tojson(indent=2) }}').render({'data': {'name': 'John', 'age': 30}})
        expected = {'name': 'John', 'age': 30}
        self.assertEqual(json.loads(result), expected)

    def test_json_helper_with_str_indent(self) -> None:
        result = self.env.from_string('{{ data | tojson(indent="4") }}').render({'data': {'name': 'John', 'age': 30}})
        expected = {'name': 'John', 'age': 30}
        self.assertEqual(json.loads(result), expected)

    def test_json_helper_with_array(self) -> None:
        result = self.env.from_string('{{ data | tojson }}').render({'data': [1, 2, 3]})
        self.assertEqual(json.loads(result), [1, 2, 3])

    def test_role_helper_system(self) -> None:
        result = self.env.from_string('{{ role("system") }}').render({})
        self.assertEqual(result, '<<<dotprompt:role:system>>>')

    def test_role_helper_user(self) -> None:
        result = self.env.from_string('{{ role("user") }}').render({})
        self.assertEqual(result, '<<<dotprompt:role:user>>>')

    def test_media_helper_with_url(self) -> None:
        result = self.env.from_string('{{ media(url="https://example.com/img.png") }}').render({})
        self.assertEqual(result, '<<<dotprompt:media:url https://example.com/img.png>>>')

    def test_media_helper_with_url_and_content_type(self) -> None:
        result = self.env.from_string(
            '{{ media(url="https://example.com/img.png", contentType="image/png") }}'
        ).render({})
        expected = '<<<dotprompt:media:url https://example.com/img.png image/png>>>'
        self.assertEqual(result, expected)

    def test_unless_equals_helper_unequal_values(self) -> None:
        """Test unlessEquals helper with unequal integer values."""
        result = self.env.from_string('{% if arg1 != arg2 %}yes{% else %}no{% endif %}').render({'arg1': 1, 'arg2': 2})
        self.assertEqual(result, 'yes')

    def test_if_equals_helper_equal_values(self) -> None:
        """Test ifEquals helper with equal integer values."""
        result = self.env.from_string('{% if arg1 == arg2 %}yes{% else %}no{% endif %}').render({'arg1': 1, 'arg2': 1})
        self.assertEqual(result, 'yes')

    def test_if_equals_helper_unequal_values(self) -> None:
        """Test ifEquals helper with unequal integer values."""
        result = self.env.from_string('{% if arg1 == arg2 %}yes{% else %}no{% endif %}').render({'arg1': 1, 'arg2': 2})
        self.assertEqual(result, 'no')

    def test_if_equals_helper_string_equal_values(self) -> None:
        """Test ifEquals helper with equal string values."""
        result = self.env.from_string('{% if arg1 == arg2 %}yes{% else %}no{% endif %}').render({
            'arg1': 'test',
            'arg2': 'test',
        })
        self.assertEqual(result, 'yes')

    def test_if_equals_helper_string_unequal_values(self) -> None:
        """Test ifEquals helper with unequal string values."""
        result = self.env.from_string('{% if arg1 == arg2 %}yes{% else %}no{% endif %}').render({
            'arg1': 'test',
            'arg2': 'diff',
        })
        self.assertEqual(result, 'no')

    def test_unless_equals_helper_equal_values(self) -> None:
        """Test unlessEquals helper with equal integer values."""
        result = self.env.from_string('{% if arg1 != arg2 %}yes{% else %}no{% endif %}').render({'arg1': 1, 'arg2': 1})
        self.assertEqual(result, 'no')

    def test_unless_equals_helper_string_unequal_values(self) -> None:
        """Test unlessEquals helper with unequal string values."""
        result = self.env.from_string('{% if arg1 != arg2 %}yes{% else %}no{% endif %}').render({
            'arg1': 'test',
            'arg2': 'diff',
        })
        self.assertEqual(result, 'yes')

    def test_unless_equals_helper_string_equal_values(self) -> None:
        """Test unlessEquals helper with equal string values."""
        result = self.env.from_string('{% if arg1 != arg2 %}yes{% else %}no{% endif %}').render({
            'arg1': 'test',
            'arg2': 'test',
        })
        self.assertEqual(result, 'no')

    # Edge case tests for cross-runtime parity

    def test_json_helper_4_space_indent(self) -> None:
        """Test json helper with 4-space indentation."""
        result = self.env.from_string('{{ data | tojson(indent=4) }}').render({'data': {'test': True}})
        expected = '{\n    "test": true\n}'
        self.assertEqual(result, expected)

    def test_json_helper_nested_objects(self) -> None:
        """Test json helper with nested objects."""
        result = self.env.from_string('{{ data | tojson }}').render({'data': {'outer': {'inner': {'value': 42}}}})
        self.assertEqual(result, '{"outer":{"inner":{"value":42}}}')

    def test_json_helper_empty_object(self) -> None:
        """Test json helper with empty object."""
        result = self.env.from_string('{{ data | tojson }}').render({'data': {}})
        self.assertEqual(result, '{}')

    def test_json_helper_raises_on_non_serializable(self) -> None:
        """Test json helper raises TypeError on non-serializable objects.

        This test documents the fail-fast behavior that matches JavaScript's
        JSON.stringify which throws on non-serializable values.
        """

        # Create a non-serializable object
        class NonSerializable:
            pass

        with self.assertRaises(TypeError):
            tojson_filter(NonSerializable())

    def test_if_equals_type_safety_int_vs_string(self) -> None:
        """Test ifEquals uses strict equality (int 5 != string '5').

        This test documents that the comparison uses strict equality,
        matching JavaScript's === operator behavior.
        """
        # 5 (int) should not equal "5" (string)
        result = self.env.from_string('{% if arg1 == arg2 %}equal{% else %}not equal{% endif %}').render({
            'arg1': 5,
            'arg2': '5',
        })
        self.assertEqual(result, 'not equal')

    def test_if_equals_boolean_comparison(self) -> None:
        """Test ifEquals with boolean values."""
        # true == true
        result = self.env.from_string('{% if arg1 == arg2 %}equal{% else %}not equal{% endif %}').render({
            'arg1': True,
            'arg2': True,
        })
        self.assertEqual(result, 'equal')
        # true != false
        result = self.env.from_string('{% if arg1 == arg2 %}equal{% else %}not equal{% endif %}').render({
            'arg1': True,
            'arg2': False,
        })
        self.assertEqual(result, 'not equal')

    def test_if_equals_null_comparison(self) -> None:
        """Test ifEquals with null values."""
        # null == null
        result = self.env.from_string('{% if arg1 == arg2 %}equal{% else %}not equal{% endif %}').render({
            'arg1': None,
            'arg2': None,
        })
        self.assertEqual(result, 'equal')
        # null != 0
        result = self.env.from_string('{% if arg1 == arg2 %}equal{% else %}not equal{% endif %}').render({
            'arg1': None,
            'arg2': 0,
        })
        self.assertEqual(result, 'not equal')

    def test_unless_equals_type_safety_int_vs_string(self) -> None:
        """Test unlessEquals uses strict inequality (int 5 != string '5').

        This test documents that the comparison uses strict inequality,
        matching JavaScript's !== operator behavior.
        """
        # 5 (int) should not equal "5" (string)
        result = self.env.from_string('{% if arg1 != arg2 %}not equal{% else %}equal{% endif %}').render({
            'arg1': 5,
            'arg2': '5',
        })
        self.assertEqual(result, 'not equal')

    def test_json_helper_invalid_indent_string_falls_back_to_zero(self) -> None:
        """Test json helper falls back to indent=0 when indent is non-numeric string."""
        result = self.env.from_string('{{ data | tojson(indent="abc") }}').render({'data': {'x': 1}})
        self.assertEqual(json.loads(result), {'x': 1})

    def test_json_helper_no_auto_var_stripping(self) -> None:
        """Test json helper does NOT strip UPPERCASE keys (stripping was removed)."""
        result = tojson_filter({'name': 'Alice', 'TIME': '2025', 'NAME': 'test'})
        parsed = json.loads(result)
        self.assertEqual(parsed, {'name': 'Alice', 'TIME': '2025', 'NAME': 'test'})

    def test_role_direct_call(self) -> None:
        """Test role() function called directly."""
        self.assertEqual(role('model'), '<<<dotprompt:role:model>>>')
        self.assertEqual(role('tool'), '<<<dotprompt:role:tool>>>')

    def test_media_direct_call_with_url_and_content_type(self) -> None:
        """Test media() function called directly with url and contentType."""
        result = media(url='https://example.com/img.jpg', contentType='image/jpeg')
        self.assertEqual(result, '<<<dotprompt:media:url https://example.com/img.jpg image/jpeg>>>')


if __name__ == '__main__':
    unittest.main()
