"""Memory protocols — structural typing contracts for pluggable memory backends."""

from __future__ import annotations

from typing import Protocol, runtime_checkable

from agent_gateway.memory.domain import (
    MemoryRecord,
    MemorySearchResult,
    MemoryType,
)


@runtime_checkable
class MemoryRepository(Protocol):
    """Storage and retrieval contract for agent memories.

    Implementations must handle CRUD and search.
    Satisfied structurally (duck typing) — no inheritance required.

    The ``user_id`` parameter enables per-user memory scoping:
    - ``user_id=None`` → global agent memory only
    - ``user_id="xyz"`` with ``include_global=True`` → both user and global
    - ``user_id="xyz"`` with ``include_global=False`` → user-only
    """

    async def save(self, record: MemoryRecord) -> None:
        """Upsert a memory record (insert or update by id)."""
        ...

    async def get(self, agent_id: str, memory_id: str) -> MemoryRecord | None:
        """Retrieve a specific memory by ID."""
        ...

    async def list_memories(
        self,
        agent_id: str,
        *,
        user_id: str | None = None,
        include_global: bool = True,
        memory_type: MemoryType | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> list[MemoryRecord]:
        """List memories with optional type/user filter, ordered by updated_at desc."""
        ...

    async def search(
        self,
        agent_id: str,
        query: str,
        *,
        user_id: str | None = None,
        include_global: bool = True,
        memory_type: MemoryType | None = None,
        limit: int = 10,
    ) -> list[MemorySearchResult]:
        """Search memories by relevance. Backend decides search strategy."""
        ...

    async def delete(self, agent_id: str, memory_id: str) -> bool:
        """Delete a memory. Returns True if it existed."""
        ...

    async def delete_all(self, agent_id: str, user_id: str | None = None) -> int:
        """Delete all memories for an agent (optionally scoped to user). Returns count."""
        ...

    async def count(self, agent_id: str, user_id: str | None = None) -> int:
        """Count memories for an agent (optionally scoped to user)."""
        ...


@runtime_checkable
class MemoryBackend(Protocol):
    """Top-level memory backend with lifecycle management.

    Mirrors the PersistenceBackend pattern — provides lifecycle hooks
    and access to the underlying repository.
    """

    async def initialize(self) -> None:
        """Create tables/indexes/files. Must be idempotent."""
        ...

    async def dispose(self) -> None:
        """Close connections and release resources."""
        ...

    @property
    def memory_repo(self) -> MemoryRepository:
        """Access the memory repository."""
        ...
