"""Tests for expression serialization and deserialization."""

from typing import Any

import pytest

from therismos import (
    FALSE,
    TRUE,
    AllExpr,
    AnyExpr,
    Eq,
    Expr,
    F,
    FalseExpr,
    Ge,
    Gt,
    In,
    IsNull,
    Le,
    Lt,
    Ne,
    NotExpr,
    Regex,
    TrueExpr,
    unwind_data,
)
from therismos.expr.serializer import Serializer


class TestBasicSerialization:
    """Test basic serialization of atomic expressions."""

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (Eq(F("age"), 18), "age==18"),
            (Ne(F("status"), "active"), 'status!="active"'),
            (Lt(F("price"), 100.5), "price<100.5"),
            (Le(F("count"), 10), "count<=10"),
            (Gt(F("score"), 75), "score>75"),
            (Ge(F("rating"), 4.5), "rating>=4.5"),
        ],
    )
    def test_comparison_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of comparison expressions."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (Eq(F("active"), True), "active==true"),
            (Eq(F("deleted"), False), "deleted==false"),
            (Eq(F("value"), None), "value==null"),
        ],
    )
    def test_special_value_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of boolean and null values."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected

    def test_string_escaping(self) -> None:
        """Test that strings are properly escaped."""
        expr = Eq(F("text"), 'Hello "World"\n\tWith\\Escapes')
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == r'text=="Hello \"World\"\n\tWith\\Escapes"'

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (In(F("status"), ("active", "pending")), 'status=in=("active","pending")'),
            (In(F("id"), (1, 2, 3)), "id=in=(1,2,3)"),
            (In(F("flag"), (True, False)), "flag=in=(true,false)"),
        ],
    )
    def test_in_expression_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of IN expressions."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (Regex(F("name"), r"^A.*", None), r'name~regex("^A.*")'),
            (Regex(F("email"), r".*@example\.com", 2), r'email~regex(".*@example\\.com","2")'),
        ],
    )
    def test_regex_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of regex expressions."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (IsNull(F("deleted_at"), True), "deleted_at==null"),
            (IsNull(F("created_at"), False), "created_at!=null"),
        ],
    )
    def test_null_check_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of null check expressions."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected

    @pytest.mark.parametrize(
        "expr,expected",
        [
            (TrueExpr(), "true()"),
            (FalseExpr(), "false()"),
            (TRUE, "true()"),
            (FALSE, "false()"),
        ],
    )
    def test_boolean_constants_serialization(self, expr: Expr, expected: str) -> None:
        """Test serialization of boolean constants."""
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == expected


class TestCompoundSerialization:
    """Test serialization of compound expressions."""

    def test_and_expression(self) -> None:
        """Test serialization of AND expressions."""
        expr = AllExpr(Eq(F("age"), 18), Gt(F("score"), 75))
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == "(age==18;score>75)"

    def test_or_expression(self) -> None:
        """Test serialization of OR expressions."""
        expr = AnyExpr(Eq(F("status"), "active"), Eq(F("status"), "pending"))
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == '(status=="active",status=="pending")'

    def test_not_expression(self) -> None:
        """Test serialization of NOT expressions."""
        expr = NotExpr(Eq(F("deleted"), True))
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == "!(deleted==true)"

    def test_nested_compound_expressions(self) -> None:
        """Test serialization of nested compound expressions."""
        expr = AllExpr(
            Gt(F("age"), 18), AnyExpr(Eq(F("status"), "active"), Eq(F("status"), "pending"))
        )
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == '(age>18;(status=="active",status=="pending"))'

    def test_complex_nested_expression(self) -> None:
        """Test serialization of complex nested expressions."""
        expr = NotExpr(AllExpr(Lt(F("age"), 18), Eq(F("country"), "US")))
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == '!(age<18;country=="US")'


