import datetime
import time
import uuid
from typing import Callable, Iterable, List, Optional

import pytest as pytest

import weaviate
from integration.conftest import CollectionFactory
from weaviate.collections.classes.config import Configure, DataType, Property, ReferenceProperty
from weaviate.collections.classes.data import DataObject
from weaviate.collections.classes.filters import Filter, _Filters, _FilterValue
from weaviate.collections.classes.grpc import MetadataQuery, QueryReference, Sort
from weaviate.collections.classes.internal import ReferenceToMulti
from weaviate.types import UUID

NOW = datetime.datetime.now(datetime.timezone.utc)
LATER = NOW + datetime.timedelta(hours=1)
MUCH_LATER = NOW + datetime.timedelta(days=1)

UUID1 = uuid.uuid4()
UUID2 = uuid.uuid4()
UUID3 = uuid.uuid4()
UUID4 = uuid.uuid4()


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_property("name").equal("Banana"), [0]),
        (Filter.by_property("name").not_equal("Banana"), [1, 2]),
        (Filter.by_property("name").like("*nana"), [0]),
    ],
)
def test_filters_text(
    collection_factory: CollectionFactory,
    weaviate_filter: _Filters,
    results: List[int],
) -> None:
    collection = collection_factory(
        properties=[Property(name="Name", data_type=DataType.TEXT)],
        vectorizer_config=Configure.Vectorizer.none(),
    )

    uuids = [
        collection.data.insert({"name": "Banana"}),
        collection.data.insert({"name": "Apple"}),
        collection.data.insert({"name": "Mountain"}),
    ]

    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_property("texts").like("*nana"), [1]),
        (Filter.by_property("texts").equal("banana"), [1]),
        (Filter.by_property("ints").equal(3), [1]),
        (Filter.by_property("ints").greater_or_equal(3), [1, 2]),
        (Filter.by_property("floats").equal(3), [1]),
        (Filter.by_property("floats").less_or_equal(3), [0, 1]),
    ],
)
def test_array_types(
    collection_factory: CollectionFactory,
    weaviate_filter: _FilterValue,
    results: List[int],
) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[
            Property(name="texts", data_type=DataType.TEXT_ARRAY),
            Property(name="ints", data_type=DataType.INT_ARRAY),
            Property(name="floats", data_type=DataType.NUMBER_ARRAY),
        ],
    )

    uuids = [
        collection.data.insert({"texts": ["an", "apple"], "ints": [1, 2], "floats": [1.0, 2.0]}),
        collection.data.insert({"texts": ["a", "banana"], "ints": [2, 3], "floats": [2.0, 3.0]}),
        collection.data.insert({"texts": ["a", "text"], "ints": [4, 5], "floats": [4.0, 5.0]}),
    ]

    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_property("int").equal(1), [0]),
        (Filter.by_property("int").equal(val=1.0), [0]),
        (Filter.by_property("int").equal(val=1.2), None),
        (Filter.by_property("float").equal(val=1), [0]),
        (Filter.by_property("float").equal(val=1.0), [0]),
    ],
)
def test_filter_with_wrong_types(
    collection_factory: CollectionFactory,
    weaviate_filter: _FilterValue,
    results: Optional[List[int]],
) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[
            Property(name="int", data_type=DataType.INT),
            Property(name="float", data_type=DataType.NUMBER),
        ],
    )

    uuids = [
        collection.data.insert({"int": 1, "float": 1.0}),
        collection.data.insert({"int": 2, "float": 2.0}),
        collection.data.insert({"int": 3, "float": 3.0}),
    ]

    if results is not None:
        objects = collection.query.fetch_objects(filters=weaviate_filter).objects
        assert len(objects) == len(results)

        uuids = [uuids[result] for result in results]
        assert all(obj.uuid in uuids for obj in objects)
    else:
        with pytest.raises(weaviate.exceptions.WeaviateQueryError):
            collection.query.fetch_objects(filters=weaviate_filter).objects


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_property("num").greater_than(1) & Filter.by_property("num").less_than(3), [1]),
        (
            (Filter.by_property("num").less_or_equal(1))
            | Filter.by_property("num").greater_or_equal(3),
            [0, 2],
        ),
        (
            Filter.by_property("num").less_or_equal(1)
            | Filter.by_property("num").greater_or_equal(3),
            [0, 2],
        ),
        (
            (
                Filter.by_property("num").less_or_equal(1)
                & Filter.by_property("num").greater_or_equal(1)
            )
            | Filter.by_property("num").greater_or_equal(3)
            | Filter.by_property("num").is_none(True),
            [0, 2, 3],
        ),
    ],
)
def test_filters_nested(
    collection_factory: CollectionFactory,
    weaviate_filter: _Filters,
    results: List[int],
) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[Property(name="num", data_type=DataType.NUMBER)],
        inverted_index_config=Configure.inverted_index(index_null_state=True),
    )

    uuids = [
        collection.data.insert({"num": 1.0}),
        collection.data.insert({"num": 2.0}),
        collection.data.insert({"num": 3.0}),
        collection.data.insert({"num": None}),
    ]

    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


