import json

import pyarrow as pa

from n6k_server.pushdown import apply_columns, apply_filters


SAMPLE = pa.table({
    "id": pa.array([1, 2, 3], type=pa.int64()),
    "name": pa.array(["alice", "bob", "charlie"], type=pa.utf8()),
    "score": pa.array([95.5, 87.3, 91.8], type=pa.float64()),
    "tag": pa.array(["a", None, "b"], type=pa.utf8()),
})


class TestApplyColumns:
    def test_none_returns_unchanged(self):
        result = apply_columns(SAMPLE, None)
        assert result.column_names == ["id", "name", "score", "tag"]

    def test_empty_string_returns_unchanged(self):
        result = apply_columns(SAMPLE, "")
        assert result.column_names == ["id", "name", "score", "tag"]

    def test_valid_columns(self):
        result = apply_columns(SAMPLE, "id,name")
        assert result.column_names == ["id", "name"]
        assert result.num_rows == 3

    def test_nonexistent_columns_ignored(self):
        result = apply_columns(SAMPLE, "id,nonexistent")
        assert result.column_names == ["id"]

    def test_all_nonexistent_returns_unchanged(self):
        result = apply_columns(SAMPLE, "foo,bar")
        assert result.column_names == ["id", "name", "score", "tag"]

    def test_whitespace_trimmed(self):
        result = apply_columns(SAMPLE, " id , name ")
        assert result.column_names == ["id", "name"]


class TestApplyFilters:
    def test_none_returns_unchanged(self):
        result = apply_filters(SAMPLE, None)
        assert result.num_rows == 3

    def test_empty_string_returns_unchanged(self):
        result = apply_filters(SAMPLE, "")
        assert result.num_rows == 3

    def test_malformed_json_returns_unchanged(self):
        result = apply_filters(SAMPLE, "not json")
        assert result.num_rows == 3

    def test_non_list_json_returns_unchanged(self):
        result = apply_filters(SAMPLE, '{"col":"id"}')
        assert result.num_rows == 3

    def test_equal(self):
        f = json.dumps([{"col": "id", "op": "=", "value": 1}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [1]

    def test_not_equal(self):
        f = json.dumps([{"col": "id", "op": "!=", "value": 1}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [2, 3]

    def test_greater(self):
        f = json.dumps([{"col": "id", "op": ">", "value": 1}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [2, 3]

    def test_greater_equal(self):
        f = json.dumps([{"col": "id", "op": ">=", "value": 2}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [2, 3]

    def test_less(self):
        f = json.dumps([{"col": "id", "op": "<", "value": 3}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [1, 2]

    def test_less_equal(self):
        f = json.dumps([{"col": "id", "op": "<=", "value": 2}])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [1, 2]

    def test_in(self):
        f = json.dumps([{"col": "name", "op": "in", "value": ["alice", "bob"]}])
        result = apply_filters(SAMPLE, f)
        assert result.column("name").to_pylist() == ["alice", "bob"]

    def test_is_null(self):
        f = json.dumps([{"col": "tag", "op": "is_null"}])
        result = apply_filters(SAMPLE, f)
        assert result.column("name").to_pylist() == ["bob"]

    def test_is_not_null(self):
        f = json.dumps([{"col": "tag", "op": "is_not_null"}])
        result = apply_filters(SAMPLE, f)
        assert result.column("name").to_pylist() == ["alice", "charlie"]

    def test_multiple_filters_anded(self):
        f = json.dumps([
            {"col": "id", "op": ">", "value": 1},
            {"col": "score", "op": "<", "value": 90.0},
        ])
        result = apply_filters(SAMPLE, f)
        assert result.column("name").to_pylist() == ["bob"]

    def test_unknown_op_skipped(self):
        f = json.dumps([
            {"col": "id", "op": "like", "value": "1"},
            {"col": "id", "op": "=", "value": 2},
        ])
        result = apply_filters(SAMPLE, f)
        assert result.column("id").to_pylist() == [2]

    def test_missing_column_skipped(self):
        f = json.dumps([{"col": "missing", "op": "=", "value": 1}])
        result = apply_filters(SAMPLE, f)
        assert result.num_rows == 3

    def test_in_with_non_list_value_skipped(self):
        f = json.dumps([{"col": "id", "op": "in", "value": "not a list"}])
        result = apply_filters(SAMPLE, f)
        assert result.num_rows == 3