class TestTypeAnnotations:
    """Test type annotation handling in serialization."""

    def test_without_type_annotations(self) -> None:
        """Test serialization without type annotations by default."""
        expr = Eq(F("age", int), 18)
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == "age==18"

    def test_with_all_type_annotations(self) -> None:
        """Test serialization with all type annotations enabled."""
        expr = Eq(F("age", int), 18)
        serializer = Serializer(include_all_types=True)
        result = serializer.serialize(expr)
        assert result == "age{int}==18"

    def test_multiple_fields_with_types(self) -> None:
        """Test serialization of multiple fields with types."""
        expr = AllExpr(Eq(F("age", int), 18), Eq(F("name", str), "Alice"))
        serializer = Serializer(include_all_types=True)
        result = serializer.serialize(expr)
        assert result == '(age{int}==18;name{str}=="Alice")'

    def test_custom_type_registration(self) -> None:
        """Test registration and serialization of custom types."""

        # Define a custom type
        def custom_transform(x: Any) -> str:
            return str(x).upper()

        serializer = Serializer()
        serializer.register_custom_type(custom_transform, "custom")

        expr = Eq(F("field", custom_transform), "value")
        result = serializer.serialize(expr)
        # Without include_all_types, type annotation shouldn't appear
        # Note: value is casted to uppercase by custom_transform
        assert result == 'field=="VALUE"'

        # With include_all_types, custom type should appear
        serializer_with_types = Serializer(include_all_types=True)
        serializer_with_types.register_custom_type(custom_transform, "custom")
        result = serializer_with_types.serialize(expr)
        assert result == 'field{custom}=="VALUE"'


class TestBasicDeserialization:
    """Test basic deserialization from strings to expressions."""

    @pytest.mark.parametrize(
        "text,expected_type,expected_field,expected_value",
        [
            ("age == 18", Eq, "age", 18),
            ('status != "active"', Ne, "status", "active"),
            ("price < 100.5", Lt, "price", 100.5),
            ("count <= 10", Le, "count", 10),
            ("score > 75", Gt, "score", 75),
            ("rating >= 4.5", Ge, "rating", 4.5),
        ],
    )
    def test_comparison_deserialization(
        self, text: str, expected_type: type, expected_field: str, expected_value: Any
    ) -> None:
        """Test deserialization of comparison expressions."""
        serializer = Serializer()
        expr = serializer.deserialize(text)
        assert isinstance(expr, expected_type)
        assert expr.field.name == expected_field  # type: ignore[attr-defined]
        assert expr.value == expected_value  # type: ignore[attr-defined]

    @pytest.mark.parametrize(
        "text,expected_value",
        [
            ("active == true", True),
            ("deleted == false", False),
        ],
    )
    def test_special_value_deserialization(self, text: str, expected_value: Any) -> None:
        """Test deserialization of boolean and null values."""
        serializer = Serializer()
        expr = serializer.deserialize(text)
        assert isinstance(expr, Eq)
        assert expr.value == expected_value

    def test_string_unescaping(self) -> None:
        """Test that strings are properly unescaped."""
        serializer = Serializer()
        expr = serializer.deserialize(r'text == "Hello \"World\"\n\tWith\\Escapes"')
        assert isinstance(expr, Eq)
        assert expr.value == 'Hello "World"\n\tWith\\Escapes'

    def test_in_expression_deserialization(self) -> None:
        """Test deserialization of IN expressions."""
        serializer = Serializer()
        expr = serializer.deserialize('status =in= ("active", "pending", "completed")')
        assert isinstance(expr, In)
        assert expr.field.name == "status"
        assert expr.values == ("active", "pending", "completed")

    def test_regex_deserialization(self) -> None:
        """Test deserialization of regex expressions."""
        serializer = Serializer()
        expr = serializer.deserialize(r'name ~regex ("^A.*")')
        assert isinstance(expr, Regex)
        assert expr.field.name == "name"
        assert expr.value == r"^A.*"
        assert expr.flags is None

    def test_regex_with_flags_deserialization(self) -> None:
        """Test deserialization of regex expressions with flags."""
        serializer = Serializer()
        expr = serializer.deserialize(r'email ~regex (".*@example\.com", "2")')
        assert isinstance(expr, Regex)
        assert expr.value == r".*@example\.com"
        assert expr.flags == 2

    @pytest.mark.parametrize(
        "text,expected_is_null",
        [
            ("deleted_at == null", True),
            ("created_at != null", False),
        ],
    )
    def test_null_check_deserialization(self, text: str, expected_is_null: bool) -> None:
        """Test deserialization of null check expressions."""
        serializer = Serializer()
        expr = serializer.deserialize(text)
        assert isinstance(expr, IsNull)
        assert expr.is_null == expected_is_null

    @pytest.mark.parametrize(
        "text,expected_type",
        [
            ("true()", TrueExpr),
            ("false()", FalseExpr),
        ],
    )
    def test_boolean_constants_deserialization(self, text: str, expected_type: type) -> None:
        """Test deserialization of boolean constants."""
        serializer = Serializer()
        expr = serializer.deserialize(text)
        assert isinstance(expr, expected_type)


