"""Sandbox creation, concurrency control, and lifecycle helpers."""

from __future__ import annotations

import logging
import threading
from typing import Any, Optional

from .config import get_config

logger: logging.Logger = logging.getLogger(__name__)


class SandboxBusyError(Exception):
    pass


class SandboxManager:
    _instance: Optional['SandboxManager'] = None
    _lock: threading.Lock = threading.Lock()
    
    @classmethod
    def get_instance(cls) -> 'SandboxManager':
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = cls()
        return cls._instance
    
    def __init__(self) -> None:
        cfg = get_config()
        
        self.max_concurrent: int = cfg.max_workers

        self._active_sandboxes: dict[str, Any] = {}
        self._lock = threading.Lock()
        self._semaphore = threading.Semaphore(self.max_concurrent)
        self._stats = {
            "total_created": 0,
            "total_destroyed": 0,
            "current_active": 0,
            "max_concurrent": self.max_concurrent,
        }
        
        logger.info(f"Sandbox manager started: max_concurrent={self.max_concurrent}")
    
    def create(
        self,
        sandbox_timeout: int,
        template_id: Optional[str] = None,
        cpu_count: Optional[int] = None,
        memory_mb: Optional[int] = None,
    ) -> Any:
        acquired = self._semaphore.acquire(timeout=60)
        if not acquired:
            logger.warning("Timed out acquiring sandbox: concurrency limit reached, retry later")
            raise SandboxBusyError("Sandbox is busy, please retry later")
        
        sandbox = None
        success = False
        try:
            from ppio_sandbox.code_interpreter import Sandbox
            create_kwargs: dict[str, Any] = {"timeout": sandbox_timeout}
            if isinstance(cpu_count, int) and cpu_count > 0:
                create_kwargs["cpu_count"] = cpu_count
            if isinstance(memory_mb, int) and memory_mb > 0:
                create_kwargs["memory_mb"] = memory_mb

            try:
                if template_id:
                    sandbox = Sandbox.create(template_id, **create_kwargs)
                    logger.info(
                        "Creating sandbox: template=%s, timeout=%ss, cpu=%s, mem_mb=%s",
                        template_id,
                        sandbox_timeout,
                        create_kwargs.get("cpu_count", "default"),
                        create_kwargs.get("memory_mb", "default"),
                    )
                else:
                    sandbox = Sandbox.create(**create_kwargs)
                    logger.info(
                        "Creating sandbox: no-template, timeout=%ss, cpu=%s, mem_mb=%s",
                        sandbox_timeout,
                        create_kwargs.get("cpu_count", "default"),
                        create_kwargs.get("memory_mb", "default"),
                    )
            except TypeError:
                if template_id:
                    sandbox = Sandbox.create(template_id, timeout=sandbox_timeout)
                    logger.info(
                        "Creating sandbox (fallback): template=%s, timeout=%ss",
                        template_id,
                        sandbox_timeout,
                    )
                else:
                    sandbox = Sandbox.create(timeout=sandbox_timeout)
                    logger.info(
                        "Creating sandbox (fallback): no-template, timeout=%ss",
                        sandbox_timeout,
                    )
            sandbox_id = getattr(sandbox, 'id', str(id(sandbox)))
            with self._lock:
                self._active_sandboxes[sandbox_id] = sandbox
                self._stats["total_created"] += 1
                self._stats["current_active"] = len(self._active_sandboxes)
            
            logger.info(f"Sandbox created: {sandbox_id}")
            success = True
            return sandbox
            
        except Exception as e:
            logger.error(f"Failed to create sandbox: {e}")
            raise RuntimeError(f"Failed to create sandbox: {e}")
        finally:
            if not success:
                if sandbox:
                    try:
                        sandbox.kill()
                    except Exception:
                        pass
                self._semaphore.release()
    
    def destroy(self, sandbox: Any) -> None:
        sandbox_id = getattr(sandbox, 'id', str(id(sandbox)))
        
        try:
            if hasattr(sandbox, 'close'):
                sandbox.close()
            elif hasattr(sandbox, 'kill'):
                sandbox.kill()
        except Exception as e:
            logger.warning(f"Sandbox destroy exception: {e}")
        
        with self._lock:
            self._active_sandboxes.pop(sandbox_id, None)
            self._stats["total_destroyed"] += 1
            self._stats["current_active"] = len(self._active_sandboxes)
        
        self._semaphore.release()
        
        logger.info(f"Sandbox destroyed: {sandbox_id}")
    
    def get_stats(self) -> dict:
        with self._lock:
            return dict(self._stats)
    
    def shutdown(self) -> None:
        with self._lock:
            sandboxes = list(self._active_sandboxes.values())
        
        for sandbox in sandboxes:
            try:
                self.destroy(sandbox)
            except Exception as e:
                logger.warning(f"Sandbox shutdown exception: {e}")
        
        logger.info("Sandbox manager stopped")

def create_sandbox(
    sandbox_timeout: int,
    template_id: Optional[str] = None,
    cpu_count: Optional[int] = None,
    memory_mb: Optional[int] = None,
) -> Any:
    return SandboxManager.get_instance().create(
        sandbox_timeout=sandbox_timeout,
        template_id=template_id,
        cpu_count=cpu_count,
        memory_mb=memory_mb,
    )


def destroy_sandbox(sandbox: Any) -> None:
    SandboxManager.get_instance().destroy(sandbox)


def get_sandbox_stats() -> dict:
    return SandboxManager.get_instance().get_stats()