"""Anthropic integration for Baponi sandbox execution.

Usage with tool definitions::

    from baponi.anthropic import code_sandbox_tool, handle_tool_call

    response = client.messages.create(tools=[code_sandbox_tool], messages=[...])
    for block in response.content:
        if block.type == "tool_use":
            result = handle_tool_call(block.name, block.input)

Usage with tool_runner (anthropic>=0.40)::

    from baponi.anthropic import code_sandbox

    runner = client.beta.messages.tool_runner(tools=[code_sandbox], ...)

For custom configuration::

    from baponi.anthropic import create_code_sandbox

    tool, handler = create_code_sandbox(api_key="sk-...", thread_id="my-session")
"""

from __future__ import annotations

from typing import Any

try:
    import anthropic as _anthropic_module  # noqa: F401
except ImportError as e:
    raise ImportError(
        "anthropic is required for the Anthropic integration. "
        "Install it with: pip install baponi[anthropic]"
    ) from e

from baponi._async_client import AsyncBaponi
from baponi._base import result_to_llm_text
from baponi._client import Baponi

_TOOL_NAME = "code_sandbox"
_TOOL_DESCRIPTION = (
    "Execute code in a secure, isolated sandbox. Supports bash, python, and node.\n\n"
    "Stateless by default. Nothing persists between calls. Pass a thread_id "
    "for persistent state (files, pip packages) across calls.\n\n"
    "Storage volumes mounted at /data/<volume-name>/ if configured.\n\n"
    "Returns stdout, stderr, and exit code."
)

_INPUT_SCHEMA: dict[str, Any] = {
    "type": "object",
    "properties": {
        "code": {
            "type": "string",
            "description": "The code to execute.",
        },
        "language": {
            "type": "string",
            "description": "Programming language: python, bash, node.",
            "default": "python",
        },
        "thread_id": {
            "type": "string",
            "description": (
                "Pass for persistent state across calls (files, pip packages). "
                "Must be unique. Use a descriptive prefix + random suffix "
                "(e.g. data-analysis-x8k2m9p4z1) or a UUID."
            ),
        },
        "timeout": {
            "type": "integer",
            "description": "Execution timeout in seconds. Increase for long-running tasks, max 3600.",
            "default": 30,
        },
        "metadata": {
            "type": "object",
            "description": "Key-value metadata for audit trail. Max 10 keys.",
            "additionalProperties": {"type": "string"},
        },
        "env_vars": {
            "type": "object",
            "description": "Environment variables to set. Keys must be uppercase (e.g. MY_VAR).",
            "additionalProperties": {"type": "string"},
        },
    },
    "required": ["code"],
}

# Tool definition dict for client.messages.create(tools=[...])
code_sandbox_tool: dict[str, Any] = {
    "name": _TOOL_NAME,
    "description": _TOOL_DESCRIPTION,
    "input_schema": _INPUT_SCHEMA,
}


class _SandboxHandler:
    """Internal handler that manages client lifecycle and defaults."""

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        thread_id: str | None = None,
        timeout: int = 30,
        metadata: dict[str, str] | None = None,
        env_vars: dict[str, str] | None = None,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url
        self._default_thread_id = thread_id
        self._default_timeout = timeout
        self._default_metadata = metadata
        self._default_env_vars = env_vars
        self._sync_client: Baponi | None = None
        self._async_client: AsyncBaponi | None = None

    def _get_sync_client(self) -> Baponi:
        if self._sync_client is None:
            kwargs: dict[str, Any] = {}
            if self._api_key is not None:
                kwargs["api_key"] = self._api_key
            if self._base_url is not None:
                kwargs["base_url"] = self._base_url
            self._sync_client = Baponi(**kwargs, _client_id="baponi-python/anthropic")
        return self._sync_client

    def _get_async_client(self) -> AsyncBaponi:
        if self._async_client is None:
            kwargs: dict[str, Any] = {}
            if self._api_key is not None:
                kwargs["api_key"] = self._api_key
            if self._base_url is not None:
                kwargs["base_url"] = self._base_url
            self._async_client = AsyncBaponi(**kwargs, _client_id="baponi-python/anthropic")
        return self._async_client

    def _resolve_params(
        self, input_data: dict[str, Any]
    ) -> tuple[str, str, str | None, int, dict[str, str] | None, dict[str, str] | None]:
        """Resolve per-call params merged with handler defaults."""
        code = input_data["code"]
        language = input_data.get("language", "python")
        thread_id = input_data.get("thread_id")
        if thread_id is None:
            thread_id = self._default_thread_id
        timeout = input_data.get("timeout")
        if timeout is None:
            timeout = self._default_timeout
        metadata = input_data.get("metadata")
        if metadata is not None and self._default_metadata:
            metadata = {**self._default_metadata, **metadata}
        elif self._default_metadata:
            metadata = self._default_metadata
        env_vars = input_data.get("env_vars")
        if env_vars is not None and self._default_env_vars:
            env_vars = {**self._default_env_vars, **env_vars}
        elif self._default_env_vars:
            env_vars = self._default_env_vars
        return code, language, thread_id, timeout, metadata, env_vars

    def handle_sync(self, name: str, input_data: dict[str, Any]) -> str:
        if name != _TOOL_NAME:
            raise ValueError(f"Unknown tool: {name!r}")
        client = self._get_sync_client()
        code, language, thread_id, timeout, metadata, env_vars = self._resolve_params(
            input_data
        )
        result = client.execute(
            code,
            language=language,
            timeout=timeout,
            thread_id=thread_id,
            metadata=metadata,
            env_vars=env_vars,
        )
        return result_to_llm_text(result)

    async def handle_async(self, name: str, input_data: dict[str, Any]) -> str:
        if name != _TOOL_NAME:
            raise ValueError(f"Unknown tool: {name!r}")
        client = self._get_async_client()
        code, language, thread_id, timeout, metadata, env_vars = self._resolve_params(
            input_data
        )
        result = await client.execute(
            code,
            language=language,
            timeout=timeout,
            thread_id=thread_id,
            metadata=metadata,
            env_vars=env_vars,
        )
        return result_to_llm_text(result)