class TestCompoundDeserialization:
    """Test deserialization of compound expressions."""

    def test_and_expression_deserialization(self) -> None:
        """Test deserialization of AND expressions."""
        serializer = Serializer()
        expr = serializer.deserialize("age == 18 ; score > 75")
        assert isinstance(expr, AllExpr)
        assert len(expr.exprs) == 2
        assert isinstance(expr.exprs[0], Eq)
        assert isinstance(expr.exprs[1], Gt)

    def test_or_expression_deserialization(self) -> None:
        """Test deserialization of OR expressions."""
        serializer = Serializer()
        expr = serializer.deserialize('status == "active" , status == "pending"')
        assert isinstance(expr, AnyExpr)
        assert len(expr.exprs) == 2
        assert all(isinstance(e, Eq) for e in expr.exprs)

    def test_not_expression_deserialization(self) -> None:
        """Test deserialization of NOT expressions."""
        serializer = Serializer()
        expr = serializer.deserialize("!(deleted == true)")
        assert isinstance(expr, NotExpr)
        assert isinstance(expr.expr, Eq)

    def test_nested_compound_deserialization(self) -> None:
        """Test deserialization of nested compound expressions."""
        serializer = Serializer()
        expr = serializer.deserialize('age > 18 ; (status == "active" , status == "pending")')
        assert isinstance(expr, AllExpr)
        assert len(expr.exprs) == 2
        assert isinstance(expr.exprs[0], Gt)
        assert isinstance(expr.exprs[1], AnyExpr)

    def test_parenthesized_expression(self) -> None:
        """Test that parentheses work correctly for grouping."""
        serializer = Serializer()
        expr = serializer.deserialize("(age > 18)")
        assert isinstance(expr, Gt)

    def test_precedence_not_and_or(self) -> None:
        """Test operator precedence: NOT > AND > OR."""
        serializer = Serializer()
        # a , b ; c should parse as a , (b ; c)
        expr = serializer.deserialize("a == 1 , b == 2 ; c == 3")
        assert isinstance(expr, AnyExpr)
        assert len(expr.exprs) == 2


class TestTypeAnnotationDeserialization:
    """Test type annotation handling in deserialization."""

    def test_field_with_type_annotation(self) -> None:
        """Test deserialization of field with type annotation."""
        serializer = Serializer()
        expr = serializer.deserialize("age{int} == 18")
        assert isinstance(expr, Eq)
        assert expr.field.name == "age"
        assert expr.field.type_ is int
        assert expr.value == 18

    def test_multiple_fields_with_types(self) -> None:
        """Test deserialization of multiple fields with type annotations."""
        serializer = Serializer()
        expr = serializer.deserialize('age{int} == 18 ; name{str} == "Alice"')
        assert isinstance(expr, AllExpr)
        assert expr.exprs[0].field.type_ is int  # type: ignore[attr-defined]
        assert expr.exprs[1].field.type_ is str  # type: ignore[attr-defined]

    def test_custom_type_deserialization(self) -> None:
        """Test deserialization with custom type registry."""

        def custom_transform(x: Any) -> str:
            return str(x).upper()

        serializer = Serializer()
        serializer.register_custom_type(custom_transform, "custom")

        expr = serializer.deserialize('field{custom} == "value"')
        assert isinstance(expr, Eq)
        assert expr.field.name == "field"
        assert expr.field.type_ is custom_transform


