import pytest
import json
import rusty_jq


@pytest.fixture
def data():
    return [
        {"id": 1, "name": "Alice", "active": True, "score": 90, "role": "admin"},
        {"id": 2, "name": "Bob", "active": False, "score": 60, "role": "user"},
        {"id": 3, "name": "Carol", "active": True, "score": 45, "role": "user"},
        {"id": 4, "name": "Dave", "active": None, "score": 80, "role": "admin"},
    ]


@pytest.fixture
def json_str(data):
    return json.dumps(data)


# --- Bare boolean path ---

@pytest.mark.parametrize("query,expected", [
    # select(.active) keeps truthy values (True)
    (".[] | select(.active) | .name", ["Alice", "Carol"]),
    # select(.missing) — missing field is falsy, drops all
    (".[] | select(.missing) | .name", []),
])
def test_bool_path(json_str, query, expected):
    assert list(rusty_jq.compile(query).input(json_str)) == expected


# --- Pipe-not ---

@pytest.mark.parametrize("query,expected", [
    # select(.active | not) keeps falsy values (false, null)
    (".[] | select(.active | not) | .name", ["Bob", "Dave"]),
])
def test_pipe_not(json_str, query, expected):
    assert list(rusty_jq.compile(query).input(json_str)) == expected


# --- Path-to-path comparison ---

def test_path_to_path():
    data = json.dumps([
        {"a": 1, "b": 1},
        {"a": 2, "b": 3},
        {"a": 5, "b": 5},
    ])
    result = list(rusty_jq.compile(".[] | select(.a == .b) | .a").input(data))
    assert result == [1, 5]


def test_path_to_path_string():
    data = json.dumps([
        {"x": "hello", "y": "hello"},
        {"x": "hello", "y": "world"},
    ])
    result = list(rusty_jq.compile('.[] | select(.x == .y) | .x').input(data))
    assert result == ["hello"]


# --- And ---

@pytest.mark.parametrize("query,expected", [
    (".[] | select(.active and .score > 50) | .name", ["Alice"]),
])
def test_and(json_str, query, expected):
    assert list(rusty_jq.compile(query).input(json_str)) == expected


# --- Or ---

@pytest.mark.parametrize("query,expected", [
    ('.[] | select(.role == "admin" or .score > 70) | .name', ["Alice", "Dave"]),
])
def test_or(json_str, query, expected):
    assert list(rusty_jq.compile(query).input(json_str)) == expected


# --- And/Or precedence: and binds tighter than or ---

def test_precedence():
    # .active and .score > 70 or .role == "admin"
    # should parse as (.active and .score > 70) or (.role == "admin")
    # Alice: active=True, score=90 -> and=True -> True
    # Bob: active=False -> and=False, role=user -> False
    # Carol: active=True, score=45 -> and=False, role=user -> False
    # Dave: active=null -> and=False, role=admin -> True
    data = json.dumps([
        {"id": 1, "name": "Alice", "active": True, "score": 90, "role": "admin"},
        {"id": 2, "name": "Bob", "active": False, "score": 60, "role": "user"},
        {"id": 3, "name": "Carol", "active": True, "score": 45, "role": "user"},
        {"id": 4, "name": "Dave", "active": None, "score": 80, "role": "admin"},
    ])
    result = list(rusty_jq.compile(
        '.[] | select(.active and .score > 70 or .role == "admin") | .name'
    ).input(data))
    assert result == ["Alice", "Dave"]


# --- Parenthesized grouping ---

def test_parenthesized_grouping():
    # .active and (.score > 70 or .role == "admin")
    # Alice: active=True and (90>70=True or admin=True) -> True
    # Bob: active=False -> False
    # Carol: active=True and (45>70=False or user!=admin=False) -> False
    # Dave: active=null -> False
    data = json.dumps([
        {"id": 1, "name": "Alice", "active": True, "score": 90, "role": "admin"},
        {"id": 2, "name": "Bob", "active": False, "score": 60, "role": "user"},
        {"id": 3, "name": "Carol", "active": True, "score": 45, "role": "user"},
        {"id": 4, "name": "Dave", "active": None, "score": 80, "role": "admin"},
    ])
    result = list(rusty_jq.compile(
        '.[] | select(.active and (.score > 70 or .role == "admin")) | .name'
    ).input(data))
    assert result == ["Alice"]


# --- Null / missing field truthiness ---

def test_null_is_falsy():
    data = json.dumps([{"val": None}, {"val": 0}, {"val": ""}, {"val": False}])
    result = list(rusty_jq.compile(".[] | select(.val) | .val").input(data))
    # null and false are falsy; 0 and "" are truthy
    assert result == [0, ""]


# --- Not on comparison ---

def test_not_on_comparison():
    data = json.dumps([{"x": 1}, {"x": 2}, {"x": 3}])
    result = list(rusty_jq.compile(".[] | select(.x == 2 | not) | .x").input(data))
    assert result == [1, 3]


# --- Backward compatibility: existing select patterns still work ---

@pytest.mark.parametrize("query,expected", [
    (".[] | select(.score > 50) | .name", ["Alice", "Bob", "Dave"]),
    (".[] | select(.score == 90) | .name", ["Alice"]),
    ('.[] | select(.role == "admin") | .name', ["Alice", "Dave"]),
    (".[] | select(.id != 1) | .name", ["Bob", "Carol", "Dave"]),
    (".[] | select(.score >= 80) | .name", ["Alice", "Dave"]),
    (".[] | select(.score <= 60) | .name", ["Bob", "Carol"]),
])
def test_backward_compat(json_str, query, expected):
    assert list(rusty_jq.compile(query).input(json_str)) == expected
