"""
Logging utilities for MarkiTect.

Provides standardized logger creation, performance logging,
and integration helpers for consistent logging across the application.
"""

import logging
import time
import functools
from typing import Callable, Any, Optional, Dict
from contextlib import contextmanager

from .context import LogContext, with_log_context, log_performance_metrics
from infrastructure.exceptions import ErrorContext, OperationType


def get_logger(name: str) -> logging.Logger:
    """
    Get a standardized logger instance.

    This is the standard way to create loggers throughout the application.
    It ensures consistent configuration and proper hierarchy.

    Args:
        name: Logger name, typically __name__ for module-level loggers

    Returns:
        Configured logger instance

    Usage:
        logger = get_logger(__name__)
        logger.info("This is a log message")
    """
    return logging.getLogger(name)


def get_component_logger(component: str, subcomponent: Optional[str] = None) -> logging.Logger:
    """
    Get a logger for a specific component or subcomponent.

    Args:
        component: Main component name (e.g., 'infrastructure', 'domain', 'application')
        subcomponent: Optional subcomponent name (e.g., 'repositories', 'services')

    Returns:
        Configured logger instance

    Usage:
        logger = get_component_logger('infrastructure', 'repositories')
        logger.info("Repository operation completed")
    """
    if subcomponent:
        name = f"{component}.{subcomponent}"
    else:
        name = component

    return logging.getLogger(name)


def log_function_call(logger: Optional[logging.Logger] = None,
                     level: str = 'DEBUG',
                     include_args: bool = False,
                     include_result: bool = False,
                     performance: bool = False) -> Callable:
    """
    Decorator to log function calls with optional arguments and results.

    Args:
        logger: Logger to use (defaults to function's module logger)
        level: Log level for the messages
        include_args: Whether to include function arguments in logs
        include_result: Whether to include function result in logs
        performance: Whether to log performance metrics

    Usage:
        @log_function_call(performance=True)
        def my_function(arg1, arg2):
            return arg1 + arg2
    """
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Get logger
            func_logger = logger or get_logger(func.__module__)
            log_level = getattr(logging, level.upper())

            # Build function identifier
            func_name = f"{func.__module__}.{func.__qualname__}"

            # Log function entry
            entry_msg = f"Entering {func_name}"
            if include_args and (args or kwargs):
                args_str = ", ".join([repr(arg) for arg in args])
                kwargs_str = ", ".join([f"{k}={repr(v)}" for k, v in kwargs.items()])
                all_args = [s for s in [args_str, kwargs_str] if s]
                entry_msg += f"({', '.join(all_args)})"

            func_logger.log(log_level, entry_msg)

            # Execute function with timing
            start_time = time.perf_counter()
            try:
                result = func(*args, **kwargs)

                # Log function exit
                duration_ms = (time.perf_counter() - start_time) * 1000
                exit_msg = f"Exiting {func_name}"

                if include_result:
                    exit_msg += f" -> {repr(result)}"

                if performance:
                    exit_msg += f" [{duration_ms:.2f}ms]"
                    # Also log to performance logger
                    log_performance_metrics(func_name, duration_ms)

                func_logger.log(log_level, exit_msg)
                return result

            except Exception as e:
                duration_ms = (time.perf_counter() - start_time) * 1000
                error_msg = f"Exception in {func_name} after {duration_ms:.2f}ms: {e}"
                func_logger.error(error_msg, exc_info=True)
                raise

        return wrapper
    return decorator


@contextmanager
def log_operation(operation_id: str,
                 operation_type: OperationType,
                 logger: Optional[logging.Logger] = None,
                 level: str = 'INFO',
                 **context_fields):
    """
    Context manager for logging complete operations with start/end and timing.

    Args:
        operation_id: Unique identifier for the operation
        operation_type: Type of operation being performed
        logger: Logger to use (defaults to infrastructure logger)
        level: Log level for operation messages
        **context_fields: Additional context fields

    Usage:
        with log_operation("create_issue", OperationType.WRITE, issue_id=123):
            # Logs operation start
            create_issue_logic()
            # Logs operation end with timing
    """
    if logger is None:
        logger = get_logger('infrastructure.operations')

    log_level = getattr(logging, level.upper())

    # Create operation context
    context = LogContext(
        operation_id=operation_id,
        operation_type=operation_type.value,
        custom_fields=context_fields
    )

    start_time = time.perf_counter()

    with with_log_context(context):
        # Log operation start
        logger.log(log_level, f"Starting operation: {operation_id}")

        try:
            yield context

            # Log successful completion
            duration_ms = (time.perf_counter() - start_time) * 1000
            logger.log(log_level, f"Operation {operation_id} completed successfully [{duration_ms:.2f}ms]")

            # Log performance metrics
            log_performance_metrics(operation_id, duration_ms, **context_fields)

        except Exception as e:
            # Log failure
            duration_ms = (time.perf_counter() - start_time) * 1000
            logger.error(f"Operation {operation_id} failed after {duration_ms:.2f}ms: {e}", exc_info=True)
            raise


