"""Tests for visitor pattern implementation."""

from therismos import (
    FALSE,
    TRUE,
    CountVisitor,
    DictVisitor,
    F,
    FieldGathererVisitor,
    StringVisitor,
)


class TestStringVisitor:
    """Test the string visitor on various expressions."""

    def test_simple_comparison(self) -> None:
        """Test visitor on simple comparison."""
        age = F("age")
        expr = age > 18
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert result == "age > 18"

    def test_equality(self) -> None:
        """Test visitor on equality."""
        name = F("name")
        expr = name == "Alice"
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert result == "name = 'Alice'"

    def test_in_expression(self) -> None:
        """Test visitor on IN expression."""
        status = F("status")
        expr = status.is_in("active", "pending")
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert "IN" in result
        assert "active" in result
        assert "pending" in result

    def test_is_null(self) -> None:
        """Test visitor on IS NULL."""
        deleted = F("deleted_at")
        expr = deleted.is_null()
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert result == "deleted_at IS NULL"

    def test_is_not_null(self) -> None:
        """Test visitor on IS NOT NULL."""
        created = F("created_at")
        expr = created.is_not_null()
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert result == "created_at IS NOT NULL"

    def test_and_expression(self) -> None:
        """Test visitor on AND expression."""
        age = F("age")
        name = F("name")
        expr = (age > 18) & (name == "Alice")
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert "AND" in result
        assert "age > 18" in result
        assert "name = 'Alice'" in result

    def test_or_expression(self) -> None:
        """Test visitor on OR expression."""
        status = F("status")
        expr = (status == "active") | (status == "pending")
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert "OR" in result
        assert "active" in result
        assert "pending" in result

    def test_not_expression(self) -> None:
        """Test visitor on NOT expression."""
        age = F("age")
        expr = ~(age > 18)
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert "NOT" in result
        assert "age > 18" in result

    def test_complex_expression(self) -> None:
        """Test visitor on complex nested expression."""
        age = F("age")
        name = F("name")
        status = F("status")
        expr = ((age > 18) & (name == "Alice")) | (status == "admin")
        visitor = StringVisitor()
        result = expr.accept(visitor)
        assert "OR" in result
        assert "AND" in result

    def test_true_false_constants(self) -> None:
        """Test visitor on TRUE/FALSE constants."""
        visitor = StringVisitor()
        assert TRUE.accept(visitor) == "TRUE"
        assert FALSE.accept(visitor) == "FALSE"


class TestCountVisitor:
    """Test the count visitor on various expressions."""

    def test_atomic_expression_count(self) -> None:
        """Test that atomic expressions count as 1."""
        age = F("age")
        expr = age > 18
        visitor = CountVisitor()
        assert expr.accept(visitor) == 1

    def test_and_expression_count(self) -> None:
        """Test counting nodes in AND expression."""
        age = F("age")
        name = F("name")
        expr = (age > 18) & (name == "Alice")
        visitor = CountVisitor()
        # 1 AllExpr + 2 atomic = 3
        assert expr.accept(visitor) == 3

    def test_or_expression_count(self) -> None:
        """Test counting nodes in OR expression."""
        status = F("status")
        expr = (status == "active") | (status == "pending")
        visitor = CountVisitor()
        # 1 AnyExpr + 2 atomic = 3
        assert expr.accept(visitor) == 3

    def test_not_expression_count(self) -> None:
        """Test counting nodes in NOT expression."""
        age = F("age")
        expr = ~(age > 18)
        visitor = CountVisitor()
        # 1 NotExpr + 1 atomic = 2
        assert expr.accept(visitor) == 2

    def test_complex_expression_count(self) -> None:
        """Test counting nodes in complex expression."""
        age = F("age")
        name = F("name")
        status = F("status")
        # ((age > 18) & (name == "Alice")) | (status == "admin")
        expr = ((age > 18) & (name == "Alice")) | (status == "admin")
        visitor = CountVisitor()
        # 1 AnyExpr + 1 AllExpr + 3 atomic = 5
        assert expr.accept(visitor) == 5


