"""
Cache Tools

Redis cache operations for agents:
- Key-value operations
- TTL management
- Cache invalidation
- Queue operations (Redis Streams)
"""

from __future__ import annotations

import json
import logging
import os
from datetime import datetime, timezone
from typing import Any

logger = logging.getLogger(__name__)


class CacheTools:
    """Tools for Redis cache operations."""

    def __init__(self, redis_url: str | None = None):
        """
        Initialize cache tools.

        Args:
            redis_url: Redis connection URL
        """
        self.redis_url = redis_url or os.environ.get("REDIS_URL", "redis://localhost:6379")
        self._redis = None
        logger.info("CacheTools initialized")

    async def initialize(self) -> None:
        """Initialize Redis connection."""
        try:
            import redis.asyncio as redis
            self._redis = redis.from_url(self.redis_url)
            await self._redis.ping()
            logger.info("Redis connection established")
        except Exception as e:
            logger.warning(f"Failed to connect to Redis: {e}")

    async def close(self) -> None:
        """Close Redis connection."""
        if self._redis:
            await self._redis.close()

    async def get(self, key: str) -> dict[str, Any]:
        """
        Get a value from cache.

        Args:
            key: Cache key

        Returns:
            Cached value or null
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            value = await self._redis.get(key)
            ttl = await self._redis.ttl(key)

            if value is None:
                return {"key": key, "exists": False, "value": None}

            # Try to parse as JSON
            try:
                parsed = json.loads(value)
                return {
                    "key": key,
                    "exists": True,
                    "value": parsed,
                    "ttl_seconds": ttl if ttl > 0 else None,
                }
            except json.JSONDecodeError:
                return {
                    "key": key,
                    "exists": True,
                    "value": value.decode() if isinstance(value, bytes) else value,
                    "ttl_seconds": ttl if ttl > 0 else None,
                }

        except Exception as e:
            return {"error": str(e)}

    async def set(
        self,
        key: str,
        value: Any,
        ttl_seconds: int | None = None,
    ) -> dict[str, Any]:
        """
        Set a value in cache.

        Args:
            key: Cache key
            value: Value to cache (will be JSON serialized if dict/list)
            ttl_seconds: Time to live in seconds

        Returns:
            Success status
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            # Serialize value
            if isinstance(value, (dict, list)):
                serialized = json.dumps(value)
            else:
                serialized = str(value)

            if ttl_seconds:
                await self._redis.setex(key, ttl_seconds, serialized)
            else:
                await self._redis.set(key, serialized)

            return {
                "success": True,
                "key": key,
                "ttl_seconds": ttl_seconds,
            }

        except Exception as e:
            return {"success": False, "error": str(e)}

    async def delete(self, key: str) -> dict[str, Any]:
        """
        Delete a key from cache.

        Args:
            key: Cache key to delete
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            deleted = await self._redis.delete(key)
            return {
                "success": True,
                "key": key,
                "deleted": deleted > 0,
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    async def keys(self, pattern: str = "*") -> dict[str, Any]:
        """
        List keys matching a pattern.

        Args:
            pattern: Glob-style pattern (e.g., "user:*")
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            keys = await self._redis.keys(pattern)
            return {
                "pattern": pattern,
                "keys": [k.decode() if isinstance(k, bytes) else k for k in keys],
                "count": len(keys),
            }
        except Exception as e:
            return {"error": str(e)}

    async def flush_pattern(self, pattern: str) -> dict[str, Any]:
        """
        Delete all keys matching a pattern.

        Args:
            pattern: Glob-style pattern
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            keys = await self._redis.keys(pattern)
            if keys:
                deleted = await self._redis.delete(*keys)
            else:
                deleted = 0

            return {
                "pattern": pattern,
                "deleted_count": deleted,
            }
        except Exception as e:
            return {"error": str(e)}

    async def get_ttl(self, key: str) -> dict[str, Any]:
        """
        Get TTL for a key.

        Args:
            key: Cache key
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            ttl = await self._redis.ttl(key)
            return {
                "key": key,
                "ttl_seconds": ttl if ttl > 0 else None,
                "expires": ttl == -2,  # Key doesn't exist
                "persistent": ttl == -1,  # No TTL set
            }
        except Exception as e:
            return {"error": str(e)}

    async def set_ttl(self, key: str, ttl_seconds: int) -> dict[str, Any]:
        """
        Set TTL for an existing key.

        Args:
            key: Cache key
            ttl_seconds: New TTL in seconds
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            result = await self._redis.expire(key, ttl_seconds)
            return {
                "success": result,
                "key": key,
                "ttl_seconds": ttl_seconds,
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    async def info(self) -> dict[str, Any]:
        """Get Redis server info and stats."""
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            info = await self._redis.info()
            return {
                "redis_version": info.get("redis_version"),
                "connected_clients": info.get("connected_clients"),
                "used_memory_human": info.get("used_memory_human"),
                "total_connections_received": info.get("total_connections_received"),
                "keyspace": {
                    k: v for k, v in info.items() if k.startswith("db")
                },
            }
        except Exception as e:
            return {"error": str(e)}

    # =========================================================================
    # Queue Operations (Redis Streams)
    # =========================================================================

    async def stream_add(
        self,
        stream: str,
        data: dict[str, Any],
        maxlen: int | None = 1000,
    ) -> dict[str, Any]:
        """
        Add a message to a Redis stream.

        Args:
            stream: Stream name
            data: Message data
            maxlen: Maximum stream length (for trimming)
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            # Flatten data for Redis stream
            flat_data = {k: json.dumps(v) if isinstance(v, (dict, list)) else str(v) for k, v in data.items()}

            message_id = await self._redis.xadd(
                stream,
                flat_data,
                maxlen=maxlen,
            )

            return {
                "success": True,
                "stream": stream,
                "message_id": message_id.decode() if isinstance(message_id, bytes) else message_id,
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    async def stream_read(
        self,
        stream: str,
        count: int = 10,
        block: int | None = None,
    ) -> dict[str, Any]:
        """
        Read messages from a Redis stream.

        Args:
            stream: Stream name
            count: Maximum messages to read
            block: Milliseconds to block waiting for messages (None = no block)
        """
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            messages = await self._redis.xread({stream: "0"}, count=count, block=block)

            result_messages = []
            for stream_name, stream_messages in messages:
                for msg_id, msg_data in stream_messages:
                    parsed_data = {}
                    for k, v in msg_data.items():
                        key = k.decode() if isinstance(k, bytes) else k
                        val = v.decode() if isinstance(v, bytes) else v
                        try:
                            parsed_data[key] = json.loads(val)
                        except json.JSONDecodeError:
                            parsed_data[key] = val

                    result_messages.append({
                        "id": msg_id.decode() if isinstance(msg_id, bytes) else msg_id,
                        "data": parsed_data,
                    })

            return {
                "stream": stream,
                "messages": result_messages,
                "count": len(result_messages),
            }
        except Exception as e:
            return {"error": str(e)}

    async def stream_len(self, stream: str) -> dict[str, Any]:
        """Get the length of a Redis stream."""
        if not self._redis:
            await self.initialize()

        if not self._redis:
            return {"error": "Redis connection not available"}

        try:
            length = await self._redis.xlen(stream)
            return {"stream": stream, "length": length}
        except Exception as e:
            return {"error": str(e)}


# Tool definitions for Claude Agent SDK
CACHE_TOOLS = [
    {
        "name": "cache_get",
        "description": "Get a value from Redis cache.",
        "input_schema": {
            "type": "object",
            "properties": {
                "key": {"type": "string", "description": "Cache key"},
            },
            "required": ["key"],
        },
    },
    {
        "name": "cache_set",
        "description": "Set a value in Redis cache.",
        "input_schema": {
            "type": "object",
            "properties": {
                "key": {"type": "string", "description": "Cache key"},
                "value": {"description": "Value to cache"},
                "ttl_seconds": {"type": "integer", "description": "Time to live in seconds"},
            },
            "required": ["key", "value"],
        },
    },
    {
        "name": "cache_delete",
        "description": "Delete a key from Redis cache.",
        "input_schema": {
            "type": "object",
            "properties": {
                "key": {"type": "string", "description": "Cache key to delete"},
            },
            "required": ["key"],
        },
    },
    {
        "name": "cache_keys",
        "description": "List cache keys matching a pattern.",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "Glob pattern (e.g., 'user:*')", "default": "*"},
            },
        },
    },
    {
        "name": "cache_flush_pattern",
        "description": "Delete all keys matching a pattern. Use with caution!",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "Pattern of keys to delete"},
            },
            "required": ["pattern"],
        },
    },
    {
        "name": "cache_info",
        "description": "Get Redis server info and statistics.",
        "input_schema": {"type": "object", "properties": {}},
    },
    {
        "name": "stream_add",
        "description": "Add a message to a Redis stream (queue).",
        "input_schema": {
            "type": "object",
            "properties": {
                "stream": {"type": "string", "description": "Stream name"},
                "data": {"type": "object", "description": "Message data"},
                "maxlen": {"type": "integer", "description": "Maximum stream length", "default": 1000},
            },
            "required": ["stream", "data"],
        },
    },
    {
        "name": "stream_read",
        "description": "Read messages from a Redis stream.",
        "input_schema": {
            "type": "object",
            "properties": {
                "stream": {"type": "string", "description": "Stream name"},
                "count": {"type": "integer", "description": "Max messages to read", "default": 10},
            },
            "required": ["stream"],
        },
    },
    {
        "name": "stream_len",
        "description": "Get the length of a Redis stream.",
        "input_schema": {
            "type": "object",
            "properties": {
                "stream": {"type": "string", "description": "Stream name"},
            },
            "required": ["stream"],
        },
    },
]


__all__ = ["CacheTools", "CACHE_TOOLS"]
