"""Sandbox path rewriting layer for MCP tool arguments and responses.

Provides :class:`SandboxPathRewriter` which rewrites file paths in MCP
tool call arguments (host -> sandbox) and in tool call responses
(sandbox -> host).  This enables MCP tools to operate inside a
sandboxed workspace while the host sees paths in its own namespace.

The rewriter uses :class:`~cleveragents.tool.path_mapper.PathMapper`
for the actual bi-directional path translation and applies rewriting
to all string values in nested dicts/lists that look like absolute
file paths under the configured roots.

Usage::

    from cleveragents.mcp.sandbox import SandboxPathRewriter

    rewriter = SandboxPathRewriter(
        host_root="/home/user/project",
        sandbox_root="/workspace",
    )

    # Rewrite arguments before sending to MCP server:
    rewritten_args = rewriter.rewrite_arguments(
        {"file_path": "/home/user/project/src/main.py"}
    )
    # -> {"file_path": "/workspace/src/main.py"}

    # Rewrite response paths from sandbox back to host:
    rewritten_resp = rewriter.rewrite_response(
        {"output_file": "/workspace/build/out.txt"}
    )
    # -> {"output_file": "/home/user/project/build/out.txt"}
"""

from __future__ import annotations

import logging
from typing import Any

from pydantic import BaseModel, ConfigDict, Field

from cleveragents.tool.path_mapper import PathMapper

logger = logging.getLogger(__name__)

__all__ = [
    "SandboxPathRewriter",
    "SandboxPathRewriterConfig",
]


class SandboxPathRewriterConfig(BaseModel):
    """Configuration for sandbox path rewriting.

    Attributes:
        host_root: Absolute path to the host-side workspace root.
        sandbox_root: Absolute path to the sandbox-side workspace root.
        enabled: Whether path rewriting is active.
    """

    host_root: str = Field(..., min_length=1, description="Host workspace root")
    sandbox_root: str = Field(..., min_length=1, description="Sandbox workspace root")
    enabled: bool = Field(default=True, description="Enable path rewriting")

    model_config = ConfigDict(str_strip_whitespace=True)


class SandboxPathRewriter:
    """Bi-directional path rewriter for MCP sandbox isolation.

    Rewrites absolute file paths in tool arguments and responses between
    host and sandbox namespaces using :class:`PathMapper`.

    Parameters
    ----------
    host_root:
        Absolute path to the host-side workspace root.
    sandbox_root:
        Absolute path to the sandbox-side workspace root.
    """

    def __init__(self, host_root: str, sandbox_root: str) -> None:
        self._mapper = PathMapper(host_root=host_root, container_root=sandbox_root)
        self._host_root = host_root
        self._sandbox_root = sandbox_root

    @classmethod
    def from_config(cls, config: SandboxPathRewriterConfig) -> SandboxPathRewriter:
        """Create a rewriter from a configuration object."""
        return cls(host_root=config.host_root, sandbox_root=config.sandbox_root)

    @property
    def host_root(self) -> str:
        """Host workspace root path."""
        return self._host_root

    @property
    def sandbox_root(self) -> str:
        """Sandbox workspace root path."""
        return self._sandbox_root

    def rewrite_arguments(self, arguments: dict[str, Any]) -> dict[str, Any]:
        """Rewrite host paths to sandbox paths in tool arguments.

        Parameters
        ----------
        arguments:
            Tool call arguments dict.

        Returns
        -------
        dict[str, Any]
            Arguments with host paths replaced by sandbox paths.
        """
        return self._rewrite_value(arguments, self._host_to_sandbox)

    def rewrite_response(self, response: dict[str, Any]) -> dict[str, Any]:
        """Rewrite sandbox paths to host paths in tool responses.

        Parameters
        ----------
        response:
            Tool call response dict.

        Returns
        -------
        dict[str, Any]
            Response with sandbox paths replaced by host paths.
        """
        return self._rewrite_value(response, self._sandbox_to_host)

    def _host_to_sandbox(self, path: str) -> str:
        """Map a host path to a sandbox path."""
        return self._mapper.host_to_container(path)

    def _sandbox_to_host(self, path: str) -> str:
        """Map a sandbox path to a host path."""
        return self._mapper.container_to_host(path)

    def _rewrite_value(
        self,
        value: Any,
        rewrite_fn: Any,
    ) -> Any:
        """Recursively rewrite string values that look like paths.

        Parameters
        ----------
        value:
            Any JSON-compatible value (str, dict, list, number, bool, None).
        rewrite_fn:
            Function that maps a single path string.

        Returns
        -------
        Any
            The value with path strings rewritten.
        """
        if isinstance(value, str):
            return self._maybe_rewrite_path(value, rewrite_fn)
        if isinstance(value, dict):
            return {k: self._rewrite_value(v, rewrite_fn) for k, v in value.items()}
        if isinstance(value, list):
            return [self._rewrite_value(item, rewrite_fn) for item in value]
        return value

    @staticmethod
    def _maybe_rewrite_path(
        value: str,
        rewrite_fn: Any,
    ) -> str:
        """Rewrite a string if it looks like an absolute path.

        Only strings starting with ``/`` are considered candidates for
        path rewriting to avoid mutating non-path strings.
        """
        if not value.startswith("/"):
            return value
        return rewrite_fn(value)
