"""
File-based session memory for GSD-RLM agents.

Provides persistent conversation history that survives across
task boundaries and can be resumed in new sessions.

Extended with H-MEM integration for unified memory management.
"""

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Dict, Any, Optional, TYPE_CHECKING
from dataclasses import dataclass, asdict, field

if TYPE_CHECKING:
    from gsd_rlm.memory.hmem.store import EpisodeStore
    from gsd_rlm.memory.hmem.episode import Episode, EpisodeType
    from gsd_rlm.memory.bridge.store import MemoryBridge
    from gsd_rlm.memory.bridge.facts import BridgeFact
    from gsd_rlm.memory.integration.session_bridge import (
        SessionResumption,
        ResumptionContext,
    )


def _utcnow_iso() -> str:
    """Get current UTC time as ISO string (timezone-aware)."""
    return datetime.now(timezone.utc).isoformat()


@dataclass
class SessionMessage:
    """A single message in the session conversation."""

    role: str  # "user", "agent", "system", "tool"
    content: str
    timestamp: str
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if self.metadata is None:
            self.metadata = {}

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        return {
            "role": self.role,
            "content": self.content,
            "timestamp": self.timestamp,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SessionMessage":
        """Create from dictionary."""
        return cls(
            role=data.get("role", "unknown"),
            content=data.get("content", ""),
            timestamp=data.get("timestamp", ""),
            metadata=data.get("metadata", {}),
        )


@dataclass
class TaskOutput:
    """Output from a completed task."""

    task: str
    result: Any
    success: bool
    routing: Optional[List[str]] = None
    error: Optional[str] = None
    timestamp: str = field(default_factory=_utcnow_iso)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "task": self.task,
            "result": self.result,
            "success": self.success,
            "routing": self.routing,
            "error": self.error,
            "timestamp": self.timestamp,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "TaskOutput":
        """Create from dictionary."""
        return cls(
            task=data.get("task", ""),
            result=data.get("result"),
            success=data.get("success", False),
            routing=data.get("routing"),
            error=data.get("error"),
            timestamp=data.get("timestamp", ""),
        )


@dataclass
class SessionState:
    """Complete state of a session."""

    session_id: str
    agent_name: str
    created_at: str
    updated_at: str
    messages: List[SessionMessage]
    task_outputs: List[TaskOutput]
    current_task: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        return {
            "session_id": self.session_id,
            "agent_name": self.agent_name,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "messages": [m.to_dict() for m in self.messages],
            "task_outputs": [t.to_dict() for t in self.task_outputs],
            "current_task": self.current_task,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SessionState":
        """Create from dictionary."""
        return cls(
            session_id=data.get("session_id", ""),
            agent_name=data.get("agent_name", ""),
            created_at=data.get("created_at", ""),
            updated_at=data.get("updated_at", ""),
            messages=[SessionMessage.from_dict(m) for m in data.get("messages", [])],
            task_outputs=[
                TaskOutput.from_dict(t) for t in data.get("task_outputs", [])
            ],
            current_task=data.get("current_task"),
            metadata=data.get("metadata", {}),
        )

    @property
    def message_count(self) -> int:
        """Number of messages in session."""
        return len(self.messages)

    @property
    def task_count(self) -> int:
        """Number of tasks completed."""
        return len(self.task_outputs)


class FileSessionMemory:
    """File-based session persistence for resumable conversations (AGENT-04)."""

    def __init__(self, sessions_dir: Path):
        """
        Initialize session memory.

        Args:
            sessions_dir: Directory for session files
        """
        self.sessions_dir = Path(sessions_dir)
        self.sessions_dir.mkdir(parents=True, exist_ok=True)

    def _session_file(self, session_id: str) -> Path:
        """Get path to session file."""
        # Sanitize session_id for filesystem
        safe_id = "".join(c if c.isalnum() or c in "-_" else "_" for c in session_id)
        return self.sessions_dir / f"session-{safe_id}.json"

    def create(self, session_id: str, agent_name: str) -> SessionState:
        """Create new session.

        Args:
            session_id: Unique session identifier
            agent_name: Name of the agent

        Returns:
            New SessionState instance
        """
        now = _utcnow_iso()
        session = SessionState(
            session_id=session_id,
            agent_name=agent_name,
            created_at=now,
            updated_at=now,
            messages=[],
            task_outputs=[],
        )
        self.save(session)
        return session

    def load(self, session_id: str) -> Optional[SessionState]:
        """Load existing session.

        Args:
            session_id: Session identifier

        Returns:
            SessionState if exists, None otherwise
        """
        file = self._session_file(session_id)
        if not file.exists():
            return None

        try:
            with open(file, "r", encoding="utf-8") as f:
                data = json.load(f)
            return SessionState.from_dict(data)
        except (json.JSONDecodeError, KeyError) as e:
            # Corrupted session file
            return None

    def save(self, session: SessionState) -> None:
        """Persist session to file.

        Args:
            session: Session state to save
        """
        session.updated_at = _utcnow_iso()
        file = self._session_file(session.session_id)

        with open(file, "w", encoding="utf-8") as f:
            json.dump(session.to_dict(), f, indent=2, ensure_ascii=False)

    def delete(self, session_id: str) -> bool:
        """Delete a session.

        Args:
            session_id: Session identifier

        Returns:
            True if deleted, False if not found
        """
        file = self._session_file(session_id)
        if file.exists():
            file.unlink()
            return True
        return False

    def list_sessions(self) -> List[Dict[str, Any]]:
        """List all available sessions.

        Returns:
            List of session metadata (id, agent_name, created_at, updated_at)
        """
        sessions = []
        for file in self.sessions_dir.glob("session-*.json"):
            try:
                with open(file, "r", encoding="utf-8") as f:
                    data = json.load(f)
                sessions.append(
                    {
                        "session_id": data.get("session_id", ""),
                        "agent_name": data.get("agent_name", ""),
                        "created_at": data.get("created_at", ""),
                        "updated_at": data.get("updated_at", ""),
                        "message_count": len(data.get("messages", [])),
                        "task_count": len(data.get("task_outputs", [])),
                    }
                )
            except (json.JSONDecodeError, KeyError):
                continue

        # Sort by updated_at descending (most recent first)
        sessions.sort(key=lambda s: s.get("updated_at", ""), reverse=True)
        return sessions

    def add_message(
        self,
        session: SessionState,
        role: str,
        content: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> SessionMessage:
        """Add message to conversation history.

        Args:
            session: Session to update
            role: Message role (user, agent, system, tool)
            content: Message content
            metadata: Optional metadata

        Returns:
            The created SessionMessage
        """
        message = SessionMessage(
            role=role,
            content=content,
            timestamp=_utcnow_iso(),
            metadata=metadata or {},
        )
        session.messages.append(message)
        self.save(session)
        return message

    def add_task_output(
        self,
        session: SessionState,
        task: str,
        result: Any,
        success: bool = True,
        routing: Optional[List[str]] = None,
        error: Optional[str] = None,
    ) -> TaskOutput:
        """Add task output to session.

        Args:
            session: Session to update
            task: Task description
            result: Task result
            success: Whether task succeeded
            routing: Agent routing used
            error: Error message if failed

        Returns:
            The created TaskOutput
        """
        output = TaskOutput(
            task=task,
            result=result,
            success=success,
            routing=routing,
            error=error,
        )
        session.task_outputs.append(output)
        self.save(session)
        return output

    def get_context_for_llm(
        self,
        session: SessionState,
        max_messages: int = 50,
    ) -> List[Dict[str, str]]:
        """Get conversation history in LLM format.

        Args:
            session: Session to extract from
            max_messages: Maximum messages to include

        Returns:
            List of {role, content} dicts for LLM
        """
        messages = session.messages[-max_messages:]
        return [{"role": m.role, "content": m.content} for m in messages]

    def get_recent_task_outputs(
        self,
        session: SessionState,
        max_outputs: int = 10,
    ) -> List[Dict[str, Any]]:
        """Get recent task outputs for context.

        Args:
            session: Session to extract from
            max_outputs: Maximum outputs to include

        Returns:
            List of task output dicts
        """
        outputs = session.task_outputs[-max_outputs:]
        return [t.to_dict() for t in outputs]

    def clear_messages(self, session: SessionState) -> None:
        """Clear all messages from session.

        Args:
            session: Session to clear
        """
        session.messages = []
        self.save(session)

    def get_or_create(
        self,
        session_id: str,
        agent_name: str,
    ) -> SessionState:
        """Get existing session or create new one.

        Args:
            session_id: Session identifier
            agent_name: Agent name (used if creating)

        Returns:
            SessionState instance
        """
        session = self.load(session_id)
        if session:
            return session
        return self.create(session_id, agent_name)

    # =================================================================
    # H-MEM Integration Methods
    # =================================================================

    def record_episode(
        self,
        session: SessionState,
        episode_store: "EpisodeStore",
        agent_id: str,
        episode_type: "EpisodeType",
        context: str,
        action: str,
        outcome: str,
        success: bool = True,
        tags: Optional[List[str]] = None,
    ) -> "Episode":
        """
        Record an episode to both session memory and EpisodeStore.

        This method provides unified episode recording that persists
        to both the session's task outputs and the H-MEM episode store.

        Args:
            session: Session to record in.
            episode_store: EpisodeStore for H-MEM persistence.
            agent_id: Agent identifier.
            episode_type: Type of episode (TASK_EXECUTION, etc.).
            context: Context that led to the action.
            action: Action taken by the agent.
            outcome: Result of the action.
            success: Whether the action was successful.
            tags: Optional tags for categorization.

        Returns:
            The created Episode instance.

        Example:
            >>> episode = memory.record_episode(
            ...     session, episode_store, "agent-1",
            ...     EpisodeType.TASK_EXECUTION,
            ...     "User requested feature",
            ...     "Analyzed codebase",
            ...     "Found relevant files",
            ...     success=True
            ... )
        """
        from gsd_rlm.memory.hmem.episode import Episode

        # Create episode
        episode = Episode(
            episode_id=f"ep-{uuid.uuid4().hex[:12]}",
            agent_id=agent_id,
            session_id=session.session_id,
            episode_type=episode_type,
            context=context,
            action=action,
            outcome=outcome,
            success=success,
            tags=tags or [],
        )

        # Store in EpisodeStore
        episode_store.store(episode)

        # Also record as task output in session
        self.add_task_output(
            session,
            task=f"[Episode] {action}",
            result={
                "episode_id": episode.episode_id,
                "outcome": outcome[:200],
                "success": success,
            },
            success=success,
        )

        return episode

    async def get_resumption_context(
        self,
        session_id: str,
        session_resumption: "SessionResumption",
    ) -> "ResumptionContext":
        """
        Get full resumption context for a session.

        Uses SessionResumption to restore context from all memory stores.

        Args:
            session_id: Session identifier to resume.
            session_resumption: SessionResumption instance.

        Returns:
            ResumptionContext with all restored data.

        Raises:
            ValueError: If session not found.

        Example:
            >>> resumption = SessionResumption(
            ...     session_memory=memory,
            ...     episode_store=episodes,
            ...     memory_bridge=bridge,
            ... )
            >>> context = await memory.get_resumption_context("sess-001", resumption)
        """
        return await session_resumption.resume(session_id)

    def store_session_fact(
        self,
        memory_bridge: "MemoryBridge",
        session: SessionState,
        key: str,
        value: Any,
        source: str = "session",
        confidence: float = 1.0,
    ) -> "BridgeFact":
        """
        Store a session-level fact in Memory Bridge (L0_SESSION).

        Args:
            memory_bridge: MemoryBridge for fact storage.
            session: Session to store fact for.
            key: Fact key.
            value: Fact value.
            source: Source of the fact.
            confidence: Confidence level (0.0 to 1.0).

        Returns:
            The created BridgeFact instance.

        Example:
            >>> fact = memory.store_session_fact(
            ...     bridge, session, "user_preference", "dark_mode",
            ...     source="user_input"
            ... )
        """
        from gsd_rlm.memory.bridge.facts import BridgeFact, BridgeLevel

        fact = BridgeFact.create(
            level=BridgeLevel.L0_SESSION,
            scope_id=session.session_id,
            key=key,
            value=value,
            source=source,
            confidence=confidence,
        )

        memory_bridge.store_fact(fact)
        return fact


class IntegratedMemorySystem:
    """
    Facade for unified access to all memory systems.

    Provides a single interface for recording experiences, storing facts,
    getting context, and resuming sessions across all memory stores.

    This class integrates:
    - FileSessionMemory for session persistence
    - EpisodeStore for H-MEM episode storage
    - MemoryBridge for persistent facts
    - SessionResumption for context restoration

    Attributes:
        project_root: Root directory of the project.
        session_memory: FileSessionMemory instance.
        episode_store: EpisodeStore instance.
        memory_bridge: MemoryBridge instance.

    Example:
        >>> system = IntegratedMemorySystem(Path("/path/to/project"))
        >>> episode = system.record_experience(
        ...     "sess-001", "agent-1", "context", "action", "outcome"
        ... )
        >>> context = await system.resume_session("sess-001")
    """

    def __init__(
        self,
        project_root: Path,
        sessions_dir: Optional[Path] = None,
        hmem_db_path: Optional[str] = None,
    ):
        """
        Initialize IntegratedMemorySystem.

        Args:
            project_root: Root directory of the project.
            sessions_dir: Optional custom sessions directory.
            hmem_db_path: Optional custom H-MEM database path.
        """
        from gsd_rlm.memory.hmem.store import EpisodeStore
        from gsd_rlm.memory.bridge.store import MemoryBridge

        self.project_root = Path(project_root)

        # Set up default paths
        if sessions_dir is None:
            sessions_dir = self.project_root / ".planning" / "sessions"
        if hmem_db_path is None:
            hmem_db_path = str(self.project_root / "memory" / "hmem.db")

        # Initialize components
        self.session_memory = FileSessionMemory(sessions_dir)
        self.episode_store = EpisodeStore(hmem_db_path)
        self.memory_bridge = MemoryBridge(self.project_root)

    def record_experience(
        self,
        session_id: str,
        agent_id: str,
        context: str,
        action: str,
        outcome: str,
        success: bool = True,
        episode_type: Optional["EpisodeType"] = None,
        tags: Optional[List[str]] = None,
    ) -> "Episode":
        """
        Record an agent experience as an H-MEM episode.

        Args:
            session_id: Session identifier.
            agent_id: Agent identifier.
            context: Context that led to the action.
            action: Action taken by the agent.
            outcome: Result of the action.
            success: Whether the action was successful.
            episode_type: Optional episode type (default: TASK_EXECUTION).
            tags: Optional tags for categorization.

        Returns:
            The created Episode instance.
        """
        from gsd_rlm.memory.hmem.episode import EpisodeType

        if episode_type is None:
            episode_type = EpisodeType.TASK_EXECUTION

        # Get or create session
        session = self.session_memory.get_or_create(session_id, agent_id)

        return self.session_memory.record_episode(
            session=session,
            episode_store=self.episode_store,
            agent_id=agent_id,
            episode_type=episode_type,
            context=context,
            action=action,
            outcome=outcome,
            success=success,
            tags=tags,
        )

    def store_project_fact(
        self,
        key: str,
        value: Any,
        source: str = "unknown",
        level: Optional[str] = None,
    ) -> "BridgeFact":
        """
        Store a fact in Memory Bridge.

        Args:
            key: Fact key.
            value: Fact value.
            source: Source of the fact.
            level: Optional level override (L0, L1, L2, L3).
                   Default: L2_PROJECT.

        Returns:
            The created BridgeFact instance.
        """
        from gsd_rlm.memory.bridge.facts import BridgeFact, BridgeLevel

        # Determine level
        if level == "L0":
            bridge_level = BridgeLevel.L0_SESSION
            scope_id = "current"
        elif level == "L1":
            bridge_level = BridgeLevel.L1_PHASE
            scope_id = "03-memory-systems-infiniretri"
        elif level == "L3":
            bridge_level = BridgeLevel.L3_WORKSPACE
            scope_id = "workspace"
        else:
            bridge_level = BridgeLevel.L2_PROJECT
            scope_id = "project"

        fact = BridgeFact.create(
            level=bridge_level,
            scope_id=scope_id,
            key=key,
            value=value,
            source=source,
        )

        self.memory_bridge.store_fact(fact)
        return fact

    async def get_context_for_task(
        self,
        session_id: str,
        task: str,
        max_stores: int = 3,
    ) -> Dict[str, Any]:
        """
        Get relevant context for a task from all memory stores.

        Uses MemoryRouter to determine which stores to query and
        assembles unified context for LLM consumption.

        Args:
            session_id: Current session identifier.
            task: Task description or query.
            max_stores: Maximum number of stores to query.

        Returns:
            Dictionary with assembled context.
        """
        from gsd_rlm.memory.integration.context import ContextAssembler
        from gsd_rlm.memory.bridge.router import MemoryRouter

        # Create router and assembler
        router = MemoryRouter()
        assembler = ContextAssembler(
            session_memory=self.session_memory,
            episode_store=self.episode_store,
            memory_bridge=self.memory_bridge,
            memory_router=router,
        )

        # Get context
        context = await assembler.get_context_for_task(session_id, task, max_stores)

        return {
            "assembled_context": context.to_dict(),
            "llm_context": context.to_llm_context(),
        }

    async def resume_session(self, session_id: str) -> "ResumptionContext":
        """
        Resume a session with full context restoration.

        Args:
            session_id: Session identifier to resume.

        Returns:
            ResumptionContext with all restored data.

        Raises:
            ValueError: If session not found.
        """
        from gsd_rlm.memory.integration.session_bridge import SessionResumption

        resumption = SessionResumption(
            session_memory=self.session_memory,
            episode_store=self.episode_store,
            memory_bridge=self.memory_bridge,
        )

        return await resumption.resume(session_id)

    def create_session(self, session_id: str, agent_name: str) -> SessionState:
        """
        Create a new session.

        Args:
            session_id: Unique session identifier.
            agent_name: Name of the agent.

        Returns:
            New SessionState instance.
        """
        return self.session_memory.create(session_id, agent_name)

    def load_session(self, session_id: str) -> Optional[SessionState]:
        """
        Load an existing session.

        Args:
            session_id: Session identifier.

        Returns:
            SessionState if exists, None otherwise.
        """
        return self.session_memory.load(session_id)

    def list_sessions(self) -> List[Dict[str, Any]]:
        """
        List all available sessions.

        Returns:
            List of session metadata dictionaries.
        """
        return self.session_memory.list_sessions()
