"""Discord session manager.

Manages per-channel conversation sessions for Discord bot interactions.
Sessions store a bounded history of messages so the agent has conversational
context when responding.

Session scoping rules:
- DM channels: sessions are scoped to (channel_id, user_id) — each DM
  thread has its own session.
- Guild channels: sessions are scoped to channel_id only — all users in
  a mapped channel share one agent context.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass
class ConversationTurn:
    """A single turn in a Discord conversation history."""

    role: str  # "user" or "assistant"
    content: str
    user_id: str = ""
    username: str = ""


@dataclass
class DiscordSession:
    """Conversation session for a Discord channel."""

    session_key: str
    channel_id: str
    # user_id is set for DM sessions; empty for guild channel sessions
    user_id: str
    history: list[ConversationTurn] = field(default_factory=list)
    history_limit: int = 10

    def add_turn(self, role: str, content: str, user_id: str = "", username: str = "") -> None:
        """Append a turn and evict oldest entries beyond history_limit."""
        self.history.append(
            ConversationTurn(role=role, content=content, user_id=user_id, username=username)
        )
        if len(self.history) > self.history_limit:
            self.history = self.history[-self.history_limit :]

    def get_history(self) -> list[dict[str, Any]]:
        """Return history as a list of plain dicts for JSON serialisation."""
        return [
            {
                "role": t.role,
                "content": t.content,
                "user_id": t.user_id,
                "username": t.username,
            }
            for t in self.history
        ]

    def clear(self) -> None:
        """Reset the conversation history."""
        self.history = []


class SessionManager:
    """In-memory Discord session store.

    Maintains one DiscordSession per (scope_key). The scope key is:
    - ``"dm:{channel_id}:{user_id}"`` for DM channels
    - ``"guild:{channel_id}"`` for guild channels

    The SessionManager is intentionally in-memory — Discord sessions are
    ephemeral conversation contexts and do not need to survive server
    restarts. The persistent record of each turn is the ``discord_messages``
    DB table.
    """

    def __init__(self) -> None:
        self._sessions: dict[str, DiscordSession] = {}

    def get_or_create_session(
        self,
        channel_id: str,
        user_id: str,
        is_dm: bool = False,
        history_limit: int = 10,
    ) -> DiscordSession:
        """Return the session for this channel/user, creating it if needed.

        Args:
            channel_id: Discord channel snowflake ID.
            user_id: Discord user snowflake ID. Only used for DM sessions.
            is_dm: True if this is a DM channel.
            history_limit: Maximum number of turns to retain in history.

        Returns:
            Existing or newly created DiscordSession.
        """
        if is_dm:
            key = f"dm:{channel_id}:{user_id}"
            scoped_user_id = user_id
        else:
            key = f"guild:{channel_id}"
            scoped_user_id = ""

        if key not in self._sessions:
            self._sessions[key] = DiscordSession(
                session_key=key,
                channel_id=channel_id,
                user_id=scoped_user_id,
                history_limit=history_limit,
            )
        else:
            # Update history_limit in case config changed
            self._sessions[key].history_limit = history_limit

        return self._sessions[key]

    def get_session(self, session_key: str) -> DiscordSession | None:
        """Return a session by key, or None if it does not exist."""
        return self._sessions.get(session_key)

    def clear_session(self, session_key: str) -> bool:
        """Clear history for a session. Returns True if the session existed."""
        session = self._sessions.get(session_key)
        if session is None:
            return False
        session.clear()
        return True

    def drop_session(self, session_key: str) -> bool:
        """Remove a session entirely. Returns True if it existed."""
        if session_key in self._sessions:
            del self._sessions[session_key]
            return True
        return False

    def all_sessions(self) -> list[dict[str, Any]]:
        """Return a summary of all active sessions."""
        return [
            {
                "session_key": s.session_key,
                "channel_id": s.channel_id,
                "user_id": s.user_id,
                "history_length": len(s.history),
                "history_limit": s.history_limit,
            }
            for s in self._sessions.values()
        ]
