# ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
from typing import List, Literal, Optional, Union

from camel.interpreters import (
    DockerInterpreter,
    E2BInterpreter,
    InternalPythonInterpreter,
    JupyterKernelInterpreter,
    MicrosandboxInterpreter,
    SubprocessInterpreter,
)
from camel.logger import get_logger
from camel.toolkits import FunctionTool
from camel.toolkits.base import BaseToolkit
from camel.utils import MCPServer

logger = get_logger(__name__)


@MCPServer()
class CodeExecutionToolkit(BaseToolkit):
    r"""A toolkit for code execution.

    Args:
        sandbox (str): The environment type used to execute code.
            (default: `subprocess`)
        verbose (bool): Whether to print the output of the code execution.
            (default: :obj:`False`)
        unsafe_mode (bool):  If `True`, the interpreter runs the code
            by `eval()` without any security check. (default: :obj:`False`)
        import_white_list (Optional[List[str]]): A list of allowed imports.
            (default: :obj:`None`)
        require_confirm (Optional[bool]): Whether to require confirmation
            before executing code. If `None`, local host execution sandboxes
            require confirmation by default. (default: :obj:`None`)
        timeout (Optional[float]): General timeout for toolkit operations.
            (default: :obj:`None`)
        microsandbox_config (Optional[dict]): Configuration for microsandbox
            interpreter. Available keys: 'server_url', 'api_key',
            'namespace', 'sandbox_name', 'timeout'.
            If None, uses default configuration. (default: :obj:`None`)
    """

    def __init__(
        self,
        sandbox: Literal[
            "internal_python",
            "jupyter",
            "docker",
            "subprocess",
            "e2b",
            "microsandbox",
        ] = "subprocess",
        verbose: bool = False,
        unsafe_mode: bool = False,
        import_white_list: Optional[List[str]] = None,
        require_confirm: Optional[bool] = None,
        timeout: Optional[float] = None,
        # Microsandbox configuration dictionary
        microsandbox_config: Optional[dict] = None,
    ) -> None:
        super().__init__(timeout=timeout)
        self.verbose = verbose
        self.unsafe_mode = unsafe_mode
        self.import_white_list = import_white_list or list()
        host_execution_sandboxes = {"internal_python", "subprocess"}
        resolved_require_confirm = (
            sandbox in host_execution_sandboxes
            if require_confirm is None
            else require_confirm
        )

        # Type annotation for interpreter to allow all possible types
        self.interpreter: Union[
            InternalPythonInterpreter,
            JupyterKernelInterpreter,
            DockerInterpreter,
            SubprocessInterpreter,
            E2BInterpreter,
            MicrosandboxInterpreter,
        ]

        if sandbox == "internal_python":
            self.interpreter = InternalPythonInterpreter(
                unsafe_mode=self.unsafe_mode,
                import_white_list=self.import_white_list,
                require_confirm=resolved_require_confirm,
            )
        elif sandbox == "jupyter":
            self.interpreter = JupyterKernelInterpreter(
                require_confirm=resolved_require_confirm,
                print_stdout=self.verbose,
                print_stderr=self.verbose,
            )
        elif sandbox == "docker":
            self.interpreter = DockerInterpreter(
                require_confirm=resolved_require_confirm,
                print_stdout=self.verbose,
                print_stderr=self.verbose,
            )
        elif sandbox == "subprocess":
            self.interpreter = SubprocessInterpreter(
                require_confirm=resolved_require_confirm,
                print_stdout=self.verbose,
                print_stderr=self.verbose,
            )
        elif sandbox == "e2b":
            self.interpreter = E2BInterpreter(
                require_confirm=resolved_require_confirm
            )
        elif sandbox == "microsandbox":
            # Extract parameters with proper types for microsandbox
            config = microsandbox_config or {}

            self.interpreter = MicrosandboxInterpreter(
                require_confirm=resolved_require_confirm,
                server_url=config.get("server_url"),
                api_key=config.get("api_key"),
                namespace=config.get("namespace", "default"),
                sandbox_name=config.get("sandbox_name"),
                timeout=config.get("timeout", 30),
            )
        else:
            raise RuntimeError(
                f"The sandbox type `{sandbox}` is not supported."
            )

    def execute_code(self, code: str, code_type: str = "python") -> str:
        r"""Execute a given code snippet.

        Args:
            code (str): The input code to the Code Interpreter tool call.
            code_type (str): The type of the code to be executed
                (e.g. node.js, python, etc). (default: obj:`python`)

        Returns:
            str: The text output from the Code Interpreter tool call.
        """
        output = self.interpreter.run(code, code_type)
        content = (
            f"Executed the code below:\n```{code_type}\n{code}\n```\n"
            f"> Executed Results:\n{output}"
        )
        if self.verbose:
            print(content)
        return content

    def execute_command(self, command: str) -> Union[str, tuple[str, str]]:
        r"""Execute a command can be used to resolve the dependency of the
        code. Useful if there's dependency issues when you try to execute code.

        Args:
            command (str): The command to execute.

        Returns:
            Union[str, tuple[str, str]]: The output of the command.
        """
        output = self.interpreter.execute_command(command)
        content = (
            f"Executed the command below:\n```sh\n{command}\n```\n"
            f"> Executed Results:\n{output}"
        )
        if self.verbose:
            print(content)
        return content

    def get_tools(self) -> List[FunctionTool]:
        r"""Returns a list of FunctionTool objects representing the
        functions in the toolkit.

        Returns:
            List[FunctionTool]: A list of FunctionTool objects
                representing the functions in the toolkit.
        """
        return [
            FunctionTool(self.execute_code),
            FunctionTool(self.execute_command),
        ]
