from __future__ import annotations

"""Redis-backed persistent conversation memory."""

import json
from dataclasses import dataclass

from agentrspy.core import Memory, MemoryError, Message
from agentrspy.core.types import Role


def _message_to_dict(message: Message) -> dict:
    return {
        "role": message.role.value,
        "content": {"text_value": message.content.text_value, "parts": None},
        "tool_calls": None
        if message.tool_calls is None
        else [tool_call.__dict__ for tool_call in message.tool_calls],
        "tool_call_id": message.tool_call_id,
        "metadata": message.metadata,
    }


def _message_from_dict(value: dict) -> Message:
    from agentrspy.core import MessageContent, ToolCall

    tool_calls = value.get("tool_calls")
    return Message(
        role=Role(value["role"]),
        content=MessageContent(
            text_value=(value.get("content") or {}).get("text_value")
        ),
        tool_calls=None
        if tool_calls is None
        else [ToolCall(**item) for item in tool_calls],
        tool_call_id=value.get("tool_call_id"),
        metadata=value.get("metadata") or {},
    )


@dataclass(slots=True)
class RedisMemory(Memory):
    """Redis-backed persistent conversation memory."""

    connection: object
    session_id: str
    ttl_seconds: int = 3600

    @classmethod
    async def new(cls, redis_url: str, session_id: str) -> "RedisMemory":
        try:
            import redis.asyncio as redis
        except ImportError as error:  # pragma: no cover - optional dependency
            raise MemoryError.backend("redis extra is not installed") from error
        try:
            connection = redis.from_url(redis_url)
            await connection.ping()
        except Exception as error:  # pragma: no cover - network dependent
            raise MemoryError.backend(str(error)) from error
        return cls(connection=connection, session_id=session_id)

    def _key(self) -> str:
        return f"agentrs:session:{self.session_id}"

    async def store(self, key: str, value: Message) -> None:
        del key
        payload = json.dumps(_message_to_dict(value))
        try:
            await self.connection.rpush(self._key(), payload)
            await self.connection.expire(self._key(), self.ttl_seconds)
        except Exception as error:  # pragma: no cover - network dependent
            raise MemoryError.backend(str(error)) from error

    async def retrieve(self, query: str, limit: int) -> list[Message]:
        history = await self.history()
        lowered = query.lower()
        return [
            message for message in history if lowered in message.text_content().lower()
        ][:limit]

    async def history(self) -> list[Message]:
        try:
            payloads = await self.connection.lrange(self._key(), 0, -1)
        except Exception as error:  # pragma: no cover - network dependent
            raise MemoryError.backend(str(error)) from error
        return [_message_from_dict(json.loads(payload)) for payload in payloads]

    async def clear(self) -> None:
        try:
            await self.connection.delete(self._key())
        except Exception as error:  # pragma: no cover - network dependent
            raise MemoryError.backend(str(error)) from error

    async def token_count(self) -> int:
        history = await self.history()
        return sum(len(message.text_content()) // 4 for message in history)
