"""Flight recorder for automatic log capture during handler execution.

The flight recorder transparently captures all log output during a handler's
``process()`` call and attaches it to the ``AdapterResponse.execution_logs``
field. Handlers are unaware of the flight recorder — they just use standard
``logging.getLogger(__name__)`` calls.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any

from entr_adapter_core.schemas import (
    AdapterResponse,
    DocumentPayload,
    StatusEnum,
)

# Standard LogRecord attributes to exclude when extracting extra context
_STANDARD_ATTRS = frozenset({
    "name", "msg", "args", "created", "relativeCreated", "exc_info",
    "exc_text", "stack_info", "lineno", "funcName", "pathname",
    "filename", "module", "thread", "threadName", "process",
    "processName", "levelname", "levelno", "msecs", "message",
    "asctime", "taskName",
})

server_logger = logging.getLogger("entr_adapter_core.dispatch")


class FlightRecorderHandler(logging.Handler):
    """A logging handler that captures log records to an in-memory list.

    Each record is stored as a dict with keys: ``timestamp``, ``level``,
    ``logger_name``, ``message``, and ``context``. When the entry limit is
    exceeded, the oldest entries are dropped and a truncation marker is
    appended to the output.
    """

    def __init__(self, max_entries: int = 5000) -> None:
        super().__init__()
        self._max_entries = max_entries
        self._records: list[dict[str, Any]] = []
        self._dropped_count = 0

    def emit(self, record: logging.LogRecord) -> None:
        """Capture a log record as a formatted dict."""
        try:
            # Extract extra context (non-standard attributes)
            context: dict[str, Any] | None = None
            extra_keys = set(record.__dict__.keys()) - _STANDARD_ATTRS
            if extra_keys:
                context = {k: record.__dict__[k] for k in extra_keys}

            entry = {
                "timestamp": datetime.fromtimestamp(
                    record.created, tz=timezone.utc
                ).isoformat(),
                "level": record.levelname,
                "logger_name": record.name,
                "message": self.format(record),
                "context": context,
            }

            if len(self._records) >= self._max_entries:
                self._records.pop(0)
                self._dropped_count += 1

            self._records.append(entry)
        except Exception:
            self.handleError(record)

    def get_logs(self) -> list[dict[str, Any]]:
        """Return a copy of captured log entries.

        If entries were dropped due to the limit, a truncation marker is
        appended as the final entry.
        """
        logs = list(self._records)
        if self._dropped_count > 0:
            logs.append({
                "truncated": True,
                "dropped_count": self._dropped_count,
                "message": "Log buffer exceeded limit, oldest entries dropped",
                "level": "WARNING",
                "timestamp": datetime.now(tz=timezone.utc).isoformat(),
            })
        return logs

    def clear(self) -> None:
        """Reset captured records and dropped count."""
        self._records.clear()
        self._dropped_count = 0


class FlightRecorder:
    """Async context manager that captures log output during a block.

    Attaches a ``FlightRecorderHandler`` to a logger on entry and removes
    it on exit (even if an exception occurs). Captured logs are available
    via ``get_logs()`` after the block completes.

    Args:
        max_entries: Maximum number of log entries to retain (default 1000).
        logger_name: Logger to attach to. If None, attaches to the root logger
            to capture all log output.
    """

    def __init__(
        self,
        max_entries: int = 5000,
        logger_name: str | None = None,
    ) -> None:
        self._max_entries = max_entries
        self._logger = logging.getLogger(logger_name)
        self._handler: FlightRecorderHandler | None = None

    async def __aenter__(self) -> FlightRecorder:
        self._handler = FlightRecorderHandler(max_entries=self._max_entries)
        self._handler.setLevel(logging.DEBUG)
        self._logger.addHandler(self._handler)
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:
        if self._handler is not None:
            self._logger.removeHandler(self._handler)
        return False  # Do not suppress exceptions

    def get_logs(self) -> list[dict[str, Any]]:
        """Return captured log entries from the handler."""
        if self._handler is None:
            return []
        return self._handler.get_logs()


async def execute_with_flight_recorder(
    handler,
    payload: DocumentPayload,
    max_entries: int = 5000,
) -> AdapterResponse:
    """Execute a handler's process() with automatic log capture.

    This is the primary integration point called by the dispatcher.
    Wraps the handler call in a FlightRecorder, captures all logs,
    and attaches them to the response.

    On unhandled exceptions, the full traceback is logged INSIDE the
    flight recorder context so it appears in ``execution_logs`` for
    diagnostics. The ``failure_reason`` includes the exception message.

    Args:
        handler: The handler instance to execute.
        payload: The document payload to process.
        max_entries: Maximum flight recorder log entries (default 5000).

    Returns:
        The ``AdapterResponse`` with ``execution_logs`` populated.
    """
    async with FlightRecorder(max_entries=max_entries) as recorder:
        try:
            response = await handler.process(payload)
        except Exception as exc:
            # Log the full traceback INSIDE the recorder so it appears
            # in execution_logs — this is valuable for diagnostics
            server_logger.exception(
                "Unhandled exception in handler for adapter_identifier: %s",
                payload.adapter_identifier,
                exc_info=exc,
            )
            response = AdapterResponse(
                document_id=payload.document_id,
                status=StatusEnum.FAILED,
                failure_reason=str(exc),
            )

        response.execution_logs = recorder.get_logs()
        return response
