from __future__ import annotations

import logging
import traceback
from typing import Any

from pulselog.logger import Logger


class PulseHandler(logging.Handler):
    """Bridge between Python's standard ``logging`` module and pulselog.

    Forwards all structured fields from stdlib LogRecords — including
    filename, line number, function name, and exception tracebacks —
    so they appear as searchable key/value pairs in the dashboard.

    Usage::

        import logging
        from pulselog.handler import PulseHandler

        handler = PulseHandler("my-app", dashboard=True)
        logging.getLogger().addHandler(handler)
        logging.info("this appears in the pulselog dashboard")

        # Structured fields are preserved:
        logging.error("db failed", extra={"query": "SELECT *", "db": "users"})
    """

    def __init__(self, name: str = "root", **kwargs: Any) -> None:
        super().__init__()
        self._logger = Logger(name, **kwargs)

    def emit(self, record: logging.LogRecord) -> None:
        """Forward a stdlib log record to pulselog.

        FIX #4: Previously only forwarded self.format(record) — a single
        string — silently dropping exc_info, pathname, lineno, funcName,
        and any extra fields the caller attached.  Now all structured
        fields are preserved as kwargs so they appear in the dashboard.

        Args:
            record: Standard LogRecord from the stdlib logging framework.
        """
        level_map = {
            logging.DEBUG:    self._logger.debug,
            logging.INFO:     self._logger.info,
            logging.WARNING:  self._logger.warning,
            logging.ERROR:    self._logger.error,
            logging.CRITICAL: self._logger.critical,
        }
        fn = level_map.get(record.levelno, self._logger.info)

        try:
            # Build the structured extra dict — preserves all useful fields
            extra: dict[str, Any] = {
                "logger":   record.name,
                "module":   record.module,
                "filename": record.filename,
                "lineno":   record.lineno,
                "funcName": record.funcName,
                "process":  record.process,
                "thread":   record.thread,
            }

            # Attach exception traceback if present
            if record.exc_info and record.exc_info[0] is not None:
                extra["traceback"] = "".join(
                    traceback.format_exception(*record.exc_info)
                ).strip()

            # Attach stack info if present
            if record.stack_info:
                extra["stack_info"] = record.stack_info

            # Attach any extra fields the caller passed via logging.info(..., extra={})
            # stdlib puts them directly on the record — collect the non-standard ones
            stdlib_attrs = frozenset({
                "name", "msg", "args", "levelname", "levelno", "pathname",
                "filename", "module", "exc_info", "exc_text", "stack_info",
                "lineno", "funcName", "created", "msecs", "relativeCreated",
                "thread", "threadName", "processName", "process", "message",
                "taskName",
            })
            for key, val in record.__dict__.items():
                if not key.startswith("_") and key not in stdlib_attrs:
                    extra[key] = val

            fn(record.getMessage(), **extra)

        except Exception:
            self.handleError(record)