# ABOUTME: Base utilities for MCP tool implementation.
# ABOUTME: Provides error handling, response formatting, and logging helpers.
"""Base utilities for MCP tool implementation.

This module provides common utilities used across all Fabric MCP tools
including error handling, response formatting, and logging helpers.
"""

import logging
from typing import Any, Dict, Callable
from functools import wraps

from ..client.exceptions import (
    FabricAPIError,
    FabricAuthError,
    FabricConnectionError,
    FabricError,
    FabricItemNotFoundError,
    FabricLivyError,
    FabricLivyTimeoutError,
    FabricRateLimitError,
    FabricValidationError,
    FabricWorkspaceNotFoundError,
)

logger = logging.getLogger(__name__)

# Map exception types to error codes for consistent error responses
_ERROR_CODE_MAP = {
    FabricValidationError: "VALIDATION_ERROR",
    FabricItemNotFoundError: "ITEM_NOT_FOUND",
    FabricWorkspaceNotFoundError: "WORKSPACE_NOT_FOUND",
    FabricAuthError: "AUTH_ERROR",
    FabricConnectionError: "CONNECTION_ERROR",
    FabricRateLimitError: "RATE_LIMIT",
    FabricLivyTimeoutError: "LIVY_TIMEOUT",
    FabricLivyError: "LIVY_ERROR",
    FabricAPIError: "API_ERROR",
    FabricError: "FABRIC_ERROR",
}


def _error_code_for(exc: Exception) -> str:
    """Return a standardized error code for the given exception."""
    for exc_type, code in _ERROR_CODE_MAP.items():
        if isinstance(exc, exc_type):
            return code
    return "UNEXPECTED_ERROR"


def handle_tool_errors(tool_func: Callable) -> Callable:
    """Decorator to standardize error handling across tools.
    
    This decorator catches exceptions raised by tool functions and returns
    structured error responses instead of raising, making tools more resilient
    and providing consistent error handling for LLM interactions.
    
    Args:
        tool_func: The tool function to wrap with error handling.
        
    Returns:
        Wrapped function that returns error dict instead of raising.
        
    Example:
        ```python
        @handle_tool_errors
        def my_tool(param: str) -> dict:
            # Tool implementation that might raise exceptions
            result = risky_operation(param)
            return format_success_response(result)
        ```
    """
    @wraps(tool_func)
    def wrapper(*args, **kwargs) -> Dict[str, Any]:
        try:
            return tool_func(*args, **kwargs)
        except Exception as exc:
            error_code = _error_code_for(exc)
            logger.error(f"Error in {tool_func.__name__}: {exc}", exc_info=True)
            response: Dict[str, Any] = {
                "status": "error",
                "error_code": error_code,
                "message": str(exc),
            }
            if isinstance(exc, FabricAPIError) and exc.status_code:
                response["status_code"] = exc.status_code
            return response
    return wrapper


def format_success_response(
    data: Any = None,
    message: str = "Operation successful"
) -> Dict[str, Any]:
    """Format a successful tool response with consistent structure.
    
    Args:
        data: The result data to include in the response. Can be dict, list, str, etc.
        message: Human-readable success message describing the operation result.
        
    Returns:
        Dictionary with status="success", message, and optional data field.
        
    Example:
        ```python
        return format_success_response(
            data={"workspace_id": "123", "name": "My Workspace"},
            message="Workspace created successfully"
        )
        ```
    """
    response = {
        "status": "success",
        "message": message
    }
    if data is not None:
        response["data"] = data
    return response


def format_error_response(
    message: str,
    error_code: str = None
) -> Dict[str, Any]:
    """Format an error tool response with consistent structure.
    
    Args:
        message: Human-readable error message explaining what went wrong.
        error_code: Optional error code for programmatic error handling.
        
    Returns:
        Dictionary with status="error", message, and optional error_code.
        
    Example:
        ```python
        return format_error_response(
            message="Workspace not found: 'MyWorkspace'",
            error_code="WORKSPACE_NOT_FOUND"
        )
        ```
    """
    response = {
        "status": "error",
        "message": message
    }
    if error_code:
        response["error_code"] = error_code
    return response


def log_tool_invocation(tool_name: str, **params) -> None:
    """Log tool invocation with sanitized parameters.
    
    Logs tool invocation at INFO level with parameter values, excluding
    sensitive data like tokens, credentials, or file contents.
    
    Args:
        tool_name: Name of the tool being invoked.
        **params: Tool parameters to log (sensitive values will be redacted).
        
    Example:
        ```python
        def my_tool(workspace_name: str, api_token: str):
            log_tool_invocation("my_tool", workspace_name=workspace_name, api_token="***")
            # ... tool implementation
        ```
    """
    # Sanitize sensitive parameters
    sanitized_params = {}
    sensitive_keys = {"token", "password", "secret", "key", "credential", "content"}
    
    for key, value in params.items():
        if any(sensitive in key.lower() for sensitive in sensitive_keys):
            sanitized_params[key] = "***REDACTED***"
        elif isinstance(value, str) and len(value) > 100:
            # Truncate long strings (likely file content)
            sanitized_params[key] = f"{value[:100]}... (truncated)"
        else:
            sanitized_params[key] = value
    
    logger.info(f"Tool invocation: {tool_name}", extra={"params": sanitized_params})


def validate_required_params(**params) -> None:
    """Validate that required parameters are provided and not empty.
    
    Raises:
        ValueError: If any parameter is None or empty string.
        
    Example:
        ```python
        def my_tool(workspace_name: str, item_name: str):
            validate_required_params(workspace_name=workspace_name, item_name=item_name)
            # ... rest of implementation
        ```
    """
    for param_name, param_value in params.items():
        if param_value is None or (isinstance(param_value, str) and not param_value.strip()):
            raise ValueError(f"Required parameter '{param_name}' is missing or empty")
