"""
#exonware/xwnode/src/exonware/xwnode/errors.py
A+ Best Practice Error Handling for xwnode Library.
This module implements zero-overhead error handling with rich context,
actionable suggestions, and performance optimization for critical paths.
"""

from __future__ import annotations
import time
import difflib
from typing import Any
from exonware.xwsystem import get_logger
logger = get_logger(__name__)
# ============================================================================
# A+ BASE ERROR SYSTEM
# ============================================================================

class XWNodeError(Exception):
    """
    Base exception with rich context and zero overhead in success path.
    This error system follows modern Python best practices:
    - Zero overhead when no errors occur
    - Rich context only created on failure path
    - Chainable methods for fluent error building
    - Performance-optimized with __slots__
    """
    __slots__ = ('message', 'error_code', 'context', 'suggestions', 'timestamp', 'cause')

    def __init__(self, message: str, *, 
                 error_code: str = None,
                 context: dict[str, Any] = None,
                 suggestions: list[str] = None,
                 cause: Exception = None):
        super().__init__(message)
        self.message = message
        self.error_code = error_code or self.__class__.__name__
        self.context = context or {}
        self.suggestions = suggestions or []
        self.timestamp = time.time()
        self.cause = cause

    def add_context(self, **kwargs) -> XWNodeError:
        """Add context information (chainable)."""
        self.context.update(kwargs)
        return self

    def suggest(self, suggestion: str) -> XWNodeError:
        """Add actionable suggestion (chainable)."""
        self.suggestions.append(suggestion)
        return self

    def __str__(self) -> str:
        """Rich string representation with context and suggestions."""
        result = [self.message]
        if self.context:
            context_str = ', '.join(f"{k}={v}" for k, v in self.context.items())
            result.append(f"Context: {context_str}")
        if self.suggestions:
            suggestions_str = '; '.join(self.suggestions)
            result.append(f"Suggestions: {suggestions_str}")
        return " | ".join(result)
# ============================================================================
# PATH NAVIGATION ERRORS (Most Common)
# ============================================================================

class XWNodePathError(XWNodeError, KeyError):
    """
    Path navigation errors with AI-like suggestions.
    This is the most common error type, so it's heavily optimized
    with smart suggestion generation and fuzzy matching.
    """
    __slots__ = ('path', 'segment', 'reason', 'node_type', 'available_keys')

    def __init__(self, path: str, segment: str = None, reason: str = "not_found", 
                 node_type: str = None, available_keys: list[str] = None):
        self.path = path
        self.segment = segment or path
        self.reason = reason  # "not_found", "type_mismatch", "out_of_bounds"
        self.node_type = node_type
        self.available_keys = available_keys or []
        # Generate helpful message
        message = f"Path navigation failed: '{path}'"
        if segment and segment != path:
            message += f" at segment '{segment}'"
        message += f" ({reason})"
        context = {
            'path': path,
            'segment': segment,
            'reason': reason,
            'node_type': node_type
        }
        suggestions = self._generate_smart_suggestions()
        super().__init__(message, 
                        error_code=f"PATH_{reason.upper()}", 
                        context=context,
                        suggestions=suggestions)

    def _generate_smart_suggestions(self) -> list[str]:
        """Generate AI-like suggestions based on error context."""
        suggestions = []
        if self.reason == "not_found" and self.available_keys:
            # Fuzzy matching for typos (performance: only on error path)
            close_matches = difflib.get_close_matches(
                self.segment, self.available_keys, n=3, cutoff=0.6
            )
            if close_matches:
                suggestions.append(f"Did you mean: {', '.join(close_matches)}?")
            # Show available options (truncated for readability)
            if len(self.available_keys) <= 5:
                suggestions.append(f"Available: {', '.join(self.available_keys)}")
            else:
                preview = ', '.join(self.available_keys[:5])
                suggestions.append(f"Available: {preview}... ({len(self.available_keys)} total)")
        elif self.reason == "type_mismatch":
            suggestions.append("Cannot access children of leaf nodes")
            if self.node_type:
                suggestions.append(f"Node type is '{self.node_type}' (not a container)")
        elif self.reason == "out_of_bounds":
            suggestions.append("Check list bounds before accessing by index")
            if self.available_keys:
                suggestions.append(f"Valid range: 0-{len(self.available_keys)-1}")
        return suggestions


