"""Managed Claude Code session pool for messaging."""

import asyncio
import uuid

from loguru import logger

from free_claude_code.cli.claude_env import CLAUDE_BINARY_NAME

from .session import ManagedClaudeSession


class ManagedClaudeSessionManager:
    """
    Manages multiple Claude Code sessions for parallel conversation processing.

    Each new conversation gets its own subprocess. Replies to existing
    conversations reuse the same session instance.
    """

    def __init__(
        self,
        workspace_path: str,
        proxy_root_url: str,
        allowed_dirs: list[str] | None = None,
        claude_bin: str = CLAUDE_BINARY_NAME,
        auth_token: str = "",
        *,
        log_raw_cli_diagnostics: bool = False,
        log_messaging_error_details: bool = False,
    ):
        """
        Initialize the session manager.

        Args:
            workspace_path: Working directory for CLI processes
            proxy_root_url: Root URL for the local proxy
            allowed_dirs: Directories the CLI is allowed to access
        """
        self.workspace = workspace_path
        self.proxy_root_url = proxy_root_url
        self.allowed_dirs = allowed_dirs or []
        self.claude_bin = claude_bin
        self.auth_token = auth_token
        self._log_raw_cli_diagnostics = log_raw_cli_diagnostics
        self._log_messaging_error_details = log_messaging_error_details

        self._sessions: dict[str, ManagedClaudeSession] = {}
        self._pending_sessions: dict[str, ManagedClaudeSession] = {}
        self._temp_to_real: dict[str, str] = {}
        self._real_to_temp: dict[str, str] = {}
        self._closing_sessions: set[ManagedClaudeSession] = set()
        self._lock = asyncio.Lock()

    def _session_for_id(self, session_id: str) -> ManagedClaudeSession | None:
        lookup_id = self._temp_to_real.get(session_id, session_id)
        session = self._sessions.get(lookup_id)
        if session is not None:
            return session
        return self._pending_sessions.get(lookup_id)

    def _forget_session(self, session: ManagedClaudeSession) -> None:
        pending_ids = [
            session_id
            for session_id, owned in self._pending_sessions.items()
            if owned is session
        ]
        real_ids = [
            session_id
            for session_id, owned in self._sessions.items()
            if owned is session
        ]
        for session_id in pending_ids:
            self._pending_sessions.pop(session_id, None)
        for real_id in real_ids:
            self._sessions.pop(real_id, None)
            temp_id = self._real_to_temp.pop(real_id, None)
            if temp_id is not None:
                self._temp_to_real.pop(temp_id, None)
        self._closing_sessions.discard(session)

    async def get_or_create_session(
        self, session_id: str | None = None
    ) -> tuple[ManagedClaudeSession, str, bool]:
        """
        Get an existing session or create a new one.

        Returns:
            Tuple of (session instance, session_id, is_new_session)
        """
        async with self._lock:
            if session_id:
                lookup_id = self._temp_to_real.get(session_id, session_id)

                if lookup_id in self._sessions:
                    session = self._sessions[lookup_id]
                    if session in self._closing_sessions:
                        raise RuntimeError("Managed Claude session is closing.")
                    return session, lookup_id, False
                if lookup_id in self._pending_sessions:
                    session = self._pending_sessions[lookup_id]
                    if session in self._closing_sessions:
                        raise RuntimeError("Managed Claude session is closing.")
                    return session, lookup_id, False

            temp_id = session_id if session_id else f"pending_{uuid.uuid4().hex[:8]}"

            new_session = ManagedClaudeSession(
                workspace_path=self.workspace,
                proxy_root_url=self.proxy_root_url,
                allowed_dirs=self.allowed_dirs,
                claude_bin=self.claude_bin,
                auth_token=self.auth_token,
                log_raw_cli_diagnostics=self._log_raw_cli_diagnostics,
            )
            self._pending_sessions[temp_id] = new_session

            return new_session, temp_id, True

    async def register_real_session_id(
        self, temp_id: str, real_session_id: str
    ) -> bool:
        """Register the real session ID from CLI output."""
        async with self._lock:
            session = self._pending_sessions.get(temp_id)
            if session is None:
                logger.warning(f"Temp session {temp_id} not found")
                return False
            if session in self._closing_sessions:
                logger.warning("Cannot register a closing managed Claude session")
                return False
            existing = self._session_for_id(real_session_id)
            if existing is not None and existing is not session:
                logger.warning(
                    "Cannot register managed Claude session: real ID is already owned"
                )
                return False

            self._pending_sessions.pop(temp_id)
            self._sessions[real_session_id] = session
            self._temp_to_real[temp_id] = real_session_id
            self._real_to_temp[real_session_id] = temp_id

            logger.info(f"Registered session: {temp_id} -> {real_session_id}")
            return True

    async def remove_session(self, session_id: str) -> bool:
        """Remove a session from the manager."""
        async with self._lock:
            session = self._session_for_id(session_id)
            if session is None:
                return False
            self._closing_sessions.add(session)
            stopped = await session.stop()
            if not stopped:
                return False
            self._forget_session(session)
            return True

    async def stop_all(self) -> None:
        """Stop all sessions."""
        async with self._lock:
            all_sessions = list(
                dict.fromkeys(
                    [
                        *self._sessions.values(),
                        *self._pending_sessions.values(),
                        *self._closing_sessions,
                    ]
                )
            )
            self._closing_sessions.update(all_sessions)
            failures = 0
            for session in all_sessions:
                try:
                    stopped = await session.stop()
                except Exception as e:
                    stopped = False
                    if self._log_messaging_error_details:
                        logger.error(
                            "Error stopping session: {}: {}",
                            type(e).__name__,
                            e,
                        )
                    else:
                        logger.error(
                            "Error stopping session: exc_type={}",
                            type(e).__name__,
                        )
                if stopped:
                    self._forget_session(session)
                else:
                    failures += 1

            if failures:
                raise RuntimeError(
                    f"Managed Claude session shutdown failures: {failures}."
                )
            logger.info("All sessions stopped")

    def get_stats(self) -> dict:
        """Get session statistics."""
        return {
            "active_sessions": len(self._sessions),
            "pending_sessions": len(self._pending_sessions),
            "busy_count": sum(1 for s in self._sessions.values() if s.is_busy),
        }
