from dataclasses import dataclass
from typing import Any

from .clients.base import CacheClient
from .serializer import Serializer, SmartSerializer


# ═══════════════════════════════════════════════════════════════════════════
# Operation sub-builders
# ═══════════════════════════════════════════════════════════════════════════


@dataclass
class KeyOperation:
    """Fluent builder for a single cache key."""

    _client: CacheClient
    _key: str

    async def get(self) -> Any:
        """Return the value stored at this key, or ``None``."""
        return await self._client.get(self._key)

    async def set(self, value: Any, ttl: int | None = None) -> None:
        """Store *value* at this key with an optional *ttl* in seconds."""
        await self._client.set(self._key, value, ttl=ttl)

    async def setnx(self, value: Any, ttl: int | None = None) -> bool:
        """Set this key to *value* only if it does **not** already exist.

        Returns ``True`` if the key was set, ``False`` if it already existed.
        """
        return await self._client.setnx(self._key, value, ttl=ttl)

    async def delete(self) -> bool:
        """Delete the key.  Returns ``True`` if it existed."""
        return (await self._client.delete(self._key)) > 0

    async def exists(self) -> bool:
        """Return ``True`` if the key exists."""
        return (await self._client.exists(self._key)) > 0

    async def expire(self, ttl: int) -> bool:
        """Set a time-to-live (seconds) on the key.  Returns ``True`` on success."""
        return await self._client.expire(self._key, ttl)

    async def ttl(self) -> int:
        """Return the remaining TTL in seconds, or -1 if no expiry."""
        return await self._client.ttl(self._key)

    async def incr(self, amount: int = 1) -> int:
        """Increment the integer value by *amount*."""
        return await self._client.incr(self._key, amount)

    async def decr(self, amount: int = 1) -> int:
        """Decrement the integer value by *amount*."""
        return await self._client.decr(self._key, amount)


@dataclass
class HashFieldOperation:
    """Fluent builder for a single field inside a hash."""

    _client: CacheClient
    _key: str
    _field: str

    async def get(self) -> Any:
        """Return the value of this field, or ``None``."""
        return await self._client.hget(self._key, self._field)

    async def set(self, value: Any) -> int:
        """Set the field to *value*."""
        return await self._client.hset(self._key, {self._field: value})

    async def getall(self) -> dict[str, Any]:
        """Return all fields and values of the hash."""
        return await self._client.hgetall(self._key)

    async def delete(self) -> int:
        """Delete this field from the hash."""
        return await self._client.hdel(self._key, self._field)

    async def exists(self) -> bool:
        """Return ``True`` if the field exists in the hash."""
        return await self._client.hexists(self._key, self._field)


@dataclass
class HashOperation:
    """Fluent builder for a hash (map) stored at a key."""

    _client: CacheClient
    _key: str

    def field(self, name: str) -> HashFieldOperation:
        """Target a specific *field* inside the hash."""
        return HashFieldOperation(self._client, self._key, name)

    async def set(self, mapping: dict[str, Any]) -> int:
        """Set multiple fields at once."""
        return await self._client.hset(self._key, mapping)

    async def getall(self) -> dict[str, Any]:
        """Return all fields and values."""
        return await self._client.hgetall(self._key)

    async def delete(self, *fields: str) -> int:
        """Delete one or more fields."""
        return await self._client.hdel(self._key, *fields)

    async def keys(self) -> list[str]:
        """Return all field names in the hash."""
        return await self._client.hkeys(self._key)

    async def len(self) -> int:
        """Return the number of fields in the hash."""
        return await self._client.hlen(self._key)


@dataclass
class ListOperation:
    """Fluent builder for a list (array) stored at a key."""

    _client: CacheClient
    _key: str

    async def push(self, *values: Any) -> int:
        """Append values to the right (tail) of the list."""
        return await self._client.rpush(self._key, *values)

    async def push_left(self, *values: Any) -> int:
        """Prepend values to the left (head) of the list."""
        return await self._client.lpush(self._key, *values)

    async def pop(self) -> Any:
        """Remove and return the rightmost (last) element."""
        return await self._client.rpop(self._key)

    async def pop_left(self) -> Any:
        """Remove and return the leftmost (first) element."""
        return await self._client.lpop(self._key)

    async def range(self, start: int, stop: int) -> list[Any]:
        """Return a slice of the list (``stop`` is inclusive, Redis-style)."""
        return await self._client.lrange(self._key, start, stop)

    async def len(self) -> int:
        """Return the number of elements in the list."""
        return await self._client.llen(self._key)


@dataclass
class SetOperation:
    """Fluent builder for a set stored at a key.

    Note: named ``SetOperation`` / ``set_()`` (trailing underscore) to avoid
    shadowing Python's built-in ``set``.
    """

    _client: CacheClient
    _key: str

    async def add(self, *members: Any) -> int:
        """Add members to the set.  Returns the number of newly added members."""
        return await self._client.sadd(self._key, *members)

    async def remove(self, *members: Any) -> int:
        """Remove members from the set.  Returns the number removed."""
        return await self._client.srem(self._key, *members)

    async def members(self) -> list[Any]:
        """Return all members of the set."""
        return await self._client.smembers(self._key)

    async def contains(self, member: Any) -> bool:
        """Return ``True`` if *member* is in the set."""
        return await self._client.sismember(self._key, member)

    async def size(self) -> int:
        """Return the number of members in the set (cardinality)."""
        return await self._client.scard(self._key)


