"""Base protocol for container backends."""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Protocol


class SessionNotFoundError(Exception):
    """Session not found."""

    pass


class SessionExistsError(Exception):
    """Session with this name already exists."""

    pass


@dataclass
class Session:
    """Represents a paude session.

    Attributes:
        name: Session name (user-provided or auto-generated).
        status: Session status ("running", "stopped", "error", "pending",
            "degraded"). "degraded" means the container is running but its
            expected proxy is missing or stopped.
        workspace: Local workspace path.
        created_at: ISO timestamp of session creation.
        backend_type: Backend type ("podman" or "openshift").
        container_id: Backend-specific container/pod identifier.
        volume_name: Backend-specific volume/PVC name.
    """

    name: str
    status: str
    workspace: Path
    created_at: str
    backend_type: str
    container_id: str | None = None
    volume_name: str | None = None
    agent: str = "claude"
    provider: str | None = None
    version: str | None = None


@dataclass
class SessionConfig:
    """Configuration for creating a new session.

    Attributes:
        name: Session name (None for auto-generate).
        workspace: Local workspace path.
        image: Container image to use.
        env: Environment variables.
        mounts: Volume mount arguments (Podman-style).
        args: Arguments to pass to Claude.
        workdir: Working directory inside container.
        allowed_domains: List of domains to allow, or None for unrestricted.
        yolo: Enable YOLO mode.
        pvc_size: PVC size for OpenShift (e.g., "10Gi").
        storage_class: Storage class for OpenShift.
        network: Podman network name for proxy setup.
        ports: Ports to expose as (host_port, container_port) tuples.
    """

    name: str | None
    workspace: Path
    image: str
    env: dict[str, str] = field(default_factory=dict)
    mounts: list[str] = field(default_factory=list)
    args: list[str] = field(default_factory=list)
    workdir: str | None = None
    allowed_domains: list[str] | None = None
    yolo: bool = False
    pvc_size: str = "10Gi"
    storage_class: str | None = None
    network: str | None = None
    proxy_image: str | None = None
    credential_timeout: int = 60  # minutes of inactivity before credential removal
    wait_for_ready: bool = True
    agent: str = "claude"
    provider: str | None = None
    gpu: str | None = None
    reuse_volume: bool = False
    ports: list[tuple[int, int]] = field(default_factory=list)
    otel_ports: list[int] = field(default_factory=list)
    otel_endpoint: str | None = None


class Backend(Protocol):
    """Container backend interface.

    All container backends (Podman, OpenShift) must implement this protocol.
    The CLI delegates to the appropriate backend based on configuration.

    Session Lifecycle:
        create_session -> Creates container/StatefulSet + volume/PVC (stopped)
        start_session  -> Starts container/scales to 1, connects
        stop_session   -> Stops container/scales to 0 (preserves volume)
        delete_session -> Removes all resources including volume
        connect_session -> Attaches to running session
        list_sessions  -> Lists all sessions
        sync_session   -> Syncs files between local and remote
    """

    def create_session(self, config: SessionConfig) -> Session:
        """Create a new session (does not start it).

        Creates the container/StatefulSet and volume/PVC but leaves it stopped.

        Args:
            config: Session configuration.

        Returns:
            Session object representing the created session.
        """
        ...

    def delete_session(self, name: str, confirm: bool = False) -> None:
        """Delete a session and all its resources.

        Removes the container/StatefulSet and volume/PVC permanently.

        Args:
            name: Session name.
            confirm: Whether the user has confirmed deletion.

        Raises:
            ValueError: If session not found or confirm=False.
        """
        ...

    def start_session(self, name: str, github_token: str | None = None) -> int:
        """Start a session and connect to it.

        Starts the container/scales to 1 and connects.

        Args:
            name: Session name.
            github_token: Optional GitHub token to inject into the session.

        Returns:
            Exit code from the connected session.
        """
        ...

    def stop_session(self, name: str) -> None:
        """Stop a session (preserves volume).

        Stops the container/scales to 0 but keeps the volume intact.

        Args:
            name: Session name.
        """
        ...

    def connect_session(self, name: str, github_token: str | None = None) -> int:
        """Attach to a running session.

        Args:
            name: Session name.
            github_token: Optional GitHub token to inject into the session.

        Returns:
            Exit code from the attached session.
        """
        ...

    def list_sessions(self) -> list[Session]:
        """List all sessions for current user.

        Returns:
            List of Session objects.
        """
        ...

    def get_session(self, name: str) -> Session | None:
        """Get a session by name.

        Args:
            name: Session name.

        Returns:
            Session object or None if not found.
        """
        ...

    def find_session_for_workspace(self, workspace: Path) -> Session | None:
        """Find a session associated with the given workspace path.

        Args:
            workspace: Local workspace path.

        Returns:
            Session object or None if no session matches the workspace.
        """
        ...

    def get_allowed_domains(self, name: str) -> list[str] | None:
        """Get current allowed domains for a session.

        Args:
            name: Session name.

        Returns:
            List of domains, or None if session has no proxy (unrestricted).
        """
        ...

    def get_proxy_blocked_log(self, name: str) -> str | None:
        """Get raw squid blocked log from the proxy container.

        Returns:
            Raw log content string, empty string if no blocks yet,
            or None if session has no proxy (unrestricted network).

        Raises:
            SessionNotFoundError: If session not found.
            ValueError: If proxy is not running.
        """
        ...

    def update_allowed_domains(self, name: str, domains: list[str]) -> None:
        """Update allowed domains for a session.

        Args:
            name: Session name.
            domains: New list of allowed domains.
        """
        ...

    def start_agent_headless(self, name: str, github_token: str | None = None) -> None:
        """Start the agent in headless mode in a running session.

        Launches the agent in a background tmux session without attaching
        an interactive terminal. If the agent is already running, this is
        a no-op. Failures are logged but not raised.

        Args:
            name: Session name.
            github_token: Optional GitHub PAT to inject as GH_TOKEN.
        """
        ...

    def exec_in_session(self, name: str, command: str) -> tuple[int, str, str]:
        """Execute a command inside a running session's container.

        Args:
            name: Session name.
            command: Shell command to execute.

        Returns:
            Tuple of (return_code, stdout, stderr).

        Raises:
            SessionNotFoundError: If session not found.
            ValueError: If session is not running.
        """
        ...

    def copy_to_session(self, name: str, local_path: str, remote_path: str) -> None:
        """Copy a file or directory from local to a session.

        Args:
            name: Session name.
            local_path: Local file or directory path.
            remote_path: Destination path inside the container.

        Raises:
            SessionNotFoundError: If session not found.
            ValueError: If session is not running.
        """
        ...

    def copy_from_session(self, name: str, remote_path: str, local_path: str) -> None:
        """Copy a file or directory from a session to local.

        Args:
            name: Session name.
            remote_path: Source path inside the container.
            local_path: Local destination path.

        Raises:
            SessionNotFoundError: If session not found.
            ValueError: If session is not running.
        """
        ...
