"""Tests for the expression class hierarchy (composite and decorator patterns)."""

from therismos.expr import (
    FALSE,
    TRUE,
    AllExpr,
    AnyExpr,
    AtomicExpr,
    ComparisonExpr,
    CompositeExpr,
    DecoratorExpr,
    Eq,
    Expr,
    F,
    FieldBasedExpr,
    Gt,
    IsNull,
    MultiValuedFieldExpr,
    NotExpr,
    SingleValuedFieldExpr,
)


class TestAtomicExprInheritance:
    """Test that all atomic expressions inherit from AtomicExpr."""

    def test_eq_is_atomic(self) -> None:
        """Test that Eq is an atomic expression."""
        expr = F("x") == 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_ne_is_atomic(self) -> None:
        """Test that Ne is an atomic expression."""
        expr = F("x") != 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_lt_is_atomic(self) -> None:
        """Test that Lt is an atomic expression."""
        expr = F("x") < 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_le_is_atomic(self) -> None:
        """Test that Le is an atomic expression."""
        expr = F("x") <= 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_gt_is_atomic(self) -> None:
        """Test that Gt is an atomic expression."""
        expr = F("x") > 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_ge_is_atomic(self) -> None:
        """Test that Ge is an atomic expression."""
        expr = F("x") >= 5
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_regex_is_atomic(self) -> None:
        """Test that Regex is an atomic expression."""
        expr = F("name").matches(r"test.*")
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_in_is_atomic(self) -> None:
        """Test that In is an atomic expression."""
        expr = F("x").is_in(1, 2, 3)
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_is_null_is_atomic(self) -> None:
        """Test that IsNull is an atomic expression."""
        expr = F("x").is_null()
        assert isinstance(expr, AtomicExpr)
        assert isinstance(expr, Expr)

    def test_true_expr_is_atomic(self) -> None:
        """Test that TrueExpr is an atomic expression."""
        assert isinstance(TRUE, AtomicExpr)
        assert isinstance(TRUE, Expr)

    def test_false_expr_is_atomic(self) -> None:
        """Test that FalseExpr is an atomic expression."""
        assert isinstance(FALSE, AtomicExpr)
        assert isinstance(FALSE, Expr)


class TestCompositeExprInheritance:
    """Test that composite expressions inherit from CompositeExpr."""

    def test_all_expr_is_composite(self) -> None:
        """Test that AllExpr is a composite expression."""
        expr = AllExpr(F("x") == 1, F("y") == 2)
        assert isinstance(expr, CompositeExpr)
        assert isinstance(expr, Expr)

    def test_any_expr_is_composite(self) -> None:
        """Test that AnyExpr is a composite expression."""
        expr = AnyExpr(F("x") == 1, F("y") == 2)
        assert isinstance(expr, CompositeExpr)
        assert isinstance(expr, Expr)

    def test_all_expr_via_and_operator(self) -> None:
        """Test that & operator creates composite expressions."""
        expr = (F("x") == 1) & (F("y") == 2)
        assert isinstance(expr, CompositeExpr)
        assert isinstance(expr, AllExpr)

    def test_any_expr_via_or_operator(self) -> None:
        """Test that | operator creates composite expressions."""
        expr = (F("x") == 1) | (F("y") == 2)
        assert isinstance(expr, CompositeExpr)
        assert isinstance(expr, AnyExpr)


class TestDecoratorExprInheritance:
    """Test that decorator expressions inherit from DecoratorExpr."""

    def test_not_expr_is_decorator(self) -> None:
        """Test that NotExpr is a decorator expression."""
        expr = NotExpr(F("x") == 1)
        assert isinstance(expr, DecoratorExpr)
        assert isinstance(expr, Expr)

    def test_not_expr_via_invert_operator(self) -> None:
        """Test that ~ operator creates decorator expressions."""
        expr = ~(F("x") == 1)
        assert isinstance(expr, DecoratorExpr)
        assert isinstance(expr, NotExpr)


