"""Tests for CacheBuilder and operation sub-builders."""
import pytest
from cache_core.builder import (
    CacheBuilder,
    KeyOperation,
    HashOperation,
    ListOperation,
    SetOperation,
    SortedSetOperation,
    PatternOperation,
)
from cache_core.clients.memory import MemoryCacheClient
from cache_core.serializer import SmartSerializer


class TestCacheBuilder:
    @pytest.fixture
    async def cache(self):
        client = MemoryCacheClient(serializer=SmartSerializer())
        builder = CacheBuilder(client)
        yield builder
        await builder.flush()

    # -- Key operations ----------------------------------------------------

    async def test_key_get_set(self, cache: CacheBuilder):
        await cache.key("foo").set("bar")
        assert await cache.key("foo").get() == "bar"

    async def test_key_set_with_ttl(self, cache: CacheBuilder):
        await cache.key("ttl_key").set("val", ttl=3600)
        assert await cache.key("ttl_key").get() == "val"
        assert await cache.key("ttl_key").ttl() >= 0

    async def test_key_delete(self, cache: CacheBuilder):
        await cache.key("del").set("x")
        assert await cache.key("del").delete() is True
        assert await cache.key("del").get() is None

    async def test_key_delete_missing(self, cache: CacheBuilder):
        assert await cache.key("nope").delete() is False

    async def test_key_exists(self, cache: CacheBuilder):
        await cache.key("ex").set("y")
        assert await cache.key("ex").exists() is True
        assert await cache.key("noex").exists() is False

    async def test_key_incr_decr(self, cache: CacheBuilder):
        assert await cache.key("ctr").incr() == 1
        assert await cache.key("ctr").incr(4) == 5
        assert await cache.key("ctr").decr(2) == 3

    async def test_key_complex_value(self, cache: CacheBuilder):
        data = {"items": [{"id": 1}, {"id": 2}]}
        await cache.key("data").set(data)
        assert await cache.key("data").get() == data

    # -- Namespace scoping -----------------------------------------------

    async def test_ns_simple(self, cache: CacheBuilder):
        users = cache.ns("users")
        await users.key("123").set({"name": "Alice"})
        # The key should be "users:123"
        result = await cache.key("users:123").get()
        assert result == {"name": "Alice"}

    async def test_ns_nested(self, cache: CacheBuilder):
        a = cache.ns("a").ns("b").ns("c")
        await a.key("x").set("val")
        assert await cache.key("a:b:c:x").get() == "val"

    # -- List (array) operations -----------------------------------------

    async def test_list_push_pop(self, cache: CacheBuilder):
        await cache.list("q").push({"id": 1}, {"id": 2})
        assert await cache.list("q").pop() == {"id": 2}
        assert await cache.list("q").pop() == {"id": 1}
        assert await cache.list("q").pop() is None

    async def test_list_push_left_pop_left(self, cache: CacheBuilder):
        await cache.list("q").push_left("a", "b", "c")
        assert await cache.list("q").pop_left() == "c"

    async def test_list_range(self, cache: CacheBuilder):
        await cache.list("lst").push("a", "b", "c", "d")
        assert await cache.list("lst").range(0, 1) == ["a", "b"]
        assert await cache.list("lst").range(0, -1) == ["a", "b", "c", "d"]

    async def test_list_len(self, cache: CacheBuilder):
        await cache.list("lst").push("x", "y")
        assert await cache.list("lst").len() == 2
        await cache.list("lst").pop()
        assert await cache.list("lst").len() == 1

    # -- Set operations ---------------------------------------------------

    async def test_set_add_members(self, cache: CacheBuilder):
        added = await cache.set_("s").add({"n": 1}, {"n": 2}, {"n": 1})
        assert added == 2  # third is duplicate
        members = await cache.set_("s").members()
        assert len(members) == 2

    async def test_set_contains(self, cache: CacheBuilder):
        await cache.set_("s").add("hello")
        assert await cache.set_("s").contains("hello") is True
        assert await cache.set_("s").contains("world") is False

    async def test_set_remove_size(self, cache: CacheBuilder):
        await cache.set_("s").add("a", "b", "c")
        assert await cache.set_("s").size() == 3
        assert await cache.set_("s").remove("a") == 1
        assert await cache.set_("s").size() == 2

    # -- Hash operations --------------------------------------------------

    async def test_hash_field_set_get(self, cache: CacheBuilder):
        await cache.hash("h").field("name").set("Alice")
        assert await cache.hash("h").field("name").get() == "Alice"

    async def test_hash_bulk_set_getall(self, cache: CacheBuilder):
        # Use non-numeric strings to avoid SmartSerializer ambiguity
        await cache.hash("h").set({"a": "alpha", "b": "beta"})
        all_data = await cache.hash("h").getall()
        assert all_data == {"a": "alpha", "b": "beta"}

    async def test_hash_field_delete_exists(self, cache: CacheBuilder):
        await cache.hash("h").field("email").set("a@b.com")
        assert await cache.hash("h").field("email").exists() is True
        await cache.hash("h").field("email").delete()
        assert await cache.hash("h").field("email").exists() is False

    async def test_hash_keys_len(self, cache: CacheBuilder):
        await cache.hash("h").set({"x": "1", "y": "2", "z": "3"})
        assert await cache.hash("h").len() == 3
        assert set(await cache.hash("h").keys()) == {"x", "y", "z"}

    # -- Sorted-set operations --------------------------------------------

    async def test_sorted_set_add_range(self, cache: CacheBuilder):
        added = await cache.sorted_set("zs").add({"a": 10.0, "b": 5.0, "c": 7.0})
        assert added == 3
        members = await cache.sorted_set("zs").range(0, -1)
        assert members == ["b", "c", "a"]  # by score ascending

    async def test_sorted_set_with_scores(self, cache: CacheBuilder):
        await cache.sorted_set("zs").add({"x": 1.0, "y": 2.0})
        result = await cache.sorted_set("zs").range(0, -1, withscores=True)
        assert result == [("x", 1.0), ("y", 2.0)]

    async def test_sorted_set_remove_size(self, cache: CacheBuilder):
        await cache.sorted_set("zs").add({"a": 1, "b": 2, "c": 3})
        await cache.sorted_set("zs").remove("a")
        assert await cache.sorted_set("zs").size() == 2

    # -- Pattern operations ------------------------------------------------

    async def test_pattern_keys_count(self, cache: CacheBuilder):
        await cache.key("user:1").set("x")
        await cache.key("user:2").set("y")
        await cache.key("post:1").set("z")
        keys = await cache.pattern("user:*").keys()
        assert len(keys) == 2
        assert await cache.pattern("user:*").count() == 2

    async def test_pattern_delete(self, cache: CacheBuilder):
        await cache.key("session:a").set("x")
        await cache.key("session:b").set("y")
        await cache.key("keep:me").set("z")
        deleted = await cache.pattern("session:*").delete()
        assert deleted == 2
        assert await cache.key("keep:me").exists() is True
        assert await cache.key("session:a").exists() is False
        assert await cache.key("session:b").exists() is False

    # -- Async context manager --------------------------------------------

    async def test_async_context_manager(self):
        client = MemoryCacheClient()
        async with CacheBuilder(client) as cache:
            await cache.key("ctx").set("test")
            assert await cache.key("ctx").get() == "test"
        # After exit, memory client is still usable (disconnect is no-op for memory)
        # but the pattern is tested.