def log_with_context(logger: logging.Logger,
                    level: str,
                    message: str,
                    context: Optional[LogContext] = None,
                    error_context: Optional[ErrorContext] = None,
                    **extra_fields) -> None:
    """
    Log a message with specific context.

    Args:
        logger: Logger instance
        level: Log level
        message: Log message
        context: Log context to use
        error_context: Error context to convert to log context
        **extra_fields: Additional fields to include in log
    """
    log_level = getattr(logging, level.upper())

    # Determine context to use
    if error_context:
        use_context = LogContext.from_error_context(error_context)
    elif context:
        use_context = context
    else:
        use_context = None

    # Add extra fields to context if provided
    if extra_fields and use_context:
        for key, value in extra_fields.items():
            use_context = use_context.with_custom_field(key, value)

    if use_context:
        with with_log_context(use_context):
            logger.log(log_level, message, extra=extra_fields)
    else:
        logger.log(log_level, message, extra=extra_fields)


def setup_logger_for_testing(logger_name: str, level: str = 'WARNING') -> logging.Logger:
    """
    Set up a logger for testing with minimal output.

    Args:
        logger_name: Name of the logger
        level: Log level to set

    Returns:
        Configured logger for testing
    """
    logger = logging.getLogger(logger_name)
    logger.setLevel(getattr(logging, level.upper()))

    # Remove any existing handlers
    for handler in logger.handlers[:]:
        logger.removeHandler(handler)

    # Add null handler to prevent logging during tests
    logger.addHandler(logging.NullHandler())

    return logger


def create_performance_logger(name: str = 'performance') -> logging.Logger:
    """
    Create a specialized logger for performance metrics.

    Args:
        name: Logger name

    Returns:
        Performance logger instance
    """
    logger = get_logger(name)
    return logger


def log_repository_operation(logger: logging.Logger,
                           operation: str,
                           resource_type: str,
                           resource_id: Optional[str] = None,
                           **details) -> Callable:
    """
    Decorator for logging repository operations consistently.

    Args:
        logger: Logger to use
        operation: Operation name (e.g., 'get', 'create', 'update', 'delete')
        resource_type: Type of resource (e.g., 'Issue', 'Project')
        resource_id: Optional resource identifier
        **details: Additional operation details

    Usage:
        @log_repository_operation(logger, 'get', 'Issue')
        def get_issue(self, issue_id):
            return self._fetch_issue(issue_id)
    """
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Extract resource ID from arguments if not provided
            actual_resource_id = resource_id
            if not actual_resource_id and args:
                # Try to get ID from first argument (common pattern)
                if hasattr(args[1] if len(args) > 1 else None, '__str__'):
                    actual_resource_id = str(args[1])

            operation_id = f"{operation}_{resource_type.lower()}"
            if actual_resource_id:
                operation_id += f"_{actual_resource_id}"

            # Determine operation type
            operation_type_map = {
                'get': OperationType.READ,
                'list': OperationType.READ,
                'create': OperationType.WRITE,
                'update': OperationType.WRITE,
                'delete': OperationType.DELETE
            }
            op_type = operation_type_map.get(operation.lower(), OperationType.READ)

            with log_operation(operation_id, op_type, logger,
                             resource_type=resource_type,
                             resource_id=actual_resource_id,
                             **details):
                return func(*args, **kwargs)

        return wrapper
    return decorator


# Commonly used logger instances
infrastructure_logger = get_logger('infrastructure')
domain_logger = get_logger('domain')
application_logger = get_logger('application')
performance_logger = create_performance_logger()


def get_default_loggers() -> Dict[str, logging.Logger]:
    """
    Get dictionary of commonly used loggers.

    Returns:
        Dictionary mapping logger names to logger instances
    """
    return {
        'infrastructure': infrastructure_logger,
        'domain': domain_logger,
        'application': application_logger,
        'performance': performance_logger
    }