"""Command Runner - Executes allowlisted read-only commands safely."""

import subprocess
import logging
from typing import Optional, Tuple, List
from doctor.security.allowlist import is_command_allowed, get_command_signature


logger = logging.getLogger(__name__)


class CommandExecutionError(Exception):
    """Raised when command execution fails."""
    pass


class CommandNotAllowedError(Exception):
    """Raised when a command is not in the allowlist."""
    pass


def run_command(
    command: List[str],
    timeout: int = 10,
    encoding: str = "utf-8"
) -> Tuple[str, str, int]:
    """
    Execute a read-only command safely.
    
    Args:
        command: List of command parts (e.g., ["ps", "-eo", "pid"]).
        timeout: Maximum execution time in seconds.
        encoding: Output encoding.
        
    Returns:
        Tuple of (stdout, stderr, return_code).
        
    Raises:
        CommandNotAllowedError: If command is not in allowlist.
        CommandExecutionError: If execution fails.
    """
    if not is_command_allowed(command):
        signature = get_command_signature(command)
        logger.error(f"Command not allowed: {signature}")
        raise CommandNotAllowedError(f"Command not in allowlist: {signature}")
    
    signature = get_command_signature(command)
    logger.info(f"Executing allowed command: {signature}")
    
    try:
        result = subprocess.run(
            command,
            capture_output=True,
            timeout=timeout,
            text=True,
            encoding=encoding,
            errors="replace"  # Replace encoding errors instead of failing
        )
        
        stdout = sanitize_output(result.stdout)
        stderr = sanitize_output(result.stderr)
        
        logger.debug(f"Command '{signature}' completed with code {result.returncode}")
        
        return stdout, stderr, result.returncode
        
    except subprocess.TimeoutExpired:
        logger.error(f"Command '{signature}' timed out after {timeout}s")
        raise CommandExecutionError(f"Command timed out: {signature}")
    except Exception as e:
        logger.error(f"Command '{signature}' failed: {str(e)}")
        raise CommandExecutionError(f"Command execution failed: {str(e)}")


def sanitize_output(text: str) -> str:
    """
    Sanitize command output to remove potentially harmful content.
    
    Args:
        text: Raw output text.
        
    Returns:
        Sanitized text.
    """
    if not text:
        return ""
    
    # Remove control characters except newlines and tabs
    sanitized = "".join(
        char if (ord(char) >= 32 or char in "\n\t") else " "
        for char in text
    )
    
    # Limit length to prevent memory issues
    max_length = 100000
    if len(sanitized) > max_length:
        sanitized = sanitized[:max_length] + "\n... (truncated)"
    
    return sanitized