class TestClassHierarchyStructure:
    """Test the overall structure of the class hierarchy."""

    def test_atomic_composite_decorator_are_distinct(self) -> None:
        """Test that atomic, composite, and decorator are distinct categories."""
        atomic = F("x") == 1
        composite = AllExpr(F("x") == 1, F("y") == 2)
        decorator = NotExpr(F("x") == 1)

        # Atomic is not composite or decorator
        assert isinstance(atomic, AtomicExpr)
        assert not isinstance(atomic, CompositeExpr)
        assert not isinstance(atomic, DecoratorExpr)

        # Composite is not atomic or decorator
        assert not isinstance(composite, AtomicExpr)
        assert isinstance(composite, CompositeExpr)
        assert not isinstance(composite, DecoratorExpr)

        # Decorator is not atomic or composite
        assert not isinstance(decorator, AtomicExpr)
        assert not isinstance(decorator, CompositeExpr)
        assert isinstance(decorator, DecoratorExpr)

    def test_all_expressions_are_expr(self) -> None:
        """Test that all expression types inherit from Expr."""
        expressions = [
            F("x") == 1,  # Eq (atomic)
            F("x") != 1,  # Ne (atomic)
            F("x") < 1,  # Lt (atomic)
            F("x") <= 1,  # Le (atomic)
            F("x") > 1,  # Gt (atomic)
            F("x") >= 1,  # Ge (atomic)
            F("name").matches(r"test.*"),  # Regex (atomic)
            F("x").is_in(1, 2, 3),  # In (atomic)
            F("x").is_null(),  # IsNull (atomic)
            TRUE,  # TrueExpr (atomic)
            FALSE,  # FalseExpr (atomic)
            AllExpr(F("x") == 1, F("y") == 2),  # AllExpr (composite)
            AnyExpr(F("x") == 1, F("y") == 2),  # AnyExpr (composite)
            NotExpr(F("x") == 1),  # NotExpr (decorator)
        ]

        for expr in expressions:
            assert isinstance(expr, Expr)

    def test_complex_nested_expression(self) -> None:
        """Test a complex nested expression with all types."""
        # Build: NOT((x == 1 AND y > 2) OR z IS NULL)
        expr = NotExpr(AnyExpr(AllExpr(Eq(F("x"), 1), Gt(F("y"), 2)), IsNull(F("z"), True)))

        # Root is decorator
        assert isinstance(expr, DecoratorExpr)
        assert isinstance(expr, NotExpr)

        # First child is composite (AnyExpr)
        assert isinstance(expr.expr, CompositeExpr)
        assert isinstance(expr.expr, AnyExpr)

        # First child of AnyExpr is composite (AllExpr)
        assert isinstance(expr.expr.exprs[0], CompositeExpr)
        assert isinstance(expr.expr.exprs[0], AllExpr)

        # Children of AllExpr are atomic
        assert isinstance(expr.expr.exprs[0].exprs[0], AtomicExpr)  # Eq
        assert isinstance(expr.expr.exprs[0].exprs[1], AtomicExpr)  # Gt

        # Second child of AnyExpr is atomic
        assert isinstance(expr.expr.exprs[1], AtomicExpr)  # IsNull