class XWNodeTypeError(XWNodeError, TypeError):
    """Enhanced type errors with operation context."""
    __slots__ = ('attempted_operation', 'actual_type', 'expected_types')

    def __init__(self, message: str, *,
                 attempted_operation: str = None,
                 actual_type: str = None,
                 expected_types: list[str] = None):
        self.attempted_operation = attempted_operation
        self.actual_type = actual_type
        self.expected_types = expected_types or []
        context = {
            'operation': attempted_operation,
            'actual_type': actual_type,
            'expected_types': expected_types
        }
        suggestions = []
        if expected_types:
            suggestions.append(f"Expected types: {', '.join(expected_types)}")
        if attempted_operation:
            suggestions.append(f"Check node type before {attempted_operation}")
        super().__init__(message, 
                        error_code="TYPE_MISMATCH",
                        context=context,
                        suggestions=suggestions)


class XWNodeValueError(XWNodeError, ValueError):
    """Enhanced value errors with validation context."""
    __slots__ = ('invalid_value', 'constraints', 'validation_rules')

    def __init__(self, message: str, *,
                 invalid_value: Any = None,
                 constraints: dict[str, Any] = None,
                 validation_rules: list[str] = None):
        self.invalid_value = invalid_value
        self.constraints = constraints or {}
        self.validation_rules = validation_rules or []
        context = {
            'invalid_value': invalid_value,
            'constraints': constraints
        }
        suggestions = []
        if validation_rules:
            suggestions.extend(validation_rules)
        if constraints:
            for rule, value in constraints.items():
                suggestions.append(f"Value must satisfy {rule}: {value}")
        super().__init__(message,
                        error_code="INVALID_VALUE", 
                        context=context,
                        suggestions=suggestions)
# ============================================================================
# PERFORMANCE-OPTIMIZED ERROR FACTORY
# ============================================================================

class ErrorFactory:
    """
    Zero-overhead error creation for critical paths.
    This factory provides fast paths for common error scenarios,
    avoiding expensive operations in hot code paths.
    """
    @staticmethod

    def path_not_found(path: str, segment: str = None, available_keys: list[str] = None) -> XWNodePathError:
        """Fast path for common 'not found' errors."""
        return XWNodePathError(path, segment, "not_found", available_keys=available_keys)
    @staticmethod

    def type_mismatch(path: str, segment: str, node_type: str) -> XWNodePathError:
        """Fast path for type mismatch errors."""
        return XWNodePathError(path, segment, "type_mismatch", node_type=node_type)
    @staticmethod

    def index_out_of_bounds(path: str, index: int, length: int) -> XWNodePathError:
        """Fast path for index errors."""
        return XWNodePathError(path, str(index), "out_of_bounds").add_context(
            index=index, length=length
        ).suggest(f"Valid range: 0-{length-1}")
    @staticmethod

    def operation_not_supported(operation: str, node_type: str, supported_ops: list[str] = None) -> XWNodeTypeError:
        """Fast path for unsupported operation errors."""
        message = f"Operation '{operation}' not supported on {node_type}"
        error = XWNodeTypeError(
            message,
            attempted_operation=operation,
            actual_type=node_type
        )
        if supported_ops:
            error.suggest(f"Supported operations: {', '.join(supported_ops)}")
        return error
    @staticmethod

    def invalid_preset(preset_name: str, available_presets: list[str]) -> XWNodeValueError:
        """Fast path for invalid preset errors."""
        close_matches = difflib.get_close_matches(preset_name, available_presets, n=3, cutoff=0.6)
        error = XWNodeValueError(
            f"Unknown preset '{preset_name}'",
            invalid_value=preset_name,
            constraints={'available_presets': available_presets}
        )
        if close_matches:
            error.suggest(f"Did you mean: {', '.join(close_matches)}?")
        error.suggest(f"Available presets: {', '.join(available_presets)}")
        return error
# ============================================================================
# STRATEGY SYSTEM ERRORS (A+ Enhanced)
# ============================================================================

class XWNodeStrategyError(XWNodeError):
    """Base exception for strategy-related errors."""
    pass


class XWNodeUnsupportedCapabilityError(XWNodeStrategyError):
    """Raised when a strategy doesn't support a requested capability."""
    __slots__ = ('capability', 'strategy', 'available_capabilities')

    def __init__(self, capability: str, strategy: str, available_capabilities: list[str] = None):
        self.capability = capability
        self.strategy = strategy
        self.available_capabilities = available_capabilities or []
        message = f"Strategy '{strategy}' does not support capability '{capability}'"
        context = {
            'capability': capability,
            'strategy': strategy,
            'available_capabilities': available_capabilities
        }
        suggestions = []
        if available_capabilities:
            suggestions.append(f"Available capabilities: {', '.join(available_capabilities)}")
            # Suggest alternative strategies
            if capability == 'graph_operations':
                suggestions.append("Use preset='SOCIAL_GRAPH' or 'TREE_GRAPH_MIX' for graph features")
            elif capability == 'spatial_operations':
                suggestions.append("Use preset='SPATIAL_MAP' for spatial features")
        super().__init__(message,
                        error_code="UNSUPPORTED_CAPABILITY",
                        context=context,
                        suggestions=suggestions)