class TestRoundTrip:
    """Test round-trip serialization/deserialization."""

    @pytest.mark.parametrize(
        "expr",
        [
            Eq(F("age"), 18),
            Ne(F("status"), "active"),
            Lt(F("price"), 100.5),
            In(F("id"), (1, 2, 3)),
            Regex(F("name"), r"^A.*", None),
            IsNull(F("deleted_at"), True),
            TrueExpr(),
            FalseExpr(),
            AllExpr(Eq(F("age"), 18), Gt(F("score"), 75)),
            AnyExpr(Eq(F("status"), "active"), Eq(F("status"), "pending")),
            NotExpr(Eq(F("deleted"), True)),
        ],
    )
    def test_round_trip_without_types(self, expr: Expr) -> None:
        """Test that expressions survive round-trip serialization."""
        serializer = Serializer()
        serialized = serializer.serialize(expr)
        deserialized = serializer.deserialize(serialized)
        assert deserialized == expr

    @pytest.mark.parametrize(
        "expr",
        [
            Eq(F("age", int), 18),
            AllExpr(Eq(F("age", int), 18), Eq(F("name", str), "Alice")),
        ],
    )
    def test_round_trip_with_types(self, expr: Expr) -> None:
        """Test round-trip with type annotations enabled."""
        serializer = Serializer(include_all_types=True)
        serialized = serializer.serialize(expr)
        deserialized = serializer.deserialize(serialized)
        assert deserialized == expr

    def test_complex_nested_round_trip(self) -> None:
        """Test round-trip of complex nested expression."""
        expr = NotExpr(
            AllExpr(
                Gt(F("age"), 18),
                AnyExpr(Eq(F("status"), "active"), Eq(F("status"), "pending")),
                IsNull(F("deleted_at"), True),
            )
        )
        serializer = Serializer()
        serialized = serializer.serialize(expr)
        deserialized = serializer.deserialize(serialized)
        assert deserialized == expr


class TestEdgeCases:
    """Test edge cases and error handling."""

    def test_empty_and_expression(self) -> None:
        """Test serialization of empty AND expression."""
        expr = AllExpr()
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == "true()"

    def test_empty_or_expression(self) -> None:
        """Test serialization of empty OR expression."""
        expr = AnyExpr()
        serializer = Serializer()
        result = serializer.serialize(expr)
        assert result == "false()"

    def test_field_with_dots(self) -> None:
        """Test fields with dots in names (nested field access)."""
        serializer = Serializer()
        expr = Eq(F("user.profile.age"), 25)
        serialized = serializer.serialize(expr)
        assert serialized == "user.profile.age==25"

        deserialized = serializer.deserialize(serialized)
        assert isinstance(deserialized, Eq)
        assert deserialized.field.name == "user.profile.age"

    def test_negative_numbers(self) -> None:
        """Test serialization of negative numbers."""
        serializer = Serializer()
        expr = Gt(F("temperature"), -10.5)
        serialized = serializer.serialize(expr)
        assert serialized == "temperature>-10.5"

        deserialized = serializer.deserialize(serialized)
        assert isinstance(deserialized, Gt)
        assert deserialized.value == -10.5

    def test_identifier_as_value(self) -> None:
        """Test using identifiers as values (unquoted)."""
        serializer = Serializer()
        # Identifier values are preserved as strings
        expr = serializer.deserialize("status == active")
        assert isinstance(expr, Eq)
        assert expr.value == "active"

    @pytest.mark.parametrize(
        "invalid_text,exception",
        [
            ("invalid syntax", Exception),
            ("age >", Exception),  # Missing value
            ("== 10", Exception),  # Missing field
        ],
    )
    def test_invalid_syntax(self, invalid_text: str, exception: type) -> None:
        """Test that invalid syntax raises appropriate errors."""
        serializer = Serializer()
        with pytest.raises(exception):
            serializer.deserialize(invalid_text)

    def test_unknown_type_in_registry(self) -> None:
        """Test deserialization with unknown type name."""
        serializer = Serializer()
        # Unknown types are parsed but left as None
        expr = serializer.deserialize("field{unknown_type} == 10")
        assert isinstance(expr, Eq)
        assert expr.field.type_ is None  # Unknown type becomes None


