"""Session and client state management for ClipRoom server."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass, field

from cliproom.protocol import ProtocolValidationError


@dataclass(slots=True, eq=False)
class ClientConnection:
    """A connected client bound to a stream writer."""

    client_name: str
    writer: asyncio.StreamWriter
    lock: asyncio.Lock = field(default_factory=asyncio.Lock)

    async def send(self, payload: bytes) -> None:
        """Write a pre-encoded CRP message to this client."""
        async with self.lock:
            self.writer.write(payload)
            await self.writer.drain()


@dataclass(slots=True)
class SessionState:
    """A logical room identified by session name."""

    name: str
    auth_hash: str
    clients: set[ClientConnection] = field(default_factory=set)

    def validate_auth(self, auth_hash: str) -> None:
        if self.auth_hash != auth_hash:
            raise ProtocolValidationError("invalid_auth")


class SessionManager:
    """Owns all active sessions."""

    def __init__(
        self,
        *,
        fixed_session: str | None = None,
        fixed_auth_hash: str | None = None,
    ) -> None:
        self._sessions: dict[str, SessionState] = {}
        self._lock = asyncio.Lock()
        self._fixed_session = fixed_session
        self._fixed_auth_hash = fixed_auth_hash or ""

    async def join(
        self, session_name: str, auth_hash: str, connection: ClientConnection
    ) -> SessionState:
        """Join or create a session and register the client."""
        async with self._lock:
            if self._fixed_session and session_name != self._fixed_session:
                raise ProtocolValidationError("invalid_session")

            if self._fixed_session:
                session = self._sessions.get(self._fixed_session)
                if session is None:
                    session = SessionState(
                        name=self._fixed_session,
                        auth_hash=self._fixed_auth_hash,
                    )
                    self._sessions[self._fixed_session] = session
            else:
                session = self._sessions.get(session_name)
                if session is None:
                    session = SessionState(name=session_name, auth_hash=auth_hash)
                    self._sessions[session_name] = session

            session.validate_auth(auth_hash)
            session.clients.add(connection)
            return session

    async def leave(self, session_name: str, connection: ClientConnection) -> None:
        """Remove a client from a session and cleanup empty sessions."""
        async with self._lock:
            session = self._sessions.get(session_name)
            if session is None:
                return
            session.clients.discard(connection)
            if not session.clients:
                self._sessions.pop(session_name, None)

    async def list_clients(self, session_name: str) -> tuple[ClientConnection, ...]:
        """Return a snapshot of session clients."""
        async with self._lock:
            session = self._sessions.get(session_name)
            if session is None:
                return ()
            return tuple(session.clients)

    async def list_discoverable_sessions(self) -> tuple[tuple[str, bool], ...]:
        """Return discoverable sessions as (name, requires_passphrase)."""
        async with self._lock:
            advertised: list[tuple[str, bool]] = []

            if self._fixed_session:
                advertised.append((self._fixed_session, bool(self._fixed_auth_hash)))

            for name, session in self._sessions.items():
                if self._fixed_session and name == self._fixed_session:
                    continue
                advertised.append((name, bool(session.auth_hash)))

            return tuple(advertised)