def test_length_filter(collection_factory: CollectionFactory) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[Property(name="field", data_type=DataType.TEXT)],
        inverted_index_config=Configure.inverted_index(index_property_length=True),
    )
    uuids = [
        collection.data.insert({"field": "one"}),
        collection.data.insert({"field": "two"}),
        collection.data.insert({"field": "three"}),
        collection.data.insert({"field": "four"}),
    ]
    objects = collection.query.fetch_objects(
        filters=Filter.by_property(name="field", length=True).equal(3)
    ).objects

    results = [0, 1]
    assert len(objects) == len(results)
    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_property("number").is_none(True), [3]),
        (Filter.by_property("number").is_none(False), [0, 1, 2]),
    ],
)
def test_filters_comparison(
    collection_factory: CollectionFactory,
    weaviate_filter: _FilterValue,
    results: List[int],
) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[Property(name="number", data_type=DataType.INT)],
        inverted_index_config=Configure.inverted_index(index_null_state=True),
    )

    uuids = [
        collection.data.insert({"number": 1}),
        collection.data.insert({"number": 2}),
        collection.data.insert({"number": 3}),
        collection.data.insert({"number": None}),
    ]

    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


@pytest.mark.parametrize(
    "weaviate_filter,results,require_version",
    [
        (Filter.by_property("ints").contains_any([1, 4]), [0, 3], None),
        (Filter.by_property("ints").contains_any([1.0, 4]), [0, 3], None),
        (Filter.by_property("ints").contains_any([10]), [], None),
        (Filter.by_property("int").contains_any([1]), [0, 1], None),
        (Filter.by_property("text").contains_any(["test"]), [0, 1], None),
        (Filter.by_property("text").contains_any(["real", "deal"]), [1, 2, 3], None),
        (Filter.by_property("texts").contains_any(["test"]), [0, 1], None),
        (Filter.by_property("texts").contains_any(["real", "deal"]), [1, 2, 3], None),
        (Filter.by_property("float").contains_any([2.0]), [], None),
        (Filter.by_property("float").contains_any([2]), [], None),
        (Filter.by_property("float").contains_any([8]), [3], None),
        (Filter.by_property("float").contains_any([8.0]), [3], None),
        (Filter.by_property("floats").contains_any([2.0]), [0, 1], None),
        (Filter.by_property("floats").contains_any([0.4, 0.7]), [0, 1, 3], None),
        (Filter.by_property("floats").contains_any([2]), [0, 1], None),
        (Filter.by_property("bools").contains_any([True, False]), [0, 1, 3], None),
        (Filter.by_property("bools").contains_any([False]), [0, 1], None),
        (Filter.by_property("bool").contains_any([True]), [0, 1, 3], None),
        (Filter.by_property("ints").contains_all([1, 4]), [0], None),
        (Filter.by_property("text").contains_all(["real", "test"]), [1], None),
        (Filter.by_property("texts").contains_all(["real", "test"]), [1], None),
        (Filter.by_property("floats").contains_all([0.7, 2]), [1], None),
        (Filter.by_property("bools").contains_all([True, False]), [0], None),
        (Filter.by_property("bool").contains_all([True, False]), [], None),
        (Filter.by_property("bool").contains_all([True]), [0, 1, 3], None),
        (Filter.by_property("dates").contains_any([NOW, MUCH_LATER]), [0, 1, 3], None),
        (Filter.by_property("dates").contains_any([NOW]), [0, 1], None),
        (Filter.by_property("date").equal(NOW), [0], None),
        (Filter.by_property("date").greater_than(NOW), [1, 3], None),
        (Filter.by_property("uuids").contains_all([UUID2, UUID1]), [0, 3], None),
        (Filter.by_property("uuids").contains_any([UUID2, UUID1]), [0, 1, 3], None),
        (Filter.by_property("uuid").contains_any([UUID3]), [], None),
        (Filter.by_property("uuid").contains_any([UUID1]), [0], None),
        (Filter.by_property("_id").contains_any([UUID1, UUID3]), [0, 2], None),
        (Filter.by_property("_id").contains_none([UUID1, UUID2, UUID3, UUID4]), [], (1, 33, 0)),
    ],
)
def test_filters_contains(
    recwarn: pytest.WarningsRecorder,
    collection_factory: CollectionFactory,
    weaviate_filter: _FilterValue,
    results: List[int],
    require_version: Optional[tuple[int, int, int]],
) -> None:
    collection = collection_factory(
        vector_config=Configure.Vectors.self_provided(),
        properties=[
            Property(name="text", data_type=DataType.TEXT),
            Property(name="texts", data_type=DataType.TEXT_ARRAY),
            Property(name="int", data_type=DataType.INT),
            Property(name="ints", data_type=DataType.INT_ARRAY),
            Property(name="float", data_type=DataType.NUMBER),
            Property(name="floats", data_type=DataType.NUMBER_ARRAY),
            Property(name="bool", data_type=DataType.BOOL),
            Property(name="bools", data_type=DataType.BOOL_ARRAY),
            Property(name="dates", data_type=DataType.DATE_ARRAY),
            Property(name="date", data_type=DataType.DATE),
            Property(name="uuids", data_type=DataType.UUID_ARRAY),
            Property(name="uuid", data_type=DataType.UUID),
        ],
    )

    if require_version and collection._connection._weaviate_version.is_lower_than(*require_version):
        pytest.skip(
            f"{weaviate_filter} is not supported in v{'.'.join(str(_) for _ in require_version)}"
        )

    uuids = [
        collection.data.insert(
            {
                "text": "this is a test",
                "texts": "this is a test".split(" "),
                "int": 1,
                "ints": [1, 2, 4],
                "float": 0.5,
                "floats": [0.4, 0.9, 2],
                "bool": True,
                "bools": [True, False],
                "dates": [NOW, LATER, MUCH_LATER],
                "date": NOW,
                "uuids": [UUID1, UUID3, UUID2],
                "uuid": UUID1,
            },
            uuid=UUID1,
        ),
        collection.data.insert(
            {
                "text": "this is not a real test",
                "texts": "this is not a real test".split(" "),
                "int": 1,
                "ints": [5, 6, 9],
                "float": 0.3,
                "floats": [0.1, 0.7, 2],
                "bool": True,
                "bools": [False, False],
                "dates": [NOW, NOW, MUCH_LATER],
                "date": LATER,
                "uuids": [UUID2, UUID2],
                "uuid": UUID2,
            },
            uuid=UUID2,
        ),
        collection.data.insert(
            {
                "text": "real deal",
                "texts": "real deal".split(" "),
                "int": 3,
                "ints": [],
                "floats": [],
                "bool": False,
                "bools": [],
                "dates": [],
                "uuids": [],
            },
            uuid=UUID3,
        ),
        collection.data.insert(
            {
                "text": "not real deal",
                "texts": "not real deal".split(" "),
                "int": 4,
                "ints": [4],
                "float": 8,
                "floats": [0.7],
                "bool": True,
                "bools": [True],
                "dates": [MUCH_LATER],
                "date": MUCH_LATER,
                "uuids": [UUID1, UUID2],
                "uuid": UUID2,
            },
            uuid=UUID4,
        ),
    ]

    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)

    # Check for warnings to make sure booleans are handled as their correct type and are not sent as ints
    if len(recwarn) != 0:
        for rwarning in recwarn.list:
            print(rwarning.message)
    assert len(recwarn) == 0


