from __future__ import annotations

import pytest

from kyrodb import MetadataFilter, all_of, any_of, exact, in_values, negate, range_match


def test_exact_builder() -> None:
    filter_value = exact("tenant", "acme")
    assert isinstance(filter_value, MetadataFilter)
    proto = filter_value.to_proto()
    assert proto.exact.key == "tenant"
    assert proto.exact.value == "acme"


def test_exact_builder_rejects_empty_key_or_value() -> None:
    with pytest.raises(ValueError, match="filter key must be non-empty"):
        exact(" ", "acme")
    with pytest.raises(ValueError, match="filter value must be non-empty"):
        exact("tenant", "")


def test_in_values_rejects_empty_values() -> None:
    with pytest.raises(ValueError, match="non-empty"):
        in_values("tenant", [])


def test_in_values_rejects_empty_entries() -> None:
    with pytest.raises(ValueError, match="must not contain empty entries"):
        in_values("tenant", ["pro", ""])


def test_range_builder_requires_bound() -> None:
    with pytest.raises(ValueError, match="range bound"):
        range_match("timestamp")


def test_range_builder_rejects_empty_string_bound() -> None:
    with pytest.raises(ValueError, match="range bound"):
        range_match("timestamp", gte="")


def test_range_builder_rejects_whitespace_bound() -> None:
    with pytest.raises(ValueError, match="range bound"):
        range_match("timestamp", gte="   ")


def test_range_builder_single_bound_populates_oneof() -> None:
    filter_value = range_match("timestamp", gte="2026-02-16T00:00:00Z")
    proto = filter_value.to_proto()
    assert proto.WhichOneof("filter_type") == "range"
    assert proto.range.WhichOneof("bound") == "gte"
    assert proto.range.gte == "2026-02-16T00:00:00Z"


def test_range_builder_multiple_bounds_compose_with_and_filter() -> None:
    filter_value = range_match(
        "timestamp",
        gte="2026-02-16T00:00:00Z",
        lt="2026-02-17T00:00:00Z",
    )
    proto = filter_value.to_proto()
    assert proto.WhichOneof("filter_type") == "and_filter"
    assert len(proto.and_filter.filters) == 2
    bounds = [inner.range.WhichOneof("bound") for inner in proto.and_filter.filters]
    assert set(bounds) == {"gte", "lt"}


def test_composition_helpers() -> None:
    lhs = exact("region", "us")
    rhs = in_values("tier", ["pro", "enterprise"])

    and_filter = all_of([lhs, rhs])
    or_filter = any_of([lhs, rhs])
    not_filter = negate(lhs)

    and_proto = and_filter.to_proto()
    or_proto = or_filter.to_proto()
    not_proto = not_filter.to_proto()
    assert len(and_proto.and_filter.filters) == 2
    assert len(or_proto.or_filter.filters) == 2
    assert not_proto.not_filter.filter.exact.key == "region"


def test_composition_helpers_reject_empty_filters() -> None:
    with pytest.raises(ValueError, match="non-empty"):
        all_of([])
    with pytest.raises(ValueError, match="non-empty"):
        any_of([])
