"""Agent session management — memory between requests.

Maintains compressed conversation history per project, allowing the proxy
to avoid re-sending full context each time. Sessions are keyed by
(project_id, session_id) and stored in memory with optional persistence.
"""

from __future__ import annotations

import time
import uuid
from dataclasses import dataclass, field

import structlog

logger = structlog.get_logger()


@dataclass
class AgentSession:
    """Stateful session for an agent working on a project."""

    session_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
    project_id: str = ""
    created_at: float = field(default_factory=time.time)
    last_active: float = field(default_factory=time.time)

    # Compressed conversation history
    compressed_history: list[dict] = field(default_factory=list)

    # Active working set
    active_files: list[str] = field(default_factory=list)
    pending_tasks: list[str] = field(default_factory=list)

    # Token tracking
    total_tokens_sent: int = 0
    total_tokens_received: int = 0

    def touch(self) -> None:
        """Update last_active timestamp."""
        self.last_active = time.time()

    def add_exchange(self, user_msg: str, assistant_msg: str) -> None:
        """Record a compressed user/assistant exchange."""
        self.compressed_history.append({
            "role": "user",
            "content": user_msg[:500],  # truncate for storage
            "ts": time.time(),
        })
        self.compressed_history.append({
            "role": "assistant",
            "content": assistant_msg[:500],
            "ts": time.time(),
        })
        self.touch()

        # Keep history bounded
        if len(self.compressed_history) > 100:
            self.compressed_history = self.compressed_history[-60:]

    @property
    def age_seconds(self) -> float:
        return time.time() - self.created_at

    @property
    def idle_seconds(self) -> float:
        return time.time() - self.last_active


class SessionStore:
    """In-memory session store with TTL-based expiry.

    Parameters
    ----------
    max_idle_seconds : float
        Sessions inactive for longer than this are evicted.
    max_sessions : int
        Maximum number of concurrent sessions.
    """

    def __init__(
        self,
        max_idle_seconds: float = 3600,
        max_sessions: int = 100,
    ):
        self.max_idle_seconds = max_idle_seconds
        self.max_sessions = max_sessions
        self._sessions: dict[str, AgentSession] = {}

    def get_or_create(
        self,
        session_id: str = "",
        project_id: str = "",
    ) -> AgentSession:
        """Get an existing session or create a new one."""
        self._evict_expired()

        if session_id and session_id in self._sessions:
            session = self._sessions[session_id]
            session.touch()
            return session

        session = AgentSession(
            session_id=session_id or uuid.uuid4().hex[:12],
            project_id=project_id,
        )
        self._sessions[session.session_id] = session
        logger.info("session_created", session_id=session.session_id,
                     project_id=project_id)
        return session

    def get(self, session_id: str) -> AgentSession | None:
        """Get session by ID, or None if not found/expired."""
        session = self._sessions.get(session_id)
        if session and session.idle_seconds <= self.max_idle_seconds:
            return session
        return None

    def delete(self, session_id: str) -> bool:
        """Delete a session."""
        if session_id in self._sessions:
            del self._sessions[session_id]
            return True
        return False

    def list_sessions(self) -> list[AgentSession]:
        """List all active sessions."""
        self._evict_expired()
        return list(self._sessions.values())

    def _evict_expired(self) -> None:
        """Remove sessions that have been idle too long or exceed max count."""
        expired = [
            sid for sid, s in self._sessions.items()
            if s.idle_seconds > self.max_idle_seconds
        ]
        for sid in expired:
            del self._sessions[sid]

        # If still over limit, remove oldest
        if len(self._sessions) > self.max_sessions:
            by_age = sorted(self._sessions.items(), key=lambda x: x[1].last_active)
            for sid, _ in by_age[:len(self._sessions) - self.max_sessions]:
                del self._sessions[sid]