class TestIntermediateClassInheritance:
    """Test the intermediate class hierarchy (FieldBasedExpr, etc.)."""

    def test_comparison_expressions_are_field_based(self) -> None:
        """Test that comparison expressions inherit from FieldBasedExpr."""
        expressions = [
            F("x") == 1,  # Eq
            F("x") != 1,  # Ne
            F("x") < 1,  # Lt
            F("x") <= 1,  # Le
            F("x") > 1,  # Gt
            F("x") >= 1,  # Ge
        ]
        for expr in expressions:
            assert isinstance(expr, FieldBasedExpr)
            assert isinstance(expr, AtomicExpr)

    def test_regex_is_field_based(self) -> None:
        """Test that Regex inherits from FieldBasedExpr."""
        expr = F("name").matches(r"test.*")
        assert isinstance(expr, FieldBasedExpr)
        assert isinstance(expr, AtomicExpr)

    def test_multi_valued_expressions_are_field_based(self) -> None:
        """Test that In and IsNull inherit from FieldBasedExpr."""
        in_expr = F("x").is_in(1, 2, 3)
        is_null_expr = F("x").is_null()

        assert isinstance(in_expr, FieldBasedExpr)
        assert isinstance(is_null_expr, FieldBasedExpr)

    def test_comparison_expressions_are_single_valued(self) -> None:
        """Test that comparison expressions inherit from SingleValuedFieldExpr."""
        expressions = [
            F("x") == 1,  # Eq
            F("x") != 1,  # Ne
            F("x") < 1,  # Lt
            F("x") <= 1,  # Le
            F("x") > 1,  # Gt
            F("x") >= 1,  # Ge
        ]
        for expr in expressions:
            assert isinstance(expr, SingleValuedFieldExpr)
            assert isinstance(expr, FieldBasedExpr)

    def test_regex_is_single_valued(self) -> None:
        """Test that Regex inherits from SingleValuedFieldExpr."""
        expr = F("name").matches(r"test.*")
        assert isinstance(expr, SingleValuedFieldExpr)
        assert isinstance(expr, FieldBasedExpr)

    def test_comparison_expressions_are_comparison_expr(self) -> None:
        """Test that comparison expressions inherit from ComparisonExpr."""
        expressions = [
            F("x") == 1,  # Eq
            F("x") != 1,  # Ne
            F("x") < 1,  # Lt
            F("x") <= 1,  # Le
            F("x") > 1,  # Gt
            F("x") >= 1,  # Ge
        ]
        for expr in expressions:
            assert isinstance(expr, ComparisonExpr)
            assert isinstance(expr, SingleValuedFieldExpr)

    def test_regex_is_not_comparison_expr(self) -> None:
        """Test that Regex does NOT inherit from ComparisonExpr."""
        expr = F("name").matches(r"test.*")
        assert not isinstance(expr, ComparisonExpr)
        assert isinstance(expr, SingleValuedFieldExpr)

    def test_multi_valued_expressions_are_multi_valued(self) -> None:
        """Test that In and IsNull inherit from MultiValuedFieldExpr."""
        in_expr = F("x").is_in(1, 2, 3)
        is_null_expr = F("x").is_null()

        assert isinstance(in_expr, MultiValuedFieldExpr)
        assert isinstance(is_null_expr, MultiValuedFieldExpr)
        assert isinstance(in_expr, FieldBasedExpr)
        assert isinstance(is_null_expr, FieldBasedExpr)

    def test_constants_are_not_field_based(self) -> None:
        """Test that TRUE and FALSE do NOT inherit from FieldBasedExpr."""
        assert not isinstance(TRUE, FieldBasedExpr)
        assert not isinstance(FALSE, FieldBasedExpr)
        assert isinstance(TRUE, AtomicExpr)
        assert isinstance(FALSE, AtomicExpr)


class TestBaseClassAbstractness:
    """Test that base classes are abstract and cannot be instantiated directly."""

    def test_expr_is_abstract(self) -> None:
        """Test that Expr cannot be instantiated directly."""
        try:
            Expr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate Expr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_atomic_expr_is_abstract(self) -> None:
        """Test that AtomicExpr cannot be instantiated directly."""
        try:
            AtomicExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate AtomicExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_field_based_expr_is_abstract(self) -> None:
        """Test that FieldBasedExpr cannot be instantiated directly."""
        try:
            FieldBasedExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate FieldBasedExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_single_valued_field_expr_is_abstract(self) -> None:
        """Test that SingleValuedFieldExpr cannot be instantiated directly."""
        try:
            SingleValuedFieldExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate SingleValuedFieldExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_comparison_expr_is_abstract(self) -> None:
        """Test that ComparisonExpr cannot be instantiated directly."""
        try:
            ComparisonExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate ComparisonExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_multi_valued_field_expr_is_abstract(self) -> None:
        """Test that MultiValuedFieldExpr cannot be instantiated directly."""
        try:
            MultiValuedFieldExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate MultiValuedFieldExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_composite_expr_is_abstract(self) -> None:
        """Test that CompositeExpr cannot be instantiated directly."""
        try:
            CompositeExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate CompositeExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()

    def test_decorator_expr_is_abstract(self) -> None:
        """Test that DecoratorExpr cannot be instantiated directly."""
        try:
            DecoratorExpr()  # type: ignore[abstract]
            raise AssertionError("Should not be able to instantiate DecoratorExpr")
        except TypeError as e:
            assert "abstract" in str(e).lower()
