"""Tests for Handlebars helpers."""

import json
import unittest

from handlebarrz import Handlebars

from dotpromptz.helpers import (
    history_helper,
    if_equals_helper,
    json_helper,
    media_helper,
    role_helper,
    section_helper,
    unless_equals_helper,
)


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

    def setUp(self) -> None:
        self.handlebars = Handlebars()
        self.handlebars.register_helper('json', json_helper)
        self.handlebars.register_helper('role', role_helper)
        self.handlebars.register_helper('history', history_helper)
        self.handlebars.register_helper('section', section_helper)
        self.handlebars.register_helper('media', media_helper)
        self.handlebars.register_helper('ifEquals', if_equals_helper)
        self.handlebars.register_helper('unlessEquals', unless_equals_helper)

    def test_json_helper_basic_object(self) -> None:
        self.handlebars.register_template('test1', '{{json data}}')
        result = self.handlebars.render('test1', {'data': {'name': 'John', 'age': 30}})
        expected = {'name': 'John', 'age': 30}
        self.assertEqual(json.loads(result), expected)

    def test_json_helper_with_indent(self) -> None:
        self.handlebars.register_template('test2', '{{json data indent=2}}')
        result = self.handlebars.render('test2', {'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:
        self.handlebars.register_template('test3', '{{json data indent="4"}}')
        result = self.handlebars.render('test3', {'data': {'name': 'John', 'age': 30}})
        expected = {'name': 'John', 'age': 30}
        self.assertEqual(json.loads(result), expected)

    def test_json_helper_with_array(self) -> None:
        self.handlebars.register_template('test4', '{{json data}}')
        result = self.handlebars.render('test4', {'data': [1, 2, 3]})
        self.assertEqual(json.loads(result), [1, 2, 3])

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

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

    def test_history_helper(self) -> None:
        self.handlebars.register_template('history_test', '{{history}}')
        result = self.handlebars.render('history_test', {})
        self.assertEqual(result, '<<<dotprompt:history>>>')

    def test_section_helper(self) -> None:
        self.handlebars.register_template('section_test', '{{section "example"}}')
        result = self.handlebars.render('section_test', {})
        self.assertEqual(result, '<<<dotprompt:section example>>>')

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

    def test_media_helper_with_url_and_content_type(self) -> None:
        template = '{{media url="https://example.com/img.png" contentType="image/png"}}'
        self.handlebars.register_template('media_test2', template)
        result = self.handlebars.render('media_test2', {})
        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."""
        self.handlebars.register_template(
            'unless_equals_test',
            '{{#unlessEquals arg1 arg2}}yes{{else}}no{{/unlessEquals}}',
        )
        result = self.handlebars.render('unless_equals_test', {'arg1': 1, 'arg2': 2})
        self.assertEqual(result, 'yes')

    def test_if_equals_helper_equal_values(self) -> None:
        """Test ifEquals helper with equal integer values."""
        self.handlebars.register_template(
            'if_equals_test',
            '{{#ifEquals arg1 arg2}}yes{{else}}no{{/ifEquals}}',
        )
        result = self.handlebars.render('if_equals_test', {'arg1': 1, 'arg2': 1})
        self.assertEqual(result, 'yes')

    def test_if_equals_helper_unequal_values(self) -> None:
        """Test ifEquals helper with unequal integer values."""
        self.handlebars.register_template(
            'if_equals_test',
            '{{#ifEquals arg1 arg2}}yes{{else}}no{{/ifEquals}}',
        )
        result = self.handlebars.render('if_equals_test', {'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."""
        self.handlebars.register_template(
            'if_equals_test',
            '{{#ifEquals arg1 arg2}}yes{{else}}no{{/ifEquals}}',
        )
        result = self.handlebars.render('if_equals_test', {'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."""
        self.handlebars.register_template(
            'if_equals_test',
            '{{#ifEquals arg1 arg2}}yes{{else}}no{{/ifEquals}}',
        )
        result = self.handlebars.render('if_equals_test', {'arg1': 'test', 'arg2': 'diff'})
        self.assertEqual(result, 'no')

    def test_unless_equals_helper_equal_values(self) -> None:
        """Test unlessEquals helper with equal integer values."""
        self.handlebars.register_template(
            'unless_equals_test',
            '{{#unlessEquals arg1 arg2}}yes{{else}}no{{/unlessEquals}}',
        )
        result = self.handlebars.render('unless_equals_test', {'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."""
        self.handlebars.register_template(
            'unless_equals_test',
            '{{#unlessEquals arg1 arg2}}yes{{else}}no{{/unlessEquals}}',
        )
        result = self.handlebars.render('unless_equals_test', {'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."""
        self.handlebars.register_template(
            'unless_equals_test',
            '{{#unlessEquals arg1 arg2}}yes{{else}}no{{/unlessEquals}}',
        )
        result = self.handlebars.render('unless_equals_test', {'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."""
        self.handlebars.register_template('test5', '{{json data indent=4}}')
        result = self.handlebars.render('test5', {'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."""
        self.handlebars.register_template('nested_test', '{{json data}}')
        result = self.handlebars.render('nested_test', {'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."""
        self.handlebars.register_template('empty_test', '{{json data}}')
        result = self.handlebars.render('empty_test', {'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

        self.handlebars.register_template('non_serial', '{{json data}}')

        with self.assertRaises(TypeError):
            self.handlebars.render('non_serial', {'data': 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.
        """
        self.handlebars.register_template(
            'type_test',
            '{{#ifEquals arg1 arg2}}equal{{else}}not equal{{/ifEquals}}',
        )
        # 5 (int) should not equal "5" (string)
        result = self.handlebars.render('type_test', {'arg1': 5, 'arg2': '5'})
        self.assertEqual(result, 'not equal')

    def test_if_equals_boolean_comparison(self) -> None:
        """Test ifEquals with boolean values."""
        self.handlebars.register_template(
            'bool_test',
            '{{#ifEquals arg1 arg2}}equal{{else}}not equal{{/ifEquals}}',
        )
        # true == true
        result = self.handlebars.render('bool_test', {'arg1': True, 'arg2': True})
        self.assertEqual(result, 'equal')
        # true != false
        result = self.handlebars.render('bool_test', {'arg1': True, 'arg2': False})
        self.assertEqual(result, 'not equal')

    def test_if_equals_null_comparison(self) -> None:
        """Test ifEquals with null values."""
        self.handlebars.register_template(
            'null_test',
            '{{#ifEquals arg1 arg2}}equal{{else}}not equal{{/ifEquals}}',
        )
        # null == null
        result = self.handlebars.render('null_test', {'arg1': None, 'arg2': None})
        self.assertEqual(result, 'equal')
        # null != 0
        result = self.handlebars.render('null_test', {'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.
        """
        self.handlebars.register_template(
            'type_test2',
            '{{#unlessEquals arg1 arg2}}not equal{{else}}equal{{/unlessEquals}}',
        )
        # 5 (int) should not equal "5" (string)
        result = self.handlebars.render('type_test2', {'arg1': 5, 'arg2': '5'})
        self.assertEqual(result, 'not equal')

    def test_json_helper_no_params_returns_empty(self) -> None:
        """Test json helper returns empty string when called with no params."""
        self.handlebars.register_template('json_no_params', '{{json}}')
        result = self.handlebars.render('json_no_params', {})
        self.assertEqual(result, '')

    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."""
        self.handlebars.register_template('json_bad_indent', '{{json data indent="abc"}}')
        result = self.handlebars.render('json_bad_indent', {'data': {'x': 1}})
        self.assertEqual(json.loads(result), {'x': 1})

    def test_json_helper_filters_uppercase_auto_vars(self) -> None:
        """Test json helper strips fully UPPERCASE keys from dict (auto-vars)."""
        self.handlebars.register_template('json_filter', '{{json this}}')
        result = self.handlebars.render('json_filter', {'name': 'Alice', 'TIME': '2025', 'NAME': 'test'})
        parsed = json.loads(result)
        self.assertEqual(parsed, {'name': 'Alice'})

    def test_json_helper_preserves_mixed_case_keys(self) -> None:
        """Test json helper keeps mixed-case and lowercase keys."""
        self.handlebars.register_template('json_mixed', '{{json this}}')
        result = self.handlebars.render('json_mixed', {'userName': 'Bob', 'ID': 123, 'age': 30, 'SCHEMA': '{}'}) 
        parsed = json.loads(result)
        self.assertEqual(parsed, {'userName': 'Bob', 'age': 30})

    def test_json_helper_filters_nested_uppercase_keys(self) -> None:
        """Test json helper recursively strips UPPERCASE keys from nested dicts."""
        self.handlebars.register_template('json_nested_filter', '{{json this}}')
        data = {'user': {'name': 'Alice', 'INTERNAL': 'secret'}, 'TIME': 'now'}
        result = self.handlebars.render('json_nested_filter', data)
        parsed = json.loads(result)
        self.assertEqual(parsed, {'user': {'name': 'Alice'}})

    def test_json_helper_named_var_not_filtered(self) -> None:
        """Test json helper does not filter when serializing a named variable (not this)."""
        self.handlebars.register_template('json_named', '{{json data}}')
        result = self.handlebars.render('json_named', {'data': {'KEY': 'val', 'name': 'Bob'}})
        parsed = json.loads(result)
        # Named var's dict still gets filtered because _strip_auto_vars is applied universally
        self.assertEqual(parsed, {'name': 'Bob'})

    def test_role_helper_no_params_returns_empty(self) -> None:
        """Test role helper returns empty string when called with no params."""
        self.handlebars.register_template('role_no_params', '{{role}}')
        result = self.handlebars.render('role_no_params', {})
        self.assertEqual(result, '')

    def test_section_helper_no_params_returns_empty(self) -> None:
        """Test section helper returns empty string when called with no params."""
        self.handlebars.register_template('section_no_params', '{{section}}')
        result = self.handlebars.render('section_no_params', {})
        self.assertEqual(result, '')

    def test_media_helper_no_url_returns_empty(self) -> None:
        """Test media helper returns empty string when no url is provided."""
        self.handlebars.register_template('media_no_url', '{{media}}')
        result = self.handlebars.render('media_no_url', {})
        self.assertEqual(result, '')

    def test_if_equals_helper_too_few_params_returns_empty(self) -> None:
        """Test ifEquals helper returns empty string with fewer than 2 params."""
        result = if_equals_helper([], None)  # type: ignore[arg-type]
        self.assertEqual(result, '')

    def test_unless_equals_helper_too_few_params_returns_empty(self) -> None:
        """Test unlessEquals helper returns empty string with fewer than 2 params."""
        result = unless_equals_helper([], None)  # type: ignore[arg-type]
        self.assertEqual(result, '')


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