class XWNodePresetError(XWNodeStrategyError):
    """Raised when preset operations fail."""
    __slots__ = ('preset_name', 'operation', 'reason')

    def __init__(self, preset_name: str, operation: str, reason: str = None):
        self.preset_name = preset_name
        self.operation = operation
        self.reason = reason
        message = f"Preset '{preset_name}' {operation} failed"
        if reason:
            message += f": {reason}"
        context = {
            'preset_name': preset_name,
            'operation': operation,
            'reason': reason
        }
        # Import here to avoid circular imports
        from .defs import list_presets
        available = list_presets()
        suggestions = [f"Available presets: {', '.join(available)}"]
        super().__init__(message,
                        error_code="PRESET_ERROR",
                        context=context,
                        suggestions=suggestions)
# ============================================================================
# SECURITY ERRORS (A+ Enhanced)
# ============================================================================

class XWNodeSecurityError(XWNodeError):
    """Base exception for security-related errors."""
    pass


class XWNodeLimitError(XWNodeSecurityError):
    """Raised when resource limits are exceeded."""
    __slots__ = ('resource', 'limit', 'actual_value')

    def __init__(self, resource: str, limit: int | str, actual_value: int | str = None):
        self.resource = resource
        self.limit = limit
        self.actual_value = actual_value
        message = f"Resource limit exceeded for {resource}: limit={limit}"
        if actual_value is not None:
            message += f", actual={actual_value}"
        context = {
            'resource': resource,
            'limit': limit,
            'actual_value': actual_value
        }
        suggestions = [
            f"Increase {resource} limit in configuration",
            "Consider using a more efficient strategy",
            "Break operation into smaller chunks"
        ]
        super().__init__(message,
                        error_code="RESOURCE_LIMIT",
                        context=context,
                        suggestions=suggestions)


class XWNodePathSecurityError(XWNodeSecurityError):
    """Raised for path-related security violations."""
    __slots__ = ('path', 'violation_type', 'security_policy')

    def __init__(self, path: str, violation_type: str, security_policy: str = None):
        self.path = path
        self.violation_type = violation_type
        self.security_policy = security_policy
        message = f"Path security violation: {violation_type} in '{path}'"
        if security_policy:
            message += f" (policy: {security_policy})"
        context = {
            'path': path,
            'violation_type': violation_type,
            'security_policy': security_policy
        }
        suggestions = [
            "Check path for invalid characters or patterns",
            "Use only trusted data sources",
            "Sanitize user input before path operations"
        ]
        super().__init__(message,
                        error_code="PATH_SECURITY",
                        context=context,
                        suggestions=suggestions)
# ============================================================================
# PERFORMANCE OPTIMIZATION HELPERS
# ============================================================================

def safe_path_access(get_child_func, path: str, segment: str, node=None):
    """
    Optimized wrapper for path access with minimal error overhead.
    This function encapsulates the zero-overhead error handling pattern:
    - Fast path when operation succeeds
    - Rich error context only on failure
    """
    try:
        # Fast path - no error overhead when successful
        return get_child_func(segment)
    except KeyError:
        # Only create rich error on failure path
        available = []
        if node and hasattr(node, 'keys'):
            available = list(node.keys())
        raise ErrorFactory.path_not_found(path, segment, available)
    except IndexError:
        # Index out of bounds
        length = len(node) if node and hasattr(node, '__len__') else 0
        try:
            index = int(segment)
            raise ErrorFactory.index_out_of_bounds(path, index, length)
        except ValueError:
            # Non-numeric index on list
            raise ErrorFactory.type_mismatch(path, segment, "list")
    except TypeError:
        # Type mismatch - trying to access child of leaf
        node_type = type(node).__name__ if node else "unknown"
        raise ErrorFactory.type_mismatch(path, segment, node_type)
# ============================================================================
# EXPORTS
# ============================================================================
__all__ = [
    # Core error system
    'XWNodeError',
    'XWNodePathError', 
    'XWNodeTypeError',
    'XWNodeValueError',
    # Security errors
    'XWNodeSecurityError',
    'XWNodeLimitError',
    'XWNodePathSecurityError',
    # Strategy errors
    'XWNodeStrategyError',
    'XWNodeUnsupportedCapabilityError',
    'XWNodePresetError',
    # Performance helpers
    'ErrorFactory',
    'safe_path_access',
]
