import asyncio
import os
import difflib
import json
from typing import Type, Optional
from pydantic import BaseModel, Field
from langchain_core.tools import BaseTool
import logging

# Códigos ANSI para colores
COLOR_RED = "\033[91m"
COLOR_GREEN = "\033[92m"
COLOR_RESET = "\033[0m"

logger = logging.getLogger(__name__)

class FileUpdateTool(BaseTool):
    name: str = "file_update_tool"
    description: str = "Actualiza el contenido de un archivo existente. Requiere confirmación si hay cambios."

    class FileUpdateInput(BaseModel):
        path: str = Field(description="La ruta del archivo a actualizar.")
        content: Optional[str] = Field(default=None, description="El nuevo contenido del archivo.")

    args_schema: Type[BaseModel] = FileUpdateInput

    def _validate_workspace_path(self, path: str) -> str:
        """
        Valida que la ruta proporcionada esté dentro del directorio raíz del workspace.
        Previene ataques de Path Traversal (ej: ../../etc/passwd).
        """
        if not path:
            raise ValueError("La ruta no puede estar vacía.")
            
        workspace_root = None
        if self.workspace_context and hasattr(self.workspace_context, 'root_dir'):
            workspace_root = os.path.realpath(self.workspace_context.root_dir)
        else:
            workspace_root = os.path.realpath(os.getcwd())
            
        target_path = os.path.realpath(path)
        
        try:
            common = os.path.commonpath([workspace_root, target_path])
            if common != workspace_root:
                raise PermissionError(
                    f"Acceso denegado: La ruta '{path}' está fuera del directorio de trabajo. "
                    f"Solo se permiten operaciones dentro de '{workspace_root}'."
                )
        except ValueError:
            raise PermissionError(
                f"Acceso denegado: La ruta '{path}' está en un disco diferente al del workspace."
            )
            
        return target_path



    def get_action_description(self, **kwargs) -> str:
        path = kwargs.get("path", "")
        return f"Actualizando archivo completo: {path}"

    def _apply_update(self, path: str, content: str) -> str:
        path = self._validate_workspace_path(path.strip().replace("@", ""))
        try:
            if not os.access(path, os.W_OK):
                raise PermissionError(f"No se tienen permisos de escritura en el archivo '{path}'.")
            print(f"🔄 Actualizando archivo: {path}")
            with open(path, 'w') as f:
                f.write(content)
            return json.dumps({"status": "success", "path": path, "message": f"Archivo '{path}' actualizado exitosamente."})
        except Exception as e:
            logger.error(f"Error al aplicar la actualización en '{path}': {e}", exc_info=True)
            return json.dumps({"status": "error", "path": path, "message": f"Error al aplicar la actualización: {e}"})

    def _run(self, path: str, content: Optional[str] = None, confirm: bool = False) -> str:
        logger.debug(f"FileUpdateTool - Intentando actualizar archivo en ruta '{path}' (confirm={confirm})")
        path = self._validate_workspace_path(path.strip().replace("@", ""))
        if confirm:
            return self._apply_update(path, content)
        try:
            if not os.path.exists(path):
                return f"Error: El archivo '{path}' no existe para actualizar."
            
            with open(path, 'r') as f:
                old_content = f.read()
            
            if content is None:
                return json.dumps({"status": "error", "path": path, "message": "Error: El contenido no puede ser None para la acción 'update'."})
            
            diff = list(difflib.unified_diff(
                old_content.splitlines(keepends=True),
                content.splitlines(keepends=True),
                fromfile=f'a/{path}',
                tofile=f'b/{path}',
            ))
            
            if not diff:
                return json.dumps({"status": "no_changes", "path": path, "message": f"No hay cambios detectados para '{path}'. No se requiere actualización."})
            
            diff_output = "".join(diff)
            return json.dumps({"status": "requires_confirmation", "path": path, "diff": diff_output, "message": f"Se detectaron cambios para '{path}'. Por favor, confirma para aplicar."})
        except FileNotFoundError:
            return json.dumps({"status": "error", "path": path, "message": f"Error: El archivo '{path}' no fue encontrado para actualizar."})
        except PermissionError:
            return json.dumps({"status": "error", "path": path, "message": f"Error de Permisos: No se tienen los permisos necesarios para leer el archivo '{path}'."})
        except Exception as e:
            logger.error(f"Error inesperado en FileUpdateTool al actualizar '{path}': {e}", exc_info=True)
            return json.dumps({"status": "error", "path": path, "message": f"Error inesperado en FileUpdateTool: {e}. Por favor, revisa los logs para más detalles."})
            
    async def _arun(self, path: str, content: Optional[str] = None) -> str:        return await asyncio.to_thread(self._run, path, content)
