"""
Context-aware logging utilities for MarkiTect.

Provides correlation IDs, operation context, and contextual information
that can be attached to log messages for better traceability.
"""

import uuid
import threading
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Dict, Any, Optional, Generator
from enum import Enum

from infrastructure.exceptions import ErrorContext, OperationType


class LogLevel(Enum):
    """Log levels for context-aware logging."""
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


@dataclass
class LogContext:
    """Context information for logging operations."""
    correlation_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    operation_id: Optional[str] = None
    operation_type: Optional[str] = None
    user_id: Optional[str] = None
    request_id: Optional[str] = None
    custom_fields: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_error_context(cls, error_context: ErrorContext) -> 'LogContext':
        """Create LogContext from ErrorContext."""
        return cls(
            operation_id=error_context.operation_id,
            operation_type=error_context.operation_type.value if error_context.operation_type else None,
            custom_fields={
                'resource_type': error_context.resource_type,
                'resource_id': error_context.resource_id,
                'metadata': error_context.metadata
            }
        )

    def with_operation(self, operation_id: str, operation_type: Optional[OperationType] = None) -> 'LogContext':
        """Create new context with operation information."""
        return LogContext(
            correlation_id=self.correlation_id,
            operation_id=operation_id,
            operation_type=operation_type.value if operation_type else self.operation_type,
            user_id=self.user_id,
            request_id=self.request_id,
            custom_fields=self.custom_fields.copy()
        )

    def with_user(self, user_id: str) -> 'LogContext':
        """Create new context with user information."""
        return LogContext(
            correlation_id=self.correlation_id,
            operation_id=self.operation_id,
            operation_type=self.operation_type,
            user_id=user_id,
            request_id=self.request_id,
            custom_fields=self.custom_fields.copy()
        )

    def with_request(self, request_id: str) -> 'LogContext':
        """Create new context with request information."""
        return LogContext(
            correlation_id=self.correlation_id,
            operation_id=self.operation_id,
            operation_type=self.operation_type,
            user_id=self.user_id,
            request_id=request_id,
            custom_fields=self.custom_fields.copy()
        )

    def with_custom_field(self, key: str, value: Any) -> 'LogContext':
        """Create new context with additional custom field."""
        new_fields = self.custom_fields.copy()
        new_fields[key] = value
        return LogContext(
            correlation_id=self.correlation_id,
            operation_id=self.operation_id,
            operation_type=self.operation_type,
            user_id=self.user_id,
            request_id=self.request_id,
            custom_fields=new_fields
        )


# Thread-local storage for context
_context_storage = threading.local()


def set_log_context(context: LogContext) -> None:
    """Set the current log context for this thread."""
    _context_storage.context = context


def get_current_log_context() -> Optional[LogContext]:
    """Get the current log context for this thread."""
    return getattr(_context_storage, 'context', None)


def clear_log_context() -> None:
    """Clear the current log context for this thread."""
    if hasattr(_context_storage, 'context'):
        delattr(_context_storage, 'context')


@contextmanager
def with_log_context(context: LogContext) -> Generator[LogContext, None, None]:
    """
    Context manager for setting log context temporarily.

    Usage:
        with with_log_context(LogContext(operation_id="create_issue")):
            logger.info("Creating new issue")
            # Log messages will include operation_id context
    """
    previous_context = get_current_log_context()
    try:
        set_log_context(context)
        yield context
    finally:
        if previous_context:
            set_log_context(previous_context)
        else:
            clear_log_context()


@contextmanager
def with_operation_context(operation_id: str, operation_type: Optional[OperationType] = None) -> Generator[LogContext, None, None]:
    """
    Context manager for setting operation context.

    Usage:
        with with_operation_context("create_issue", OperationType.WRITE):
            logger.info("Starting issue creation")
    """
    current_context = get_current_log_context()
    if current_context:
        context = current_context.with_operation(operation_id, operation_type)
    else:
        context = LogContext(
            operation_id=operation_id,
            operation_type=operation_type.value if operation_type else None
        )

    with with_log_context(context) as ctx:
        yield ctx


