"""
Logging Configuration Module

This module provides centralized logging configuration for the Openfund Agent system.
It supports JSON-formatted logging with configurable levels and outputs to both file and console.
"""

import logging
import logging.handlers
import json
import sys
from pathlib import Path
from typing import Dict, Any, Optional
from datetime import datetime, timezone


class JsonFormatter(logging.Formatter):
    """
    Custom JSON formatter for structured logging.

    Formats log records as JSON objects with timestamp, level, logger name,
    message, and additional context fields.
    """

    def format(self, record: logging.LogRecord) -> str:
        """
        Format a log record as a JSON string.

        Args:
            record: The log record to format

        Returns:
            JSON-formatted log string
        """
        log_data = {
            "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
        }

        # Add exception info if present
        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)

        # Add extra fields if present
        if hasattr(record, "extra_fields"):
            log_data.update(record.extra_fields)

        # Add any custom attributes
        for key, value in record.__dict__.items():
            if key not in [
                "name",
                "msg",
                "args",
                "created",
                "filename",
                "funcName",
                "levelname",
                "lineno",
                "module",
                "msecs",
                "message",
                "pathname",
                "process",
                "processName",
                "relativeCreated",
                "thread",
                "threadName",
                "exc_info",
                "exc_text",
                "stack_info",
                "extra_fields",
            ]:
                if not key.startswith("_"):
                    log_data[key] = value

        return json.dumps(log_data, ensure_ascii=False)


class PlainTextFormatter(logging.Formatter):
    """
    Plain text formatter for console output.

    Provides human-readable log output for console/terminal display.
    """

    def __init__(self):
        """Initialize the plain text formatter with a standard format."""
        super().__init__(
            fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
        )


def setup_logging(
    level: str = "INFO",
    log_format: str = "json",
    file_path: Optional[str] = None,
    max_file_size_mb: int = 100,
    backup_count: int = 5,
    console_output: bool = True,
    console_level: Optional[str] = None,
) -> None:
    """
    Configure the logging system for the Openfund Agent.

    Sets up logging with the specified configuration, including file rotation,
    JSON or plain text formatting, and console output.

    Args:
        level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
        log_format: Format type ('json' or 'text')
        file_path: Path to log file (None to disable file logging)
        max_file_size_mb: Maximum size of each log file in MB
        backup_count: Number of backup log files to keep
        console_output: Whether to output logs to console
        console_level: Logging level for console (defaults to same as file)

    Example:
        >>> setup_logging(
        ...     level="DEBUG",
        ...     log_format="json",
        ...     file_path="logs/app.log",
        ...     console_output=True
        ... )
    """
    # Convert string level to logging constant
    numeric_level = getattr(logging, level.upper(), logging.INFO)
    console_numeric_level = (
        getattr(logging, console_level.upper(), numeric_level) if console_level else numeric_level
    )

    # Get root logger
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.DEBUG)  # Set to DEBUG to allow all handlers to filter

    # Remove existing handlers
    root_logger.handlers.clear()

    # Create formatter based on format type
    if log_format.lower() == "json":
        formatter = JsonFormatter()
    else:
        formatter = PlainTextFormatter()

    # Setup file handler if file_path is provided
    if file_path:
        # Create log directory if it doesn't exist
        log_dir = Path(file_path).parent
        log_dir.mkdir(parents=True, exist_ok=True)

        # Create rotating file handler
        max_bytes = max_file_size_mb * 1024 * 1024
        file_handler = logging.handlers.RotatingFileHandler(
            filename=file_path, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8"
        )
        file_handler.setLevel(numeric_level)
        file_handler.setFormatter(formatter)
        root_logger.addHandler(file_handler)

    # Setup console handler if enabled
    if console_output:
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(console_numeric_level)

        # Use plain text formatter for console for better readability
        if log_format.lower() == "json":
            console_handler.setFormatter(PlainTextFormatter())
        else:
            console_handler.setFormatter(formatter)

        root_logger.addHandler(console_handler)


def setup_logging_from_config(config: Dict[str, Any]) -> None:
    """
    Configure logging from a configuration dictionary.

    This is a convenience function that extracts logging configuration
    from a config dict (typically loaded from YAML) and calls setup_logging.

    Args:
        config: Configuration dictionary containing logging settings

    Example:
        >>> config = {
        ...     'logging': {
        ...         'level': 'DEBUG',
        ...         'format': 'json',
        ...         'file_path': 'logs/app.log',
        ...         'max_file_size_mb': 100,
        ...         'backup_count': 5
        ...     }
        ... }
        >>> setup_logging_from_config(config)
    """
    logging_config = config.get("logging", {})

    setup_logging(
        level=logging_config.get("level", "INFO"),
        log_format=logging_config.get("format", "json"),
        file_path=logging_config.get("file_path"),
        max_file_size_mb=logging_config.get("max_file_size_mb", 100),
        backup_count=logging_config.get("backup_count", 5),
        console_output=logging_config.get("console_output", True),
        console_level=logging_config.get("console_level"),
    )


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

    This is a convenience wrapper around logging.getLogger that ensures
    consistent logger naming across the application.

    Args:
        name: Logger name (typically __name__ of the module)

    Returns:
        Logger instance

    Example:
        >>> logger = get_logger(__name__)
        >>> logger.info("Application started")
    """
    return logging.getLogger(name)


def add_context_to_logger(logger: logging.Logger, **context: Any) -> logging.LoggerAdapter:
    """
    Add contextual information to a logger.

    Creates a LoggerAdapter that automatically includes the specified
    context fields in all log messages.

    Args:
        logger: Base logger instance
        **context: Key-value pairs to include in log context

    Returns:
        LoggerAdapter with context

    Example:
        >>> logger = get_logger(__name__)
        >>> context_logger = add_context_to_logger(
        ...     logger,
        ...     strategy_id="strategy_123",
        ...     symbol="BTC/USDT"
        ... )
        >>> context_logger.info("Trade executed")
        # Log will include strategy_id and symbol fields
    """

    class ContextAdapter(logging.LoggerAdapter):
        def process(self, msg, kwargs):
            # Add context to extra fields
            if "extra" not in kwargs:
                kwargs["extra"] = {}
            if "extra_fields" not in kwargs["extra"]:
                kwargs["extra"]["extra_fields"] = {}
            kwargs["extra"]["extra_fields"].update(self.extra)
            return msg, kwargs

    return ContextAdapter(logger, context)


# Logging level constants for convenience
DEBUG = logging.DEBUG
INFO = logging.INFO
WARNING = logging.WARNING
ERROR = logging.ERROR
CRITICAL = logging.CRITICAL
