import pytest
import redis
import valkey

from test.testtools import resp_conversion, get_protocol_version

topk_tests = pytest.importorskip("probables")

pytestmark = []
pytestmark.extend(
    [
        pytest.mark.unsupported_server_types("dragonfly", "valkey"),
        pytest.mark.supported_server_versions(min_redis_ver="7"),
    ]
)


@pytest.mark.supported_server_versions(min_redis_ver="7")
def test_topk_type(r: redis.Redis):
    assert r.topk().reserve("topk", 3, 10, 3, 1)
    assert r.type("topk") == b"TopK-TYPE"


def test_topk_incrby(r: redis.Redis):
    assert r.topk().reserve("topk", 3, 10, 3, 1)
    assert [None, None, None] == r.topk().incrby("topk", ["bar", "baz", "42"], [3, 6, 4])
    result = r.topk().incrby("topk", ["42", "xyzzy"], [8, 4])
    # "42" is already in top-3; incrementing it never displaces another item
    assert result[0] is None
    # "xyzzy"(4) competes with "bar"(3); hash collisions may inflate bar's estimate,
    # so displacement is not guaranteed — accept either outcome
    assert result[1] in (None, resp_conversion(r, b"bar", "bar"))
    with pytest.deprecated_call():
        counts = r.topk().count("topk", "bar", "baz", "42", "xyzzy", 4)
    assert counts[0] >= 3  # bar
    assert counts[1] >= 6  # baz
    assert counts[2] >= 12  # 42
    assert counts[3] >= 4  # xyzzy
    assert counts[4] >= 0  # item "4" was never added


def test_topk(r: redis.Redis):
    # test list with empty buckets
    assert r.topk().reserve("topk", 3, 50, 4, 0.9)
    ret = r.topk().add("topk", "A", "B", "C", "D", "D", "E", "A", "A", "B", "C", "G", "D", "B", "D", "A", "E", "E", 1)
    assert len(ret) == 18

    with pytest.deprecated_call():
        assert r.topk().count("topk", "A", "B", "C", "D", "E", "F", "G") == [4, 3, 2, 4, 3, 0, 1]
    ret = r.topk().query("topk", "A", "B", "C", "D", "E", "F", "G")
    assert (ret == [1, 0, 0, 1, 1, 0, 0]) or (ret == [1, 1, 0, 1, 0, 0, 0])
    # test full list
    assert r.topk().reserve("topklist", 3, 50, 3, 0.9)
    assert r.topk().add("topklist", "A", "B", "D", "E", "A", "A", "B", "C", "G", "D", "B", "A", "B", "E", "E")
    with pytest.deprecated_call():
        assert r.topk().count("topklist", "A", "B", "C", "D", "E", "F", "G") == [4, 4, 1, 2, 3, 0, 1]
    assert r.topk().list("topklist") == resp_conversion(r, [b"A", b"B", b"E"], ["A", "B", "E"])
    assert r.topk().list("topklist", withcount=True) == resp_conversion(
        r, [b"A", 4, b"B", 4, b"E", 3], ["A", 4, "B", 4, "E", 3]
    )
    info = r.topk().info("topklist")
    if get_protocol_version(r) == 2:
        assert 3 == info["k"]
        assert 50 == info["width"]
        assert 3 == info["depth"]
        assert 0.9 == round(float(info["decay"]), 1)
    else:
        assert 3 == info[b"k"]
        assert 50 == info[b"width"]
        assert 3 == info[b"depth"]
        assert 0.9 == round(float(info[b"decay"]), 1)


def test_topk_commands_on_nonexistent_key(r: redis.Redis):
    """All TopK commands raise an error when the key does not exist."""
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.topk().add("nokey", "item")
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.execute_command("TOPK.COUNT", "nokey", "item")
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.topk().query("nokey", "item")
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.topk().incrby("nokey", ["item"], [1])
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.topk().info("nokey")
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
    with pytest.raises(Exception, match="key does not exist") as ctx:
        r.topk().list("nokey")

    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))


def test_topk_reserve_already_exists(r: redis.Redis):
    """TOPK.RESERVE raises an error when the key already exists."""
    r.topk().reserve("topk", 3, 10, 3, 0.9)
    with pytest.raises(Exception, match="already exists") as ctx:
        r.topk().reserve("topk", 5, 10, 3, 0.9)

    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))


def test_topk_incrby_odd_args(r: redis.Redis):
    """TOPK.INCRBY raises an error when items and increments are mismatched."""
    r.topk().reserve("topk", 3, 10, 3, 0.9)
    with pytest.raises(Exception) as ctx:
        r.execute_command("TOPK.INCRBY", "topk", "item1", "3", "item2")
    assert isinstance(ctx.value, (redis.ResponseError, valkey.ResponseError))