class TestCustomTypeRegistry:
    """Test custom type registry functionality."""

    def test_initial_registry(self) -> None:
        """Test that serializer starts with default types."""
        serializer = Serializer()
        assert int in serializer.type_registry
        assert str in serializer.type_registry
        assert float in serializer.type_registry
        assert bool in serializer.type_registry

    def test_custom_registry_initialization(self) -> None:
        """Test initializing serializer with custom registry."""

        def custom_type(x: Any) -> Any:
            return x

        registry = {custom_type: "custom"}
        serializer = Serializer(type_registry=registry)

        # Should still have defaults
        assert int in serializer.type_registry
        # And custom type
        assert custom_type in serializer.type_registry

    def test_register_custom_type_method(self) -> None:
        """Test the register_custom_type method."""

        def custom_type(x: Any) -> Any:
            return x

        serializer = Serializer()
        serializer.register_custom_type(custom_type, "custom")

        assert custom_type in serializer.type_registry
        assert serializer.type_registry[custom_type] == "custom"
        assert "custom" in serializer._reverse_type_registry
        assert serializer._reverse_type_registry["custom"] is custom_type


class TestValueCasting:
    """Test automatic value casting during deserialization."""

    def test_comparison_value_cast_on_deserialize(self) -> None:
        """Test that comparison values are cast during deserialization."""
        import uuid

        serializer = Serializer()
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = 'my_id{uuid.UUID}=="11111111-1111-1111-1111-111111111111"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        assert expr.field.name == "my_id"
        assert expr.field.type_ is uuid.UUID
        # Value should be cast to UUID during deserialization
        assert isinstance(expr.value, uuid.UUID)
        assert expr.value == uuid.UUID("11111111-1111-1111-1111-111111111111")

    def test_in_values_cast_on_deserialize(self) -> None:
        """Test that IN values are cast during deserialization."""
        import uuid

        serializer = Serializer()
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = (
            "ids{uuid.UUID}=in=("
            '"11111111-1111-1111-1111-111111111111",'
            '"22222222-2222-2222-2222-222222222222"'
            ")"
        )
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, In)
        assert expr.field.name == "ids"
        assert expr.field.type_ is uuid.UUID
        # All values should be cast to UUID during deserialization
        assert len(expr.values) == 2
        assert all(isinstance(v, uuid.UUID) for v in expr.values)
        assert expr.values[0] == uuid.UUID("11111111-1111-1111-1111-111111111111")
        assert expr.values[1] == uuid.UUID("22222222-2222-2222-2222-222222222222")

    def test_eval_with_already_typed_data(self) -> None:
        """Test that eval works when data values are already the correct type."""
        import uuid

        serializer = Serializer()
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = 'my_id{uuid.UUID}=="11111111-1111-1111-1111-111111111111"'
        expr = serializer.deserialize(expr_str)

        # Test with data that already has UUID type
        data_with_uuid = {"my_id": uuid.UUID("11111111-1111-1111-1111-111111111111")}
        assert expr.evaluate(unwind_data(data_with_uuid)) is True

        # Test with data that has string (will be cast)
        data_with_string = {"my_id": "11111111-1111-1111-1111-111111111111"}
        assert expr.evaluate(unwind_data(data_with_string)) is True

        # Test with different UUID (should be False)
        data_different = {"my_id": uuid.UUID("22222222-2222-2222-2222-222222222222")}
        assert expr.evaluate(unwind_data(data_different)) is False

    def test_cast_with_int_type(self) -> None:
        """Test casting with built-in int type."""
        serializer = Serializer()

        expr_str = 'count{int}=="42"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        # Value should be cast to int during deserialization
        assert isinstance(expr.value, int)
        assert expr.value == 42

    def test_cast_with_float_type(self) -> None:
        """Test casting with built-in float type."""
        serializer = Serializer()

        expr_str = 'price{float}=="19.99"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        # Value should be cast to float during deserialization
        assert isinstance(expr.value, float)
        assert expr.value == 19.99


