"""
Memory Manager

Facade for the Agent Memory System providing unified access
to all memory types and operations.
"""

import logging
from typing import Any
from uuid import UUID

from .config import MemorySettings, get_memory_settings
from .types import (
    Episode,
    EpisodeCreate,
    Fact,
    FactCreate,
    MemoryStats,
    MemoryType,
    Outcome,
    Procedure,
    ProcedureCreate,
    RetrievalResult,
    ScopeContext,
    ScopeLevel,
    TaskState,
)

logger = logging.getLogger(__name__)


class MemoryManager:
    """
    Unified facade for the Agent Memory System.

    Provides a single entry point for all memory operations across
    working, episodic, semantic, and procedural memory types.

    Usage:
        manager = MemoryManager.create()

        # Working memory
        await manager.set_working("key", {"data": "value"})
        value = await manager.get_working("key")

        # Episodic memory
        episode = await manager.record_episode(episode_data)
        similar = await manager.search_episodes("query")

        # Semantic memory
        fact = await manager.store_fact(fact_data)
        facts = await manager.search_facts("query")

        # Procedural memory
        procedure = await manager.record_procedure(procedure_data)
        procedures = await manager.find_procedures("context")
    """

    def __init__(
        self,
        settings: MemorySettings,
        scope: ScopeContext,
    ) -> None:
        """
        Initialize the MemoryManager.

        Args:
            settings: Memory configuration settings
            scope: The scope context for this manager instance
        """
        self._settings = settings
        self._scope = scope
        self._initialized = False

        # These will be initialized when the respective sub-modules are implemented
        self._working_memory: Any | None = None
        self._episodic_memory: Any | None = None
        self._semantic_memory: Any | None = None
        self._procedural_memory: Any | None = None

        logger.debug(
            "MemoryManager created for scope %s:%s",
            scope.scope_type.value,
            scope.scope_id,
        )

    @classmethod
    def create(
        cls,
        scope_type: ScopeLevel = ScopeLevel.SESSION,
        scope_id: str = "default",
        parent_scope: ScopeContext | None = None,
        settings: MemorySettings | None = None,
    ) -> "MemoryManager":
        """
        Create a new MemoryManager instance.

        Args:
            scope_type: The scope level for this manager
            scope_id: The scope identifier
            parent_scope: Optional parent scope for inheritance
            settings: Optional custom settings (uses global if not provided)

        Returns:
            A new MemoryManager instance
        """
        if settings is None:
            settings = get_memory_settings()

        scope = ScopeContext(
            scope_type=scope_type,
            scope_id=scope_id,
            parent=parent_scope,
        )

        return cls(settings=settings, scope=scope)

    @classmethod
    def for_session(
        cls,
        session_id: str,
        agent_instance_id: UUID | None = None,
        project_id: UUID | None = None,
    ) -> "MemoryManager":
        """
        Create a MemoryManager for a specific session.

        Builds the appropriate scope hierarchy based on provided IDs.

        Args:
            session_id: The session identifier
            agent_instance_id: Optional agent instance ID
            project_id: Optional project ID

        Returns:
            A MemoryManager configured for the session scope
        """
        settings = get_memory_settings()

        # Build scope hierarchy
        parent: ScopeContext | None = None

        if project_id:
            parent = ScopeContext(
                scope_type=ScopeLevel.PROJECT,
                scope_id=str(project_id),
                parent=ScopeContext(
                    scope_type=ScopeLevel.GLOBAL,
                    scope_id="global",
                ),
            )

        if agent_instance_id:
            parent = ScopeContext(
                scope_type=ScopeLevel.AGENT_INSTANCE,
                scope_id=str(agent_instance_id),
                parent=parent,
            )

        scope = ScopeContext(
            scope_type=ScopeLevel.SESSION,
            scope_id=session_id,
            parent=parent,
        )

        return cls(settings=settings, scope=scope)

    @property
    def scope(self) -> ScopeContext:
        """Get the current scope context."""
        return self._scope

    @property
    def settings(self) -> MemorySettings:
        """Get the memory settings."""
        return self._settings

    # =========================================================================
    # Working Memory Operations
    # =========================================================================

    async def set_working(
        self,
        key: str,
        value: Any,
        ttl_seconds: int | None = None,
    ) -> None:
        """
        Set a value in working memory.

        Args:
            key: The key to store the value under
            value: The value to store (must be JSON serializable)
            ttl_seconds: Optional TTL (uses default if not provided)
        """
        # Placeholder - will be implemented in #89
        logger.debug("set_working called for key=%s (not yet implemented)", key)
        raise NotImplementedError("Working memory not yet implemented")

    async def get_working(
        self,
        key: str,
        default: Any = None,
    ) -> Any:
        """
        Get a value from working memory.

        Args:
            key: The key to retrieve
            default: Default value if key not found

        Returns:
            The stored value or default
        """
        # Placeholder - will be implemented in #89
        logger.debug("get_working called for key=%s (not yet implemented)", key)
        raise NotImplementedError("Working memory not yet implemented")

    async def delete_working(self, key: str) -> bool:
        """
        Delete a value from working memory.

        Args:
            key: The key to delete

        Returns:
            True if the key was deleted, False if not found
        """
        # Placeholder - will be implemented in #89
        logger.debug("delete_working called for key=%s (not yet implemented)", key)
        raise NotImplementedError("Working memory not yet implemented")

    async def set_task_state(self, state: TaskState) -> None:
        """
        Set the current task state in working memory.

        Args:
            state: The task state to store
        """
        # Placeholder - will be implemented in #89
        logger.debug(
            "set_task_state called for task=%s (not yet implemented)",
            state.task_id,
        )
        raise NotImplementedError("Working memory not yet implemented")

    async def get_task_state(self) -> TaskState | None:
        """
        Get the current task state from working memory.

        Returns:
            The current task state or None
        """
        # Placeholder - will be implemented in #89
        logger.debug("get_task_state called (not yet implemented)")
        raise NotImplementedError("Working memory not yet implemented")

    async def create_checkpoint(self) -> str:
        """
        Create a checkpoint of the current working memory state.

        Returns:
            The checkpoint ID
        """
        # Placeholder - will be implemented in #89
        logger.debug("create_checkpoint called (not yet implemented)")
        raise NotImplementedError("Working memory not yet implemented")

    async def restore_checkpoint(self, checkpoint_id: str) -> None:
        """
        Restore working memory from a checkpoint.

        Args:
            checkpoint_id: The checkpoint to restore from
        """
        # Placeholder - will be implemented in #89
        logger.debug(
            "restore_checkpoint called for id=%s (not yet implemented)",
            checkpoint_id,
        )
        raise NotImplementedError("Working memory not yet implemented")

    # =========================================================================
    # Episodic Memory Operations
    # =========================================================================

    async def record_episode(self, episode: EpisodeCreate) -> Episode:
        """
        Record a new episode in episodic memory.

        Args:
            episode: The episode data to record

        Returns:
            The created episode with ID
        """
        # Placeholder - will be implemented in #90
        logger.debug(
            "record_episode called for task=%s (not yet implemented)",
            episode.task_type,
        )
        raise NotImplementedError("Episodic memory not yet implemented")

    async def search_episodes(
        self,
        query: str,
        limit: int | None = None,
    ) -> RetrievalResult[Episode]:
        """
        Search for similar episodes.

        Args:
            query: The search query
            limit: Maximum results to return

        Returns:
            Retrieval result with matching episodes
        """
        # Placeholder - will be implemented in #90
        logger.debug(
            "search_episodes called for query=%s (not yet implemented)",
            query[:50],
        )
        raise NotImplementedError("Episodic memory not yet implemented")

    async def get_recent_episodes(
        self,
        limit: int = 10,
    ) -> list[Episode]:
        """
        Get the most recent episodes.

        Args:
            limit: Maximum episodes to return

        Returns:
            List of recent episodes
        """
        # Placeholder - will be implemented in #90
        logger.debug("get_recent_episodes called (not yet implemented)")
        raise NotImplementedError("Episodic memory not yet implemented")

    async def get_episodes_by_outcome(
        self,
        outcome: Outcome,
        limit: int = 10,
    ) -> list[Episode]:
        """
        Get episodes by outcome.

        Args:
            outcome: The outcome to filter by
            limit: Maximum episodes to return

        Returns:
            List of episodes with the specified outcome
        """
        # Placeholder - will be implemented in #90
        logger.debug(
            "get_episodes_by_outcome called for outcome=%s (not yet implemented)",
            outcome.value,
        )
        raise NotImplementedError("Episodic memory not yet implemented")

    # =========================================================================
    # Semantic Memory Operations
    # =========================================================================

    async def store_fact(self, fact: FactCreate) -> Fact:
        """
        Store a new fact in semantic memory.

        Args:
            fact: The fact data to store

        Returns:
            The created fact with ID
        """
        # Placeholder - will be implemented in #91
        logger.debug(
            "store_fact called for %s %s %s (not yet implemented)",
            fact.subject,
            fact.predicate,
            fact.object,
        )
        raise NotImplementedError("Semantic memory not yet implemented")

    async def search_facts(
        self,
        query: str,
        limit: int | None = None,
    ) -> RetrievalResult[Fact]:
        """
        Search for facts matching a query.

        Args:
            query: The search query
            limit: Maximum results to return

        Returns:
            Retrieval result with matching facts
        """
        # Placeholder - will be implemented in #91
        logger.debug(
            "search_facts called for query=%s (not yet implemented)",
            query[:50],
        )
        raise NotImplementedError("Semantic memory not yet implemented")

    async def get_facts_by_entity(
        self,
        entity: str,
        limit: int = 20,
    ) -> list[Fact]:
        """
        Get facts related to an entity.

        Args:
            entity: The entity to search for
            limit: Maximum facts to return

        Returns:
            List of facts mentioning the entity
        """
        # Placeholder - will be implemented in #91
        logger.debug(
            "get_facts_by_entity called for entity=%s (not yet implemented)",
            entity,
        )
        raise NotImplementedError("Semantic memory not yet implemented")

    async def reinforce_fact(self, fact_id: UUID) -> Fact:
        """
        Reinforce a fact (increase confidence from repeated learning).

        Args:
            fact_id: The fact to reinforce

        Returns:
            The updated fact
        """
        # Placeholder - will be implemented in #91
        logger.debug(
            "reinforce_fact called for id=%s (not yet implemented)",
            fact_id,
        )
        raise NotImplementedError("Semantic memory not yet implemented")

    # =========================================================================
    # Procedural Memory Operations
    # =========================================================================

    async def record_procedure(self, procedure: ProcedureCreate) -> Procedure:
        """
        Record a new procedure.

        Args:
            procedure: The procedure data to record

        Returns:
            The created procedure with ID
        """
        # Placeholder - will be implemented in #92
        logger.debug(
            "record_procedure called for name=%s (not yet implemented)",
            procedure.name,
        )
        raise NotImplementedError("Procedural memory not yet implemented")

    async def find_procedures(
        self,
        context: str,
        limit: int = 5,
    ) -> list[Procedure]:
        """
        Find procedures matching the current context.

        Args:
            context: The context to match against
            limit: Maximum procedures to return

        Returns:
            List of matching procedures sorted by success rate
        """
        # Placeholder - will be implemented in #92
        logger.debug(
            "find_procedures called for context=%s (not yet implemented)",
            context[:50],
        )
        raise NotImplementedError("Procedural memory not yet implemented")

    async def record_procedure_outcome(
        self,
        procedure_id: UUID,
        success: bool,
    ) -> None:
        """
        Record the outcome of using a procedure.

        Args:
            procedure_id: The procedure that was used
            success: Whether the procedure succeeded
        """
        # Placeholder - will be implemented in #92
        logger.debug(
            "record_procedure_outcome called for id=%s success=%s (not yet implemented)",
            procedure_id,
            success,
        )
        raise NotImplementedError("Procedural memory not yet implemented")

    # =========================================================================
    # Cross-Memory Operations
    # =========================================================================

    async def recall(
        self,
        query: str,
        memory_types: list[MemoryType] | None = None,
        limit: int = 10,
    ) -> dict[MemoryType, list[Any]]:
        """
        Recall memories across multiple memory types.

        Args:
            query: The search query
            memory_types: Memory types to search (all if not specified)
            limit: Maximum results per type

        Returns:
            Dictionary mapping memory types to results
        """
        # Placeholder - will be implemented in #97 (Component Integration)
        logger.debug("recall called for query=%s (not yet implemented)", query[:50])
        raise NotImplementedError("Cross-memory recall not yet implemented")

    async def get_stats(
        self,
        memory_type: MemoryType | None = None,
    ) -> list[MemoryStats]:
        """
        Get memory statistics.

        Args:
            memory_type: Specific type or all if not specified

        Returns:
            List of statistics for requested memory types
        """
        # Placeholder - will be implemented in #100 (Metrics & Observability)
        logger.debug("get_stats called (not yet implemented)")
        raise NotImplementedError("Memory stats not yet implemented")

    # =========================================================================
    # Lifecycle Operations
    # =========================================================================

    async def initialize(self) -> None:
        """
        Initialize the memory manager and its backends.

        Should be called before using the manager.
        """
        if self._initialized:
            logger.debug("MemoryManager already initialized")
            return

        logger.info(
            "Initializing MemoryManager for scope %s:%s",
            self._scope.scope_type.value,
            self._scope.scope_id,
        )

        # TODO: Initialize backends when implemented

        self._initialized = True
        logger.info("MemoryManager initialized successfully")

    async def close(self) -> None:
        """
        Close the memory manager and release resources.

        Should be called when done using the manager.
        """
        if not self._initialized:
            return

        logger.info(
            "Closing MemoryManager for scope %s:%s",
            self._scope.scope_type.value,
            self._scope.scope_id,
        )

        # TODO: Close backends when implemented

        self._initialized = False
        logger.info("MemoryManager closed successfully")

    async def __aenter__(self) -> "MemoryManager":
        """Async context manager entry."""
        await self.initialize()
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Async context manager exit."""
        await self.close()