# Default handler instance
_default_handler = _SandboxHandler()


def handle_tool_call(name: str, input_data: dict[str, Any]) -> str:
    """Handle a tool_use block from an Anthropic messages response.

    Args:
        name: The tool name from block.name.
        input_data: The input dict from block.input.

    Returns:
        String result for the tool_result content block.
    """
    return _default_handler.handle_sync(name, input_data)


async def async_handle_tool_call(name: str, input_data: dict[str, Any]) -> str:
    """Async version of handle_tool_call."""
    return await _default_handler.handle_async(name, input_data)


# @beta_tool for tool_runner support
try:
    from anthropic import beta_tool

    @beta_tool(name=_TOOL_NAME, description=_TOOL_DESCRIPTION)
    def code_sandbox(
        code: str,
        language: str = "python",
        thread_id: str | None = None,
        timeout: int | None = None,
        metadata: dict[str, str] | None = None,
        env_vars: dict[str, str] | None = None,
    ) -> str:
        """Execute code in a sandbox."""
        input_data: dict[str, Any] = {
            "code": code,
            "language": language,
        }
        if thread_id is not None:
            input_data["thread_id"] = thread_id
        if timeout is not None:
            input_data["timeout"] = timeout
        if metadata is not None:
            input_data["metadata"] = metadata
        if env_vars is not None:
            input_data["env_vars"] = env_vars
        return _default_handler.handle_sync(_TOOL_NAME, input_data)
except (ImportError, AttributeError):
    # beta_tool requires anthropic>=0.40
    code_sandbox = None  # type: ignore[assignment]

    import warnings

    warnings.warn(
        "baponi.anthropic.code_sandbox requires anthropic>=0.40 (beta_tool support). "
        "Upgrade with: pip install 'anthropic>=0.40'. "
        "You can still use code_sandbox_tool + handle_tool_call as a workaround.",
        stacklevel=2,
    )


def create_code_sandbox(
    *,
    api_key: str | None = None,
    base_url: str | None = None,
    thread_id: str | None = None,
    timeout: int = 30,
    metadata: dict[str, str] | None = None,
    env_vars: dict[str, str] | None = None,
) -> tuple[dict[str, Any], Any]:
    """Create a configured code sandbox for Anthropic.

    Args:
        api_key: Baponi API key. Falls back to BAPONI_API_KEY env var.
        base_url: Custom API base URL (for self-hosted deployments).
        thread_id: Default thread_id for persistent state across calls.
        timeout: Default execution timeout in seconds.
        metadata: Default metadata merged into every call.
        env_vars: Default environment variables merged into every call.

    Returns:
        Tuple of (tool_definition, handle_tool_call_fn).
    """
    handler = _SandboxHandler(
        api_key=api_key,
        base_url=base_url,
        thread_id=thread_id,
        timeout=timeout,
        metadata=metadata,
        env_vars=env_vars,
    )
    return code_sandbox_tool, handler.handle_sync
