"""
Error capture utilities: decorator and context manager.

Provides ergonomic wrappers around ErrorService for easy error capture.

Example usage:
    # Decorator for functions/views
    @capture_errors(source='api', severity='high')
    def risky_view(request):
        ...

    # Context manager for code blocks
    with error_context('Processing payment', severity='critical'):
        process_payment()

    # Context manager with request for full context
    with error_context('Creating ride', request=request, entity_type='Ride'):
        create_ride(data)
"""

import functools
import logging
from contextlib import contextmanager
from typing import Any, Callable, TypeVar

from django.http import HttpRequest

from apps.core.services import ErrorService

logger = logging.getLogger(__name__)

F = TypeVar('F', bound=Callable[..., Any])


def capture_errors(
    source: str = 'backend',
    severity: str = 'high',
    reraise: bool = True,
    log_errors: bool = True,
) -> Callable[[F], F]:
    """
    Decorator that automatically captures exceptions to the error dashboard.

    Use this on views, service methods, or any function where you want
    automatic error tracking.

    Args:
        source: Error source - 'frontend', 'backend', or 'api'
        severity: Default severity - 'critical', 'high', 'medium', 'low'
        reraise: Whether to re-raise the exception after capturing
        log_errors: Whether to also log to Python logger

    Returns:
        Decorated function

    Example:
        @capture_errors(source='api', severity='high')
        def create_park(request, data):
            # If this raises, error is automatically captured
            return ParkService.create(data)

        @capture_errors(severity='critical', reraise=False)
        def optional_cleanup():
            # Errors captured but swallowed
            cleanup_temp_files()
    """
    def decorator(func: F) -> F:
        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            # Try to extract request from args (common in Django views)
            request = None
            for arg in args:
                if isinstance(arg, HttpRequest):
                    request = arg
                    break
            if not request:
                request = kwargs.get('request')

            try:
                return func(*args, **kwargs)
            except Exception as e:
                if log_errors:
                    logger.exception(f"Error in {func.__name__}: {e}")

                # Capture to error dashboard
                try:
                    ErrorService.capture_error(
                        error=e,
                        source=source,
                        request=request,
                        severity=severity,
                        metadata={
                            'function_name': func.__name__,
                            'module': func.__module__,
                        },
                    )
                except Exception as capture_error:
                    logger.error(f"Failed to capture error: {capture_error}")

                if reraise:
                    raise

        return wrapper  # type: ignore
    return decorator


@contextmanager
def error_context(
    action: str,
    source: str = 'backend',
    severity: str = 'high',
    request: HttpRequest | None = None,
    entity_type: str | None = None,
    entity_id: int | str | None = None,
    reraise: bool = True,
    metadata: dict | None = None,
):
    """
    Context manager for capturing errors in code blocks.

    Use this when you want to capture errors from a specific block of code
    with rich context about what was happening.

    Args:
        action: Description of what the code block is doing
        source: Error source - 'frontend', 'backend', or 'api'
        severity: Error severity level
        request: Optional HTTP request for context
        entity_type: Optional entity type being operated on (e.g., 'Ride', 'Park')
        entity_id: Optional entity ID being operated on
        reraise: Whether to re-raise the exception after capturing
        metadata: Additional metadata to include

    Yields:
        None

    Example:
        with error_context('Creating ride submission', request=request, entity_type='Ride'):
            submission = SubmissionService.create(data)

        with error_context('Bulk import', severity='critical', reraise=False):
            for item in items:
                process_item(item)  # Errors logged but processing continues
    """
    try:
        yield
    except Exception as e:
        logger.exception(f"Error during '{action}': {e}")

        # Build metadata
        error_metadata = {
            'action': action,
            **(metadata or {}),
        }
        if entity_type:
            error_metadata['entity_type'] = entity_type
        if entity_id:
            error_metadata['entity_id'] = entity_id

        # Capture to error dashboard
        try:
            ErrorService.capture_error(
                error=e,
                source=source,
                request=request,
                severity=severity,
                metadata=error_metadata,
            )
        except Exception as capture_error:
            logger.error(f"Failed to capture error: {capture_error}")

        if reraise:
            raise


def capture_and_log(
    error: Exception,
    action: str,
    source: str = 'backend',
    severity: str = 'medium',
    request: HttpRequest | None = None,
    **kwargs: Any,
) -> str:
    """
    One-liner function to capture an error and return its ID.

    Use this when you've already caught an exception and want to
    report it without the decorator/context manager.

    Args:
        error: The exception to capture
        action: Description of what was happening
        source: Error source
        severity: Error severity level
        request: Optional HTTP request for context
        **kwargs: Additional metadata fields

    Returns:
        The error_id (short UUID) for reference

    Example:
        try:
            result = risky_operation()
        except Exception as e:
            error_id = capture_and_log(e, 'Risky operation failed', severity='high')
            return Response({'error': f'Failed (ref: {error_id})'}, status=500)
    """
    try:
        app_error = ErrorService.capture_error(
            error=error,
            source=source,
            request=request,
            severity=severity,
            metadata={'action': action, **kwargs},
        )
        return app_error.short_error_id
    except Exception as capture_error:
        logger.error(f"Failed to capture error: {capture_error}")
        return "unknown"