@pytest.mark.parametrize(
    "weaviate_filter,results",
    [
        (Filter.by_ref("ref").by_property("int").greater_than(3), [1]),
        (Filter.by_ref("ref").by_property("text", length=True).less_than(6), [0]),
        (Filter.by_ref("ref").by_id().equal(UUID2), [1]),
        (
            Filter.by_ref("ref2").by_ref("ref").by_property("text", length=True).less_than(6),
            [2],
        ),  # second obj links to first one
    ],
)
def test_ref_filters(
    collection_factory: CollectionFactory, weaviate_filter: _Filters, results: List[int]
) -> None:
    to_collection = collection_factory(
        name="Target",
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[
            Property(name="int", data_type=DataType.INT),
            Property(name="text", data_type=DataType.TEXT),
        ],
        inverted_index_config=Configure.inverted_index(index_property_length=True),
    )

    uuids_to = [
        to_collection.data.insert(properties={"int": 0, "text": "first"}, uuid=UUID1),
        to_collection.data.insert(properties={"int": 15, "text": "second"}, uuid=UUID2),
    ]
    from_collection = collection_factory(
        properties=[
            Property(name="name", data_type=DataType.TEXT),
        ],
        references=[ReferenceProperty(name="ref", target_collection=to_collection.name)],
        vectorizer_config=Configure.Vectorizer.none(),
    )
    from_collection.config.add_reference(
        ReferenceProperty(name="ref2", target_collection=from_collection.name)
    )

    uuids_from = [
        from_collection.data.insert(properties={"name": "first"}, references={"ref": uuids_to[0]}),
        from_collection.data.insert(properties={"name": "second"}, references={"ref": uuids_to[1]}),
    ]
    uuids_from.extend(
        [
            from_collection.data.insert(
                properties={"name": "third"}, references={"ref2": uuids_from[0]}
            ),
            from_collection.data.insert(
                properties={"name": "fourth"}, references={"ref2": uuids_from[1]}
            ),
        ]
    )

    objects = from_collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [uuids_from[result] for result in results]
    assert all(obj.uuid in uuids for obj in objects)


