from common.metadata_utils import meta_filter


def test_contains():
    # returns chunk where the metadata contains the value
    metas = {"version": {"hello earth": ["doc1"], "hello mars": ["doc2"]}}
    filters = [{"key": "version", "op": "contains", "value": "earth"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_not_contains():
    # returns chunk where the metadata does not contain the value
    metas = {"version": {"hello earth": ["doc1"], "hello mars": ["doc2"]}}
    filters = [{"key": "version", "op": "not contains", "value": "earth"}]

    assert meta_filter(metas, filters) == ["doc2"]


def test_in_operator():
    # returns chunk where the metadata is in the value
    metas = {"status": {"active": ["doc1"], "pending": ["doc2"], "done": ["doc3"]}}
    filters = [{"key": "status", "op": "in", "value": "active,pending"}]

    assert set(meta_filter(metas, filters)) == {"doc1", "doc2"}


def test_not_in_operator():
    # returns chunk where the metadata is not in the value
    metas = {"status": {"active": ["doc1"], "pending": ["doc2"], "done": ["doc3"]}}
    filters = [{"key": "status", "op": "not in", "value": "active,pending"}]

    assert meta_filter(metas, filters) == ["doc3"]


def test_in_operator_with_list_value_is_case_insensitive():
    metas = {"product": {"F2": ["doc1"], "F11": ["doc2"], "G1": ["doc3"]}}
    filters = [{"key": "product", "op": "in", "value": ["F2", "F11"]}]

    assert set(meta_filter(metas, filters)) == {"doc1", "doc2"}


def test_not_in_operator_with_list_value_is_case_insensitive():
    metas = {"product": {"F2": ["doc1"], "F11": ["doc2"], "G1": ["doc3"]}}
    filters = [{"key": "product", "op": "not in", "value": ["F2", "F11"]}]

    assert meta_filter(metas, filters) == ["doc3"]


def test_start_with():
    # returns chunk where the metadata starts with the value
    metas = {"name": {"prefix_value": ["doc1"], "other": ["doc2"]}}
    filters = [{"key": "name", "op": "start with", "value": "pre"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_end_with():
    # returns chunk where the metadata ends with the value
    metas = {"file": {"report.pdf": ["doc1"], "image.png": ["doc2"]}}
    filters = [{"key": "file", "op": "end with", "value": ".pdf"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_empty():
    # returns chunk where the metadata is empty
    metas = {"notes": {"": ["doc1"], "non-empty": ["doc2"]}}
    filters = [{"key": "notes", "op": "empty", "value": ""}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_not_empty():
    # returns chunk where the metadata is not empty
    metas = {"notes": {"": ["doc1"], "non-empty": ["doc2"]}}
    filters = [{"key": "notes", "op": "not empty", "value": ""}]

    assert meta_filter(metas, filters) == ["doc2"]


def test_equal():
    # returns chunk where the metadata is equal to the value
    metas = {"score": {"5": ["doc1"], "6": ["doc2"]}}
    filters = [{"key": "score", "op": "=", "value": "5"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_not_equal():
    # returns chunk where the metadata is not equal to the value
    metas = {"score": {"5": ["doc1"], "6": ["doc2"]}}
    filters = [{"key": "score", "op": "≠", "value": "5"}]

    assert meta_filter(metas, filters) == ["doc2"]


def test_greater_than():
    # returns chunk where the metadata is greater than the value
    metas = {"score": {"10": ["doc1"], "2": ["doc2"]}}
    filters = [{"key": "score", "op": ">", "value": "5"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_less_than():
    # returns chunk where the metadata is less than the value
    metas = {"score": {"10": ["doc1"], "2": ["doc2"]}}
    filters = [{"key": "score", "op": "<", "value": "5"}]

    assert meta_filter(metas, filters) == ["doc2"]


def test_greater_than_or_equal():
    # returns chunk where the metadata is greater than or equal to the value
    metas = {"score": {"5": ["doc1"], "6": ["doc2"], "4": ["doc3"]}}
    filters = [{"key": "score", "op": "≥", "value": "5"}]

    assert set(meta_filter(metas, filters)) == {"doc1", "doc2"}


def test_less_than_or_equal():
    # returns chunk where the metadata is less than or equal to the value
    metas = {"score": {"5": ["doc1"], "6": ["doc2"], "4": ["doc3"]}}
    filters = [{"key": "score", "op": "≤", "value": "5"}]

    assert set(meta_filter(metas, filters)) == {"doc1", "doc3"}


def test_and_logic_returns_empty_when_first_condition_matches_nothing():
    metas = {
        "author": {"Alice": ["doc1"]},
        "page_count": {"40": ["doc2"], "10": ["doc3"]},
    }
    filters = [
        {"key": "author", "op": "contains", "value": "Toby"},
        {"key": "page_count", "op": ">", "value": "30"},
    ]

    assert meta_filter(metas, filters, logic="and") == []


def test_and_logic_intersects_matching_conditions():
    metas = {
        "author": {"Toby Jones": ["doc1"], "Alice": ["doc2"]},
        "page_count": {"40": ["doc1"], "10": ["doc2"]},
    }
    filters = [
        {"key": "author", "op": "contains", "value": "Toby"},
        {"key": "page_count", "op": ">", "value": "30"},
    ]

    assert meta_filter(metas, filters, logic="and") == ["doc1"]


def test_or_logic_still_unions_after_empty_first_condition():
    metas = {
        "author": {"Alice": ["doc1"]},
        "page_count": {"40": ["doc2"], "10": ["doc3"]},
    }
    filters = [
        {"key": "author", "op": "contains", "value": "Toby"},
        {"key": "page_count", "op": ">", "value": "30"},
    ]

    assert set(meta_filter(metas, filters, logic="or")) == {"doc2"}


def test_equal_is_case_insensitive_for_python_keyword_literals():
    # "None" is a metadata cell that happens to be a Python literal keyword; the
    # query value "none" (lowercase) is not a valid literal on its own. Coercing
    # one side (input -> None) while leaving the other as the string "none" would
    # compare mismatched types and silently fail the case-insensitive match.
    metas = {"status": {"None": ["doc1"], "Active": ["doc2"]}}
    filters = [{"key": "status", "op": "=", "value": "none"}]

    assert meta_filter(metas, filters) == ["doc1"]


def test_not_equal_is_case_insensitive_for_python_keyword_literals():
    metas = {"status": {"None": ["doc1"], "Active": ["doc2"]}}
    filters = [{"key": "status", "op": "≠", "value": "none"}]

    assert meta_filter(metas, filters) == ["doc2"]


def test_greater_than_unaffected_by_prior_dict_entry_coercing_the_query_value():
    # The query value must be re-read fresh for every metadata entry -- if an
    # earlier entry coerces it in place (e.g. "5" -> 5), a later entry must not
    # compare against that already-coerced leftover instead of the original "5".
    metas = {"score": {"5": ["doc1"], "10": ["doc2"]}}
    filters = [{"key": "score", "op": ">", "value": "5"}]

    assert meta_filter(metas, filters) == ["doc2"]