class TestImplicitFieldTypes:
    """Test implicit field type mapping."""

    def test_implicit_field_type_via_constructor(self) -> None:
        """Test implicit field type using constructor parameter."""
        import uuid

        implicit_field_types = {"my_id": uuid.UUID}
        serializer = Serializer(implicit_field_types=implicit_field_types)
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        # Without explicit type annotation
        expr_str = 'my_id=="11111111-1111-1111-1111-111111111111"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        assert expr.field.name == "my_id"
        assert expr.field.type_ is uuid.UUID
        # Value should be cast to UUID automatically
        assert isinstance(expr.value, uuid.UUID)
        assert expr.value == uuid.UUID("11111111-1111-1111-1111-111111111111")

    def test_implicit_field_type_via_register(self) -> None:
        """Test implicit field type using register_field_type method."""
        import uuid

        serializer = Serializer()
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")
        serializer.register_field_type("user_id", uuid.UUID)

        expr_str = 'user_id=="22222222-2222-2222-2222-222222222222"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        assert expr.field.name == "user_id"
        assert expr.field.type_ is uuid.UUID
        assert isinstance(expr.value, uuid.UUID)
        assert expr.value == uuid.UUID("22222222-2222-2222-2222-222222222222")

    def test_explicit_type_overrides_implicit(self) -> None:
        """Test that explicit type annotation overrides implicit field type."""
        import uuid
        from decimal import Decimal

        implicit_field_types = {"amount": Decimal}
        serializer = Serializer(implicit_field_types=implicit_field_types)
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")
        serializer.register_custom_type(Decimal, "Decimal")

        # Explicit type annotation should override implicit
        expr_str = 'amount{int}=="100"'
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, Eq)
        assert expr.field.name == "amount"
        # Should be int, not Decimal (explicit overrides implicit)
        assert expr.field.type_ is int
        assert isinstance(expr.value, int)
        assert expr.value == 100

    def test_implicit_type_in_compound_expressions(self) -> None:
        """Test implicit field types work in compound expressions."""
        import uuid

        serializer = Serializer(implicit_field_types={"status_id": uuid.UUID})
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = (
            'status_id=="11111111-1111-1111-1111-111111111111";'
            'status_id!="22222222-2222-2222-2222-222222222222"'
        )
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, AllExpr)
        eq_expr = expr.exprs[0]
        ne_expr = expr.exprs[1]

        assert isinstance(eq_expr, Eq)
        assert eq_expr.field.type_ is uuid.UUID
        assert isinstance(eq_expr.value, uuid.UUID)

        assert isinstance(ne_expr, Ne)
        assert ne_expr.field.type_ is uuid.UUID
        assert isinstance(ne_expr.value, uuid.UUID)

    def test_implicit_type_in_in_expression(self) -> None:
        """Test implicit field types work in IN expressions."""
        import uuid

        serializer = Serializer(implicit_field_types={"id": uuid.UUID})
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = (
            'id=in=("11111111-1111-1111-1111-111111111111","22222222-2222-2222-2222-222222222222")'
        )
        expr = serializer.deserialize(expr_str)

        assert isinstance(expr, In)
        assert expr.field.name == "id"
        assert expr.field.type_ is uuid.UUID
        # All values should be cast to UUID
        assert len(expr.values) == 2
        assert all(isinstance(v, uuid.UUID) for v in expr.values)

    def test_eval_with_implicit_field_type(self) -> None:
        """Test evaluation works correctly with implicit field types."""
        import uuid

        serializer = Serializer(implicit_field_types={"product_id": uuid.UUID})
        serializer.register_custom_type(uuid.UUID, "uuid.UUID")

        expr_str = 'product_id=="33333333-3333-3333-3333-333333333333"'
        expr = serializer.deserialize(expr_str)

        # Test with UUID in data
        data_uuid = {"product_id": uuid.UUID("33333333-3333-3333-3333-333333333333")}
        assert expr.evaluate(unwind_data(data_uuid)) is True

        # Test with string in data (will be cast)
        data_str = {"product_id": "33333333-3333-3333-3333-333333333333"}
        assert expr.evaluate(unwind_data(data_str)) is True

        # Test with different UUID
        data_different = {"product_id": uuid.UUID("44444444-4444-4444-4444-444444444444")}
        assert expr.evaluate(unwind_data(data_different)) is False