class TestDictVisitor:
    """Test dictionary conversion visitor."""

    def test_simple_comparison_to_dict(self) -> None:
        """Test converting simple comparison to dict."""
        age = F("age")
        expr = age > 18
        visitor = DictVisitor()
        result = expr.accept(visitor)
        assert result == {"type": "gt", "field": "age", "value": 18}

    def test_and_to_dict(self) -> None:
        """Test converting AND expression to dict."""
        age = F("age")
        name = F("name")
        expr = (age > 18) & (name == "Alice")
        visitor = DictVisitor()
        result = expr.accept(visitor)
        assert result["type"] == "and"
        assert len(result["exprs"]) == 2

    def test_complex_to_dict(self) -> None:
        """Test converting complex expression to dict."""
        age = F("age")
        status = F("status")
        expr = ~((age > 18) & (status == "active"))
        visitor = DictVisitor()
        result = expr.accept(visitor)
        assert result["type"] == "not"
        assert result["expr"]["type"] == "and"


class TestVisitorTypeHints:
    """Test that visitor pattern works with type hints."""

    def test_visitor_returns_correct_type(self) -> None:
        """Test that visitor methods return the correct type."""
        age = F("age")
        expr = age > 18

        string_visitor = StringVisitor()
        string_result = expr.accept(string_visitor)
        assert isinstance(string_result, str)

        count_visitor = CountVisitor()
        count_result = expr.accept(count_visitor)
        assert isinstance(count_result, int)

        dict_visitor = DictVisitor()
        dict_result = expr.accept(dict_visitor)
        assert isinstance(dict_result, dict)


class TestFieldGathererVisitor:
    """Test the field gatherer visitor."""

    def test_single_field(self) -> None:
        """Test gathering fields from a simple expression."""
        age = F("age")
        expr = age > 18
        visitor = FieldGathererVisitor()
        expr.accept(visitor)
        assert visitor.field_names == {"age"}

    def test_multiple_fields(self) -> None:
        """Test gathering fields from expression with multiple fields."""
        age = F("age")
        name = F("name")
        status = F("status")
        expr = (age > 18) & (name == "Alice") | (status == "active")
        visitor = FieldGathererVisitor()
        expr.accept(visitor)
        assert visitor.field_names == {"age", "name", "status"}

    def test_duplicate_fields(self) -> None:
        """Test that duplicate field names are not duplicated in the set."""
        age = F("age")
        expr = (age > 18) & (age < 65)
        visitor = FieldGathererVisitor()
        expr.accept(visitor)
        assert visitor.field_names == {"age"}

    def test_nested_expressions(self) -> None:
        """Test gathering fields from nested expressions."""
        age = F("age")
        name = F("name")
        status = F("status")
        dept = F("department")
        expr = ~(((age > 18) & (name == "Alice")) | ((status == "active") & (dept == "sales")))
        visitor = FieldGathererVisitor()
        expr.accept(visitor)
        assert visitor.field_names == {"age", "name", "status", "department"}

    def test_no_fields(self) -> None:
        """Test gathering fields from expression with no field-based expressions."""
        visitor = FieldGathererVisitor()
        TRUE.accept(visitor)
        assert visitor.field_names == set()

    def test_various_expression_types(self) -> None:
        """Test gathering fields from various expression types."""
        name = F("name")
        age = F("age")
        deleted = F("deleted_at")
        status = F("status")
        expr = (
            (name.matches(r"^A.*"))
            & (age.is_in(25, 30, 35))
            & (deleted.is_null())
            & (status != "inactive")
        )
        visitor = FieldGathererVisitor()
        expr.accept(visitor)
        assert visitor.field_names == {"name", "age", "deleted_at", "status"}
