# app/xulcan/tools/executors/sandbox/docker.py

import logging
import asyncio
import base64
from typing import Dict, Any, Optional

import docker
from docker.errors import DockerException, NotFound

from xulcan.tools.executors.sandbox.provider import IsolationProvider

logger = logging.getLogger("xulcan.tools.sandbox.docker")

class DockerProvider(IsolationProvider):
    """
    Implementación Stateless de IsolationProvider usando Docker.
    No usa diccionarios en memoria. La fuente de la verdad es el Docker Daemon.
    """

    def __init__(self, image: str = "python:3.11-slim") -> None:
        try:
            self.client = docker.from_env()
            self.image = image
            self.prefix = "xulcan_sandbox_"
            logger.info(f"🐳 DockerProvider (Stateless) inicializado. Imagen: {self.image}")
        except DockerException as e:
            logger.critical(f"❌ No se pudo conectar al daemon de Docker: {e}")
            raise

    def _get_container_name(self, session_id: str) -> str:
        return f"{self.prefix}{session_id}"

    async def is_active(self, session_id: str) -> bool:
        """Consulta al daemon de Docker si el contenedor existe y está corriendo."""
        name = self._get_container_name(session_id)
        def _check():
            try:
                container = self.client.containers.get(name)
                return container.status == "running"
            except NotFound:
                return False
        return await asyncio.to_thread(_check)

    async def start_session(self, session_id: str, workspace_path: Optional[str] = None) -> None:
        if await self.is_active(session_id):
            return

        name = self._get_container_name(session_id)
        
        def _start():
            volumes = {}
            if workspace_path:
                volumes[workspace_path] = {'bind': '/workspace', 'mode': 'rw'}
            
            # El truco maestro: 'tail -f /dev/null' consume 0% CPU y mantiene la máquina viva
            self.client.containers.run(
                self.image,
                command="tail -f /dev/null",
                detach=True,
                working_dir="/workspace",
                volumes=volumes,
                name=name,
                auto_remove=True,
                network_mode="bridge"
            )

        try:
            await asyncio.to_thread(_start)
            logger.info(f"🟢 Contenedor '{name}' encendido.")
        except docker.errors.APIError as e:
            # Si da error 409, significa que otro hilo milisegundos más rápido ya lo encendió.
            if e.response is not None and e.response.status_code == 409:
                logger.debug(f"⚡ Contenedor '{name}' ya fue encendido por otro proceso en paralelo.")
            else:
                logger.error(f"❌ Error arrancando sesión {session_id}: {e}")
                raise
        except Exception as e:
            logger.error(f"❌ Error arrancando sesión {session_id}: {e}")
            raise

    async def execute_command(self, session_id: str, command: str, timeout: int = 30) -> Dict[str, Any]:
        name = self._get_container_name(session_id)

        def _exec():
            try:
                container = self.client.containers.get(name)
            except NotFound:
                raise RuntimeError(f"La sesión {session_id} no está activa en Docker.")

            # Demux separa correctamente stdout de stderr a nivel binario
            exit_code, output = container.exec_run(
                cmd=["bash", "-c", command],
                demux=True
            )
            stdout = output[0].decode("utf-8") if output[0] else ""
            stderr = output[1].decode("utf-8") if output[1] else ""
            return {"exit_code": exit_code, "stdout": stdout, "stderr": stderr}

        return await asyncio.to_thread(_exec)

    async def read_file(self, session_id: str, file_path: str) -> str:
        result = await self.execute_command(session_id, f"cat {file_path}")
        if result["exit_code"] != 0:
            raise FileNotFoundError(f"Error leyendo {file_path}: {result['stderr']}")
        return result["stdout"]

    async def write_file(self, session_id: str, file_path: str, content: str) -> None:
        """Escritura a prueba de balas usando Base64 para evitar errores de comillas en bash."""
        encoded = base64.b64encode(content.encode("utf-8")).decode("utf-8")
        command = f"echo '{encoded}' | base64 -d > {file_path}"
        
        result = await self.execute_command(session_id, command)
        if result["exit_code"] != 0:
            raise IOError(f"Error escribiendo {file_path}: {result['stderr']}")

    async def terminate_session(self, session_id: str) -> None:
        name = self._get_container_name(session_id)

        def _stop():
            try:
                container = self.client.containers.get(name)
                container.stop(timeout=2) # auto_remove=True se encargará de borrarlo
            except NotFound:
                pass 

        await asyncio.to_thread(_stop)
        logger.info(f"🛑 Sesión {session_id} terminada y destruida.")