def test_ref_filters_multi_target(collection_factory: CollectionFactory) -> None:
    to_collection = collection_factory(
        name="target",
        vectorizer_config=Configure.Vectorizer.none(),
        properties=[Property(name="int", data_type=DataType.INT)],
    )

    uuid_to = to_collection.data.insert(properties={"int": 0})
    uuid_to2 = to_collection.data.insert(properties={"int": 5})
    from_collection = collection_factory(
        name="source",
        properties=[
            Property(name="name", data_type=DataType.TEXT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
    )
    from_collection.config.add_reference(
        ReferenceProperty.MultiTarget(
            name="ref", target_collections=[to_collection.name, from_collection.name]
        )
    )

    uuid_from_to_target1 = from_collection.data.insert(
        {
            "name": "first",
        },
        references={
            "ref": ReferenceToMulti(uuids=uuid_to, target_collection=to_collection.name),
        },
    )
    uuid_from_to_target2 = from_collection.data.insert(
        {
            "name": "second",
        },
        references={
            "ref": ReferenceToMulti(uuids=uuid_to2, target_collection=to_collection.name),
        },
    )
    from_collection.data.insert(
        {
            "name": "third",
        },
        references={
            "ref": ReferenceToMulti(
                uuids=uuid_from_to_target1, target_collection=from_collection.name
            ),
        },
    )
    from_collection.data.insert(
        {
            "name": "fourth",
        },
        references={
            "ref": ReferenceToMulti(
                uuids=uuid_from_to_target2, target_collection=from_collection.name
            ),
        },
    )

    objects = from_collection.query.fetch_objects(
        filters=Filter.by_ref_multi_target("ref", to_collection.name)
        .by_property("int")
        .greater_than(3)
    ).objects
    assert len(objects) == 1
    assert objects[0].properties["name"] == "second"

    objects = from_collection.query.fetch_objects(
        filters=Filter.by_ref_multi_target("ref", target_collection=from_collection.name)
        .by_property("name")
        .equal("first")
    ).objects
    assert len(objects) == 1
    assert objects[0].properties["name"] == "third"


@pytest.mark.parametrize(
    "weav_filter",
    [
        Filter.by_id().equal(UUID1),
        Filter.by_id().contains_any([UUID1]),
        Filter.by_id().not_equal(UUID2),
        Filter.by_property("_id").equal(UUID1),
    ],
)
def test_filter_id(collection_factory: CollectionFactory, weav_filter: _FilterValue) -> None:
    collection = collection_factory(
        properties=[
            Property(name="Name", data_type=DataType.TEXT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
    )

    collection.data.insert_many(
        [
            DataObject(properties={"name": "first"}, uuid=UUID1),
            DataObject(properties={"name": "second"}, uuid=UUID2),
        ]
    )

    objects = collection.query.fetch_objects(filters=weav_filter).objects

    assert len(objects) == 1
    assert objects[0].uuid == UUID1


@pytest.mark.parametrize(
    "ids, expected_len, expected",
    [
        ([], 0, set()),
        ((), 0, set()),
        (
            [
                UUID3,
            ],
            1,
            {
                UUID3,
            },
        ),
        ([UUID1, UUID2], 2, {UUID1, UUID2}),
        ((UUID1, UUID3), 2, {UUID1, UUID3}),
        ((UUID1, UUID3, UUID3), 2, {UUID1, UUID3}),
    ],
)
def test_filter_ids(
    collection_factory: CollectionFactory,
    ids: Iterable[UUID],
    expected_len: int,
    expected: set,
) -> None:
    collection = collection_factory(
        properties=[
            Property(name="Name", data_type=DataType.TEXT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
    )

    collection.data.insert_many(
        [
            DataObject(properties={"name": "first"}, uuid=UUID1),
            DataObject(properties={"name": "second"}, uuid=UUID2),
            DataObject(properties={"name": "third"}, uuid=UUID3),
        ]
    )

    objects = collection.query.fetch_objects_by_ids(ids).objects

    assert len(objects) == expected_len
    assert {o.uuid for o in objects} == expected


@pytest.mark.parametrize("path", ["_creationTimeUnix", "_lastUpdateTimeUnix"])
def test_filter_timestamp_direct_path(collection_factory: CollectionFactory, path: str) -> None:
    collection = collection_factory(
        properties=[
            Property(name="Name", data_type=DataType.TEXT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
        inverted_index_config=Configure.inverted_index(index_timestamps=True),
    )

    obj1_uuid = collection.data.insert(properties={"name": "first"})
    time.sleep(0.01)
    obj2_uuid = collection.data.insert(properties={"name": "second"})

    obj2 = collection.query.fetch_object_by_id(uuid=obj2_uuid)
    assert obj2 is not None
    assert obj2.metadata is not None
    assert obj2.metadata.creation_time is not None

    filters = Filter.by_property(path).less_than(obj2.metadata.creation_time)
    objects = collection.query.fetch_objects(
        filters=filters, return_metadata=MetadataQuery(creation_time=True)
    ).objects

    assert len(objects) == 1
    assert objects[0].uuid == obj1_uuid


def test_time_update_and_creation_time(collection_factory: CollectionFactory) -> None:
    collection = collection_factory(
        properties=[
            Property(name="Name", data_type=DataType.TEXT),
        ],
        vectorizer_config=Configure.Vectorizer.none(),
        inverted_index_config=Configure.inverted_index(index_timestamps=True),
    )

    obj1_uuid = collection.data.insert(properties={"name": "first"})
    time.sleep(0.01)
    obj2_uuid = collection.data.insert(properties={"name": "second"})

    collection.data.update(uuid=obj1_uuid, properties={"name": "first updated"})

    obj1 = collection.query.fetch_object_by_id(uuid=obj1_uuid)
    assert obj1 is not None
    assert obj1.metadata is not None
    assert obj1.metadata.creation_time is not None
    assert obj1.metadata.last_update_time is not None
    assert obj1.metadata.creation_time < obj1.metadata.last_update_time

    filter_creation = Filter.by_update_time().less_than(obj1.metadata.creation_time)
    filter_update = Filter.by_update_time().less_than(obj1.metadata.last_update_time)

    objects_creation = collection.query.fetch_objects(
        filters=filter_creation, return_metadata=MetadataQuery(creation_time=True)
    ).objects
    assert len(objects_creation) == 0

    objects_update = collection.query.fetch_objects(
        filters=filter_update, return_metadata=MetadataQuery(creation_time=True)
    ).objects
    assert len(objects_update) == 1
    assert objects_update[0].uuid == obj2_uuid


@pytest.mark.parametrize(
    "weav_filter,filter_value,results",
    [
        (Filter.by_creation_time().equal, 2, [2]),
        (Filter.by_creation_time().not_equal, 1, [0, 2]),
        (Filter.by_creation_time().greater_than, 1, [2]),
        (Filter.by_creation_time().greater_or_equal, 1, [1, 2]),
        (Filter.by_creation_time().less_than, 1, [0]),
        (Filter.by_creation_time().less_or_equal, 1, [0, 1]),
    ],
)
def test_time_filter(
    collection_factory: CollectionFactory,
    weav_filter: Callable,
    filter_value: int,
    results: List[int],
) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        inverted_index_config=Configure.inverted_index(index_timestamps=True),
    )
    collection.data.insert(properties={})
    time.sleep(0.01)
    collection.data.insert(properties={})
    time.sleep(0.01)
    collection.data.insert(properties={})

    all_objects = collection.query.fetch_objects(
        sort=Sort.by_creation_time(), return_metadata=["creation_time"]
    ).objects
    weaviate_filter = weav_filter(all_objects[filter_value].metadata.creation_time)
    objects = collection.query.fetch_objects(filters=weaviate_filter).objects
    assert len(objects) == len(results)

    uuids = [all_objects[result].uuid for result in results]
    assert all(obj.uuid in uuids for obj in objects)


def test_time_filter_contains(collection_factory: CollectionFactory) -> None:
    collection = collection_factory(
        vectorizer_config=Configure.Vectorizer.none(),
        inverted_index_config=Configure.inverted_index(index_timestamps=True),
    )
    collection.data.insert(properties={})
    time.sleep(0.01)
    uuid2 = collection.data.insert(properties={})
    time.sleep(0.01)
    uuid3 = collection.data.insert(properties={})

    obj2 = collection.query.fetch_object_by_id(uuid=uuid2)
    obj3 = collection.query.fetch_object_by_id(uuid=uuid3)

    objects = collection.query.fetch_objects(
        filters=Filter.by_creation_time().contains_any(
            [obj2.metadata.creation_time, obj3.metadata.creation_time]
        )
    ).objects
    assert len(objects) == 2
    uuids = [uuid2, uuid3]
    assert all(obj.uuid in uuids for obj in objects)


@pytest.mark.parametrize(
    "weav_filter,results",
    [
        (Filter.by_ref_count("ref").not_equal(1), [0, 2]),
        (Filter.by_ref_count("ref").less_than(count=2), [0, 1]),
        (Filter.by_ref_count("ref").less_or_equal(count=1), [0, 1]),
        (Filter.by_ref_count("ref").greater_than(0), [1, 2]),
        (Filter.by_ref_count("ref").greater_or_equal(1), [1, 2]),
    ],
)
def test_ref_count_filter(
    collection_factory: CollectionFactory,
    weav_filter: _FilterValue,
    results: List[int],
) -> None:
    collection = collection_factory()
    collection.config.add_reference(
        ReferenceProperty(name="ref", target_collection=collection.name)
    )

    uuids = [
        collection.data.insert({}, uuid=UUID1),
        collection.data.insert({}, uuid=UUID2, references={"ref": UUID1}),
        collection.data.insert({}, references={"ref": [UUID1, UUID2]}, uuid=UUID3),
    ]

    objects = collection.query.fetch_objects(filters=weav_filter).objects
    assert len(objects) == len(results)

    uuids_res = [uuids[result] for result in results]
    assert all(obj.uuid in uuids_res for obj in objects)


def test_multi_target_ref_count_filter(collection_factory: CollectionFactory) -> None:
    collection = collection_factory()
    collection.config.add_reference(
        ReferenceProperty.MultiTarget(name="ref", target_collections=[collection.name])
    )

    uuid1 = collection.data.insert({})
    uuid2 = collection.data.insert({})

    uuid3 = collection.data.insert({}, references={"ref": [uuid1, uuid2]})

    objects = collection.query.fetch_objects(
        filters=Filter.by_ref_count("ref").greater_than(0),
        return_references=QueryReference(link_on="ref"),
    ).objects
    assert len(objects) == 1
    assert objects[0].uuid == uuid3


def test_nested_ref_count_filter(collection_factory: CollectionFactory) -> None:
    one = collection_factory("one")
    two = collection_factory(
        "two", references=[ReferenceProperty(name="ref2", target_collection=one.name)]
    )
    one.config.add_reference(ReferenceProperty(name="ref1", target_collection=one.name))

    uuid11 = one.data.insert({})
    uuid12 = one.data.insert({}, references={"ref1": uuid11})
    uuid13 = one.data.insert({}, references={"ref1": [uuid11, uuid12]})
    two.data.insert({})
    uuid21 = two.data.insert({}, references={"ref2": uuid12})
    two.data.insert({}, references={"ref2": uuid13})

    objects = two.query.fetch_objects(
        filters=Filter.by_ref("ref2").by_ref_count("ref1").equal(1),
        return_references=QueryReference(link_on="ref2"),
    ).objects
    assert len(objects) == 1
    assert objects[0].uuid == uuid21


def test_filter_by_ref_and_multi_ref(collection_factory: CollectionFactory) -> None:
    one = collection_factory("one")
    two = collection_factory(
        name="two", references=[ReferenceProperty(name="ref2", target_collection=one.name)]
    )
    one.config.add_reference(
        ReferenceProperty.MultiTarget(name="ref1", target_collections=[one.name, two.name])
    )
    uuid11 = one.data.insert({})
    uuid12 = one.data.insert(
        {}, references={"ref1": ReferenceToMulti(uuids=uuid11, target_collection=one.name)}
    )
    uuid13 = one.data.insert(
        {},
        references={"ref1": ReferenceToMulti(uuids=[uuid11, uuid12], target_collection=one.name)},
    )

    two.data.insert({}, references={"ref2": uuid11})
    two.data.insert({}, references={"ref2": uuid12})
    uuid23 = two.data.insert({}, references={"ref2": uuid13})

    objects = two.query.fetch_objects(
        filters=Filter.by_ref("ref2")
        .by_ref_multi_target("ref1", target_collection=one.name)
        .by_ref_count("ref1")
        .equal(1),
    ).objects

    assert objects[0].uuid == uuid23
