"""Session store for conversation persistence."""

import json
from dataclasses import asdict, dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Iterator


@dataclass
class ConversationMessage:
    """A message in a conversation."""

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

    def __post_init__(self):
        if not self.timestamp:
            self.timestamp = datetime.now().isoformat()


@dataclass
class Conversation:
    """A conversation session."""

    id: str
    title: str = ""
    messages: list[ConversationMessage] = field(default_factory=list)
    created_at: str = ""
    updated_at: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        now = datetime.now().isoformat()
        if not self.created_at:
            self.created_at = now
        if not self.updated_at:
            self.updated_at = now

    def add_message(self, role: str, content: str, metadata: dict | None = None) -> ConversationMessage:
        """Add a message to the conversation.

        Args:
            role: Message role (user, assistant, system, tool).
            content: Message content.
            metadata: Optional metadata.

        Returns:
            The created message.
        """
        msg = ConversationMessage(
            role=role,
            content=content,
            metadata=metadata or {},
        )
        self.messages.append(msg)
        self.updated_at = datetime.now().isoformat()
        return msg

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "id": self.id,
            "title": self.title,
            "messages": [asdict(m) for m in self.messages],
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "Conversation":
        """Create from dictionary."""
        messages = [
            ConversationMessage(**m) for m in data.get("messages", [])
        ]
        return cls(
            id=data["id"],
            title=data.get("title", ""),
            messages=messages,
            created_at=data.get("created_at", ""),
            updated_at=data.get("updated_at", ""),
            metadata=data.get("metadata", {}),
        )


class SessionStore:
    """Manages conversation persistence.

    Stores conversations as JSON files in a directory.
    Provides load, save, list, and delete operations.

    Usage:
        store = SessionStore(Path("~/.cleave/sessions"))

        # Create new conversation
        conv = store.create("My Chat")
        conv.add_message("user", "Hello")
        store.save(conv)

        # Load existing
        conv = store.load("conv-123")

        # List all
        for conv in store.list_conversations():
            print(conv.title)
    """

    def __init__(self, session_dir: Path | None = None):
        """Initialize session store.

        Args:
            session_dir: Directory for session files. Defaults to ~/.cleave/sessions.
        """
        self.session_dir = session_dir or Path.home() / ".cleave" / "sessions"
        self._ensure_dir()
        self._counter = 0

    def _ensure_dir(self) -> None:
        """Ensure session directory exists."""
        self.session_dir.mkdir(parents=True, exist_ok=True)

    def _session_path(self, session_id: str) -> Path:
        """Get path for a session file."""
        return self.session_dir / f"{session_id}.json"

    def create(self, title: str = "", metadata: dict | None = None) -> Conversation:
        """Create a new conversation.

        Args:
            title: Optional conversation title.
            metadata: Optional metadata.

        Returns:
            New Conversation instance.
        """
        self._counter += 1
        session_id = f"conv-{datetime.now().strftime('%Y%m%d-%H%M%S')}-{self._counter}"

        return Conversation(
            id=session_id,
            title=title or f"Conversation {self._counter}",
            metadata=metadata or {},
        )

    def save(self, conversation: Conversation) -> None:
        """Save a conversation to disk.

        Args:
            conversation: Conversation to save.
        """
        conversation.updated_at = datetime.now().isoformat()
        path = self._session_path(conversation.id)

        try:
            with path.open("w", encoding="utf-8") as f:
                json.dump(conversation.to_dict(), f, indent=2)
        except Exception as e:
            raise IOError(f"Failed to save conversation: {e}") from e

    def load(self, session_id: str) -> Conversation | None:
        """Load a conversation from disk.

        Args:
            session_id: ID of conversation to load.

        Returns:
            Loaded Conversation, or None if not found.
        """
        path = self._session_path(session_id)
        if not path.exists():
            return None

        try:
            with path.open("r", encoding="utf-8") as f:
                data = json.load(f)
            return Conversation.from_dict(data)
        except Exception:
            return None

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

        Args:
            session_id: ID of conversation to delete.

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

    def list_conversations(self) -> Iterator[Conversation]:
        """List all conversations.

        Yields:
            Conversations, most recently updated first.
        """
        sessions = []
        for path in self.session_dir.glob("*.json"):
            conv = self.load(path.stem)
            if conv:
                sessions.append(conv)

        # Sort by updated_at, most recent first
        sessions.sort(key=lambda c: c.updated_at, reverse=True)
        yield from sessions

    def search(self, query: str) -> Iterator[Conversation]:
        """Search conversations by content.

        Args:
            query: Text to search for.

        Yields:
            Matching conversations.
        """
        query_lower = query.lower()
        for conv in self.list_conversations():
            # Search in title
            if query_lower in conv.title.lower():
                yield conv
                continue

            # Search in messages
            for msg in conv.messages:
                if query_lower in msg.content.lower():
                    yield conv
                    break

    def get_recent(self, limit: int = 10) -> list[Conversation]:
        """Get most recent conversations.

        Args:
            limit: Maximum number to return.

        Returns:
            List of recent conversations.
        """
        conversations = list(self.list_conversations())
        return conversations[:limit]


def get_default_session_dir() -> Path:
    """Get the default session storage directory."""
    return Path.home() / ".cleave" / "sessions"
