import logging
import asyncio
import uuid
from typing import Dict, List, Any, Optional

from aworld.sandbox.api.local.sandbox_api import LocalSandboxApi
from aworld.sandbox.models import SandboxStatus, SandboxEnvType, SandboxInfo
from aworld.sandbox.run.mcp_servers import McpServers
from aworld.sandbox.common import BaseSandbox


class LocalSandbox(BaseSandbox, LocalSandboxApi):
    """
    Local sandbox implementation that runs in the local environment.
    This sandbox runs processes and MCP servers directly on the local machine.
    """

    def __init__(
            self,
            sandbox_id: Optional[str] = None,
            metadata: Optional[Dict[str, str]] = None,
            timeout: Optional[int] = None,
            mcp_servers: Optional[List[str]] = None,
            mcp_config: Optional[Any] = None,
            **kwargs
    ):
        """
        Initialize a new LocalSandbox instance.
        
        Args:
            sandbox_id: Unique identifier for the sandbox. If None, one will be generated.
            metadata: Additional metadata for the sandbox.
            timeout: Timeout for sandbox operations.
            mcp_servers: List of MCP servers to use.
            mcp_config: Configuration for MCP servers.
            **kwargs: Additional parameters for specific sandbox types.
        """
        super().__init__(
            sandbox_id=sandbox_id,
            env_type=SandboxEnvType.LOCAL,
            metadata=metadata,
            timeout=timeout,
            mcp_servers=mcp_servers,
            mcp_config=mcp_config
        )

        if sandbox_id:
            if not self._metadata:
                return self
            else:
                raise ValueError("sandbox_id is not exist")

        # Initialize properties
        self._status = SandboxStatus.INIT
        self._timeout = timeout or self.default_sandbox_timeout
        self._metadata = metadata or {}
        self._env_type = SandboxEnvType.LOCAL
        self._mcp_servers = mcp_servers
        self._mcp_config = mcp_config
        
        # Ensure sandbox_id has a value in all cases
        self._sandbox_id = sandbox_id or str(uuid.uuid4())

        # If no sandbox_id provided, create a new sandbox
        if not sandbox_id:
            response = self._create_sandbox(
                env_type=self._env_type,
                env_config=None,
                mcp_servers=mcp_servers,
                mcp_config=mcp_config,
            )
            
            if not response:
                self._status = SandboxStatus.ERROR
                # If creation fails, keep the generated UUID as the ID
                logging.warning(f"Failed to create sandbox, using generated ID: {self._sandbox_id}")
            else:
                self._sandbox_id = response.sandbox_id
                self._status = SandboxStatus.RUNNING
                self._metadata = {
                    "status": getattr(response, 'status', None),
                    "mcp_config": getattr(response, 'mcp_config', None),
                    "env_type": getattr(response, 'env_type', None),
                }
                self._mcp_config = getattr(response, 'mcp_config', None)
            
        # Initialize McpServers with a reference to this sandbox instance
        self._mcpservers = McpServers(
            mcp_servers,
            self._mcp_config,
            sandbox=self
        )

    async def remove(self) -> None:
        """
        Remove sandbox.
        """
        await self._remove_sandbox(
            sandbox_id=self.sandbox_id,
            metadata=self._metadata,
            env_type=self._env_type
        )
        
    async def cleanup(self) -> None:
        """
        Clean up Sandbox resources, including MCP server connections
        """
        try:
            if hasattr(self, '_mcpservers') and self._mcpservers:
                await self._mcpservers.cleanup()
                logging.info(f"Cleaned up MCP servers for sandbox {self.sandbox_id}")
        except Exception as e:
            logging.warning(f"Failed to cleanup MCP servers: {e}")
        
        # Call the original remove method
        try:
            await self.remove()
        except Exception as e:
            logging.warning(f"Failed to remove sandbox: {e}")
            
    def __del__(self):
        """
        Ensure resources are cleaned up when the object is garbage collected
        """
        try:
            # Handle the case where an event loop already exists
            try:
                loop = asyncio.get_running_loop()
                logging.warning("Cannot clean up sandbox in __del__ when event loop is already running")
                return
            except RuntimeError:
                # No running event loop, create a new one
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)
                loop.run_until_complete(self.cleanup())
                loop.close()
        except Exception as e:
            logging.warning(f"Failed to cleanup sandbox resources during garbage collection: {e}") 