"""Adapter to wrap upstash-redis client to match redis-py async interface.

This module provides compatibility between upstash-redis and redis-py clients,
allowing redis-fs to use either interchangeably.
"""

from __future__ import annotations

import hashlib
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

if TYPE_CHECKING:
    from collections.abc import Sequence


@runtime_checkable
class UpstashRedisProtocol(Protocol):
    """Protocol matching upstash-redis async client interface."""

    async def eval(
        self,
        script: str,
        keys: list[str] | None = None,
        args: list[Any] | None = None,
    ) -> Any: ...

    async def evalsha(
        self,
        sha: str,
        keys: list[str] | None = None,
        args: list[Any] | None = None,
    ) -> Any: ...

    async def get(self, key: str) -> Any: ...

    async def set(self, key: str, value: Any, **kwargs: Any) -> Any: ...

    async def hget(self, key: str, field: str) -> Any: ...

    async def hset(self, key: str, field: str, value: Any) -> Any: ...

    async def delete(self, *keys: str) -> Any: ...

    async def incr(self, key: str) -> Any: ...

    async def keys(self, pattern: str) -> Any: ...

    async def scan(
        self, cursor: int, match: str | None = None, count: int | None = None
    ) -> Any: ...


class UpstashScript:
    """Script wrapper that mimics redis-py's Script interface for upstash-redis.

    Provides automatic SHA caching and fallback from EVALSHA to EVAL on NOSCRIPT.
    """

    def __init__(self, client: "UpstashAdapter", script: str):
        self._client = client
        self._script = script
        self._sha = hashlib.sha1(script.encode()).hexdigest()

    async def __call__(
        self,
        keys: Sequence[str] | None = None,
        args: Sequence[Any] | None = None,
        client: Any = None,
    ) -> Any:
        """Execute the script.

        Args:
            keys: List of key names the script accesses.
            args: List of argument values for the script.
            client: Optional client override (for compatibility, uses stored client).

        Returns:
            The result of script execution.
        """
        actual_client = client if client is not None else self._client
        keys_list = list(keys) if keys else []
        args_list = list(args) if args else []

        try:
            # Try EVALSHA first (more efficient if cached)
            return await actual_client._upstash.evalsha(
                self._sha, keys=keys_list, args=args_list
            )
        except Exception as e:
            # On NOSCRIPT error, fall back to EVAL
            error_str = str(e).upper()
            if "NOSCRIPT" in error_str:
                return await actual_client._upstash.eval(
                    self._script, keys=keys_list, args=args_list
                )
            raise


class UpstashAdapter:
    """Adapter wrapping upstash-redis to match redis-py async interface.

    This adapter allows upstash-redis clients to be used with code expecting
    the redis-py interface. The main differences handled:

    1. Script registration: redis-py uses register_script() returning callable Script
    2. eval signature: redis-py uses (script, numkeys, *keys_and_args),
       upstash uses (script, keys=[], args=[])
    """

    def __init__(self, upstash_client: UpstashRedisProtocol):
        """Initialize the adapter with an upstash-redis client.

        Args:
            upstash_client: An upstash-redis async client instance.
        """
        self._upstash = upstash_client

    def register_script(self, script: str) -> UpstashScript:
        """Register a Lua script and return a callable Script object.

        This mimics redis-py's register_script behavior.

        Args:
            script: The Lua script source code.

        Returns:
            An UpstashScript instance that can be called to execute the script.
        """
        return UpstashScript(self, script)

    async def eval(
        self, script: str, numkeys: int, *keys_and_args: Any
    ) -> Any:
        """Execute a Lua script using redis-py style arguments.

        Args:
            script: The Lua script source code.
            numkeys: Number of keys in keys_and_args.
            *keys_and_args: Keys followed by args.

        Returns:
            The result of script execution.
        """
        keys = list(keys_and_args[:numkeys])
        args = list(keys_and_args[numkeys:])
        return await self._upstash.eval(script, keys=keys, args=args)

    # Pass-through methods that have compatible signatures

    async def get(self, name: str) -> Any:
        """Get the value of a key."""
        return await self._upstash.get(name)

    async def set(self, name: str, value: Any, **kwargs: Any) -> Any:
        """Set the value of a key."""
        return await self._upstash.set(name, value, **kwargs)

    async def hget(self, name: str, key: str) -> Any:
        """Get the value of a hash field."""
        return await self._upstash.hget(name, key)

    async def hset(
        self, name: str, key: str | None = None, value: Any = None, mapping: dict | None = None
    ) -> Any:
        """Set hash field(s)."""
        if mapping:
            # upstash-redis uses hset(name, mapping)
            result = None
            for k, v in mapping.items():
                result = await self._upstash.hset(name, k, v)
            return result
        return await self._upstash.hset(name, key, value)

    async def delete(self, *names: str) -> Any:
        """Delete one or more keys."""
        return await self._upstash.delete(*names)

    async def incr(self, name: str) -> Any:
        """Increment the value of a key."""
        return await self._upstash.incr(name)

    async def keys(self, pattern: str = "*") -> Any:
        """Find all keys matching pattern."""
        return await self._upstash.keys(pattern)

    async def scan(
        self,
        cursor: int = 0,
        match: str | None = None,
        count: int | None = None,
        **kwargs: Any,
    ) -> tuple[int, list[str]]:
        """Scan keys matching pattern."""
        return await self._upstash.scan(cursor, match=match, count=count)

    async def close(self) -> None:
        """Close the connection (no-op for HTTP-based upstash)."""
        # upstash-redis is HTTP-based, no connection to close
        pass

    async def __aenter__(self) -> "UpstashAdapter":
        return self

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


def is_upstash_client(client: Any) -> bool:
    """Check if the given client is an upstash-redis client.

    Args:
        client: A Redis client instance.

    Returns:
        True if the client appears to be from upstash-redis.
    """
    # Check for upstash-redis specific attributes/module
    client_module = type(client).__module__
    return "upstash" in client_module.lower()


def wrap_if_upstash(client: Any) -> Any:
    """Wrap an upstash-redis client in the adapter if necessary.

    Args:
        client: A Redis client (either redis-py or upstash-redis).

    Returns:
        The original client if redis-py, or wrapped in UpstashAdapter if upstash.
    """
    if is_upstash_client(client):
        return UpstashAdapter(client)
    return client
