"""Custom log formatter for structured logging output."""

import json
import logging
from typing import Any

__all__ = ("CustomFormatter", "RailwayJsonFormatter")


# Level mapping from Python logging levels to Railway's supported levels
_RAILWAY_LEVEL_MAP: dict[int, str] = {
    logging.DEBUG: "debug",
    logging.INFO: "info",
    logging.WARNING: "warn",
    logging.ERROR: "error",
    logging.CRITICAL: "error",  # Railway only supports debug/info/warn/error
}


class CustomFormatter(logging.Formatter):
    """A custom log formatter that adapts to structured 'details'.

    This formatter checks if a log record has a 'details' attribute. If
    it does, it formats the log message to include the entire 'details'
    dictionary. Otherwise, it falls back to the standard message format.
    """

    def __init__(self) -> None:
        """Initialize the CustomFormatter with a default date format."""
        super().__init__(None, "%Y-%m-%d %H:%M:%S")

    def format(self, record: logging.LogRecord) -> str:
        """Format the specified log record as text.

        Dynamically adjusts the format string based on whether the record
        contains a structured 'details' attribute or not. This allows
        structured logs to display their full details dictionary while
        standard logs use the basic message format.

        Args:
            record: The log record to format.

        Returns:
            The formatted log string with timestamp, level, and message or details.
        """
        if hasattr(record, "details"):
            self._style._fmt = "%(asctime)s - %(levelname)s - %(details)s"
        else:
            self._style._fmt = "%(asctime)s - %(levelname)s - %(message)s"
        return super().format(record)


class RailwayJsonFormatter(logging.Formatter):
    """JSON formatter compatible with Railway's structured logging format.

    This formatter outputs logs as single-line JSON objects with the structure
    expected by Railway's log explorer. When used with footprint.leave(), all
    metadata fields become top-level JSON keys for easy querying.

    Railway format requirements:
        - Single-line JSON (no pretty printing)
        - Required 'message' field
        - Optional 'level' field (debug, info, warn, error)
        - Custom attributes become queryable via @name:value syntax

    Example output:
        {"level":"info","message":"User logged in","controller":"auth","user_id":"abc-123"}
    """

    def __init__(self) -> None:
        """Initialize the RailwayJsonFormatter."""
        super().__init__()

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

        Extracts structured 'details' from footprint.leave() calls and flattens
        them into top-level JSON keys. For standard log calls, creates a minimal
        JSON object with just level and message.

        Args:
            record: The log record to format.

        Returns:
            A single-line JSON string compatible with Railway's structured logs.
        """
        # Get Railway-compatible level
        level = _RAILWAY_LEVEL_MAP.get(record.levelno, "info")

        # Build the JSON object
        log_data: dict[str, Any] = {
            "level": level,
        }

        # Check for structured details from footprint.leave()
        # Note: 'details' is added dynamically via logging's extra mechanism
        details = getattr(record, "details", None)
        if details is not None and isinstance(details, dict):
            # Use the message from details, or fallback to record.msg
            log_data["message"] = details.get("message") or str(record.msg)

            # Add all other details as top-level keys for Railway querying
            for key, value in details.items():
                if key == "message":
                    continue  # Already handled
                # Convert UUIDs to strings for JSON serialization
                if hasattr(value, "hex"):  # UUID check
                    log_data[key] = str(value)
                elif value is not None:
                    log_data[key] = value
        else:
            # Standard log without structured details
            log_data["message"] = record.getMessage()

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

        # Output as compact single-line JSON (required by Railway)
        return json.dumps(log_data, separators=(",", ":"), default=str)