class TestSanitizer:
    """Test sanitizer callback functionality."""

    def test_sanitizer_receives_correct_parameters(self) -> None:
        """Test that sanitizer receives field name, value, and expected type."""
        received_calls: list[tuple[str, Any, Any]] = []

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            received_calls.append((field_name, value, expected_type))
            return value

        serializer = Serializer(sanitizer=sanitizer)
        serializer.deserialize("age==25")

        assert len(received_calls) == 1
        assert received_calls[0] == ("age", 25, None)

    def test_sanitizer_with_typed_field(self) -> None:
        """Test that sanitizer receives the expected type for typed fields."""
        received_calls: list[tuple[str, Any, Any]] = []

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            received_calls.append((field_name, value, expected_type))
            return value

        serializer = Serializer(sanitizer=sanitizer)
        serializer.deserialize("age{int}==25")

        assert len(received_calls) == 1
        assert received_calls[0] == ("age", 25, int)

    def test_sanitizer_can_modify_value(self) -> None:
        """Test that sanitizer can modify the value before it's used."""

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            # Convert all string values to uppercase
            if isinstance(value, str):
                return value.upper()
            return value

        serializer = Serializer(sanitizer=sanitizer)
        expr = serializer.deserialize('status=="active"')

        assert isinstance(expr, Eq)
        assert expr.value == "ACTIVE"

    def test_sanitizer_with_in_expression(self) -> None:
        """Test that sanitizer is applied to all values in IN expression."""
        received_calls: list[tuple[str, Any, Any]] = []

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            received_calls.append((field_name, value, expected_type))
            return value

        serializer = Serializer(sanitizer=sanitizer)
        serializer.deserialize('status=in=("active","pending","closed")')

        assert len(received_calls) == 3
        assert received_calls[0] == ("status", "active", None)
        assert received_calls[1] == ("status", "pending", None)
        assert received_calls[2] == ("status", "closed", None)

    def test_sanitizer_with_implicit_field_types(self) -> None:
        """Test that sanitizer receives field type from implicit_field_types registry."""
        received_calls: list[tuple[str, Any, Any]] = []

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            received_calls.append((field_name, value, expected_type))
            return value

        serializer = Serializer(implicit_field_types={"age": int}, sanitizer=sanitizer)
        serializer.deserialize("age==25")

        assert len(received_calls) == 1
        assert received_calls[0] == ("age", 25, int)

    def test_sanitizer_value_validation(self) -> None:
        """Test using sanitizer for value validation."""

        def sanitizer(field_name: str, value: Any, expected_type: Any) -> Any:
            # Validate that age is positive
            if field_name == "age" and isinstance(value, (int, float)) and value < 0:
                raise ValueError(f"Age cannot be negative: {value}")
            return value

        serializer = Serializer(sanitizer=sanitizer)

        # Valid age
        expr = serializer.deserialize("age==25")
        assert isinstance(expr, Eq)
        assert expr.value == 25

        # Invalid age
        with pytest.raises(ValueError, match="Age cannot be negative"):
            serializer.deserialize("age==-5")