@dataclass
class SortedSetOperation:
    """Fluent builder for a sorted set (zset) stored at a key."""

    _client: CacheClient
    _key: str

    async def add(self, mapping: dict[Any, float]) -> int:
        """Add members with scores.  Returns the number of newly added members."""
        return await self._client.zadd(self._key, mapping)

    async def remove(self, *members: Any) -> int:
        """Remove members from the sorted set.  Returns the number removed."""
        return await self._client.zrem(self._key, *members)

    async def range(
        self, start: int, stop: int, withscores: bool = False
    ) -> list[Any]:
        """Return members in score order between *start* and *stop* (inclusive)."""
        return await self._client.zrange(self._key, start, stop, withscores=withscores)

    async def size(self) -> int:
        """Return the number of members in the sorted set (cardinality)."""
        return await self._client.zcard(self._key)


@dataclass
class PatternOperation:
    """Fluent builder for glob-pattern batch operations."""

    _client: CacheClient
    _pattern: str

    async def delete(self) -> int:
        """Delete all keys matching the glob pattern.  Returns the count."""
        keys = await self._client.keys(self._pattern)
        if not keys:
            return 0
        return await self._client.delete(*keys)

    async def keys(self) -> list[str]:
        """Return all keys matching the glob pattern."""
        return await self._client.keys(self._pattern)

    async def count(self) -> int:
        """Return the number of keys matching the glob pattern."""
        keys = await self._client.keys(self._pattern)
        return len(keys)


# ═══════════════════════════════════════════════════════════════════════════
# CacheBuilder
# ═══════════════════════════════════════════════════════════════════════════


class CacheBuilder:
    """Fluent, Pythonic wrapper around a :class:`~cache_core.clients.base.CacheClient`.

    Provides scoped operation builders for every cache data type: keys, hashes,
    lists, sets, sorted sets, and pattern-based batch operations.

    Parameters
    ----------
    client:
        A concrete :class:`CacheClient` (e.g. :class:`RedisCacheClient` or
        :class:`MemoryCacheClient`).
    serializer:
        Optional serializer applied to the client.  When provided it is set on
        the underlying client for transparent per-member serialization.
        Defaults to :class:`SmartSerializer` when ``None``.
    prefix:
        Internal namespace prefix (used by :meth:`ns`).
    """

    def __init__(
        self,
        client: CacheClient,
        serializer: Serializer | None = None,
        prefix: str = "",
    ) -> None:
        self._client = client
        self._prefix = prefix

        # If the client already has a serializer set, prefer it.
        # Otherwise, apply the one passed here (default SmartSerializer).
        if hasattr(client, "_serializer") and client._serializer is not None:
            self._serializer = client._serializer
        elif serializer is not None:
            self._serializer = serializer
            if hasattr(client, "_serializer"):
                client._serializer = serializer
        else:
            self._serializer = SmartSerializer()
            if hasattr(client, "_serializer") and client._serializer is None:
                client._serializer = self._serializer

    # -- Scoping -------------------------------------------------------------

    def ns(self, namespace: str) -> "CacheBuilder":
        """Return a new builder scoped to *namespace*.

        All keys built from the returned builder are prefixed with
        ``namespace:``.

        Example::

            users = cache.ns("users")
            await users.key("123").set({"name": "Jane"})
            # → key is "users:123"
        """
        new_prefix = f"{self._prefix}{namespace}:"
        return CacheBuilder(self._client, serializer=self._serializer, prefix=new_prefix)

    # -- Operation builders --------------------------------------------------

    def key(self, name: str) -> KeyOperation:
        """Target a specific key."""
        return KeyOperation(self._client, f"{self._prefix}{name}")

    def hash(self, name: str) -> HashOperation:
        """Target a hash (map)."""
        return HashOperation(self._client, f"{self._prefix}{name}")

    def list(self, name: str) -> ListOperation:
        """Target a list (array)."""
        return ListOperation(self._client, f"{self._prefix}{name}")

    def set_(self, name: str) -> SetOperation:
        """Target a set."""
        return SetOperation(self._client, f"{self._prefix}{name}")

    def sorted_set(self, name: str) -> SortedSetOperation:
        """Target a sorted set (zset)."""
        return SortedSetOperation(self._client, f"{self._prefix}{name}")

    def pattern(self, glob: str) -> PatternOperation:
        """Target keys matching a glob pattern."""
        return PatternOperation(self._client, f"{self._prefix}{glob}")

    # -- Direct delegation (shortcuts) ---------------------------------------

    async def connect(self) -> None:
        """Open connections on the underlying client."""
        await self._client.connect()

    async def disconnect(self) -> None:
        """Close connections on the underlying client."""
        await self._client.disconnect()

    async def ping(self) -> bool:
        """Health check."""
        return await self._client.ping()

    async def flush(self) -> None:
        """Remove all keys from the cache."""
        await self._client.flush()

    async def __aenter__(self) -> "CacheBuilder":
        await self.connect()
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.disconnect()