@contextmanager
def with_correlation_id(correlation_id: Optional[str] = None) -> Generator[LogContext, None, None]:
    """
    Context manager for setting correlation ID.

    Usage:
        with with_correlation_id() as ctx:
            logger.info("Processing request")
            # New correlation ID is generated and used
    """
    if correlation_id is None:
        correlation_id = str(uuid.uuid4())

    current_context = get_current_log_context()
    if current_context:
        context = LogContext(
            correlation_id=correlation_id,
            operation_id=current_context.operation_id,
            operation_type=current_context.operation_type,
            user_id=current_context.user_id,
            request_id=current_context.request_id,
            custom_fields=current_context.custom_fields.copy()
        )
    else:
        context = LogContext(correlation_id=correlation_id)

    with with_log_context(context) as ctx:
        yield ctx


def create_child_context(parent_operation_id: str, child_operation_id: str) -> LogContext:
    """
    Create a child context that inherits from current context.

    Args:
        parent_operation_id: The parent operation ID for reference
        child_operation_id: The new child operation ID

    Returns:
        New LogContext with child operation ID and inherited correlation ID
    """
    current_context = get_current_log_context()
    if current_context:
        return current_context.with_operation(child_operation_id).with_custom_field(
            'parent_operation_id', parent_operation_id
        )
    else:
        return LogContext(
            operation_id=child_operation_id,
            custom_fields={'parent_operation_id': parent_operation_id}
        )


def log_performance_metrics(operation_id: str, duration_ms: float, **metrics: Any) -> None:
    """
    Log performance metrics with context.

    Args:
        operation_id: The operation being measured
        duration_ms: Duration in milliseconds
        **metrics: Additional performance metrics
    """
    import logging

    logger = logging.getLogger('performance')

    # Create performance context
    context = LogContext(
        operation_id=operation_id,
        operation_type="PERFORMANCE",
        custom_fields={'metrics': metrics}
    )

    with with_log_context(context):
        # Add performance data to log record
        extra = {
            'duration_ms': duration_ms,
            **{f'perf_{k}': v for k, v in metrics.items()}
        }

        logger.info(f"Performance: {operation_id} completed in {duration_ms:.2f}ms", extra=extra)


def log_with_error_context(logger, level: LogLevel, message: str, error_context: ErrorContext, exc_info=None) -> None:
    """
    Log a message with error context information.

    Args:
        logger: Logger instance
        level: Log level
        message: Log message
        error_context: Error context with operation details
        exc_info: Exception information
    """
    log_context = LogContext.from_error_context(error_context)

    with with_log_context(log_context):
        log_method = getattr(logger, level.value.lower())
        log_method(message, exc_info=exc_info)


# Convenience functions for common logging patterns

def log_operation_start(logger, operation_id: str, operation_type: OperationType, **details: Any) -> LogContext:
    """Log the start of an operation and return context for continued use."""
    context = LogContext(
        operation_id=operation_id,
        operation_type=operation_type.value,
        custom_fields=details
    )

    with with_log_context(context):
        logger.info(f"Starting operation: {operation_id}")

    return context


def log_operation_end(logger, context: LogContext, success: bool = True, **details: Any) -> None:
    """Log the end of an operation with result."""
    result = "completed successfully" if success else "failed"

    with with_log_context(context.with_custom_field('result', result)):
        if details:
            context = context.with_custom_field('result_details', details)

        if success:
            logger.info(f"Operation {context.operation_id} {result}")
        else:
            logger.error(f"Operation {context.operation_id} {result}")


def log_operation_progress(logger, context: LogContext, step: str, progress: Optional[float] = None, **details: Any) -> None:
    """Log progress during a long-running operation."""
    progress_context = context.with_custom_field('step', step)
    if progress is not None:
        progress_context = progress_context.with_custom_field('progress_percent', progress)
    if details:
        progress_context = progress_context.with_custom_field('step_details', details)

    with with_log_context(progress_context):
        progress_str = f" ({progress:.1f}%)" if progress is not None else ""
        logger.info(f"Operation {context.operation_id}: {step}{progress_str}")