# tests/test_q_objects.py

import pytest
from atomsql.orm.models import Model
from atomsql.orm.fields import StringField, IntegerField
from atomsql.orm.query import Q


class User(Model):
    name = StringField()
    age = IntegerField()
    status = StringField(default="active")


@pytest.fixture
def setup_users(db):
    """Fixture to register the model and seed test data."""
    db.register(User)

    # Seed data
    User(name="Alice", age=25, status="active").save()
    User(name="Bob", age=40, status="admin").save()
    User(name="Charlie", age=70, status="active").save()
    User(name="Dave", age=15, status="inactive").save()
    return User


def test_q_object_tree_structure():
    """Verify that Q objects combine into the correct tree structure."""
    q1 = Q(age__gt=18)
    q2 = Q(status="active")

    # Test AND
    and_q = q1 & q2
    assert and_q.connector == Q.AND
    assert len(and_q.children) == 2

    # Test OR
    or_q = q1 | q2
    assert or_q.connector == Q.OR
    assert len(or_q.children) == 2

    # Test Negation
    not_q = ~q1
    assert not_q.negated is True


def test_sql_generation_complex_q(setup_users):
    """Verify the SQL produced for complex, nested Q logic."""
    User = setup_users

    # 1. Simple OR
    qs = User.filter(Q(age__gt=30) | Q(status="admin"))
    sql, params = qs._build_sql()
    # Expectation: "user"."age" > ? OR "user"."status" = ?
    assert "OR" in sql
    assert params == [30, "admin"]

    # 2. Negation (NOT)
    qs = User.filter(~Q(status="inactive"))
    sql, params = qs._build_sql()
    assert "NOT" in sql
    assert "status" in sql

    # 3. Deeply Nested logic
    # (status='active' AND (age < 18 OR age > 65))
    qs = User.filter(Q(status="active") & (Q(age__lt=18) | Q(age__gt=65)))
    sql, params = qs._build_sql()
    # Verify parentheses exist for precedence
    assert sql.count("(") >= 2
    assert params == ["active", 18, 65]


def test_q_filter_execution(setup_users):
    """Verify that the database returns the correct records for Q filters."""
    User = setup_users

    # Find users who are either under 20 OR over 60
    results = list(User.filter(Q(age__lt=20) | Q(age__gt=60)))
    names = [u.name for u in results]
    assert len(names) == 2
    assert "Dave" in names  # age 15
    assert "Charlie" in names  # age 70

    # Complex: (Status active OR admin) AND age > 30
    results = list(
        User.filter((Q(status="active") | Q(status="admin")) & Q(age__gt=35))
    )
    names = [u.name for u in results]
    assert len(names) == 2
    assert "Bob" in names  # admin, age 40
    assert "Charlie" in names  # active, age 70


def test_exclude_with_q(setup_users):
    """Verify that exclude() correctly negates Q objects."""
    User = setup_users

    # Exclude users who are active (should leave Bob and Dave)
    results = list(User.exclude(status="active"))
    names = [u.name for u in results]
    assert "Alice" not in names
    assert "Charlie" not in names
    assert "Bob" in names
    assert "Dave" in names


def test_mixed_kwargs_and_q(setup_users):
    """Verify mixing standard filter kwargs and Q objects."""
    User = setup_users

    # age=25 is ANDed with the OR block
    qs = User.filter(Q(status="active") | Q(status="admin"), age=25)
    results = list(qs)

    assert len(results) == 1
    assert results[0].name == "Alice"


def test_operator_lookups_in_q(setup_users):
    """Test standard lookups like __in and __contains inside Q."""
    User = setup_users

    # Test __in
    results = list(User.filter(Q(name__in=["Alice", "Bob"])))
    assert len(results) == 2

    # Test __contains (LIKE)
    results = list(User.filter(Q(name__contains="li")))  # Alice, Charlie
    assert len(results) == 2
    assert "Alice" in [u.name for u in results]
