"""Human-readable text formatter for TTY console output.

ANSI-coloured by level when stderr is a TTY; plain text otherwise
(the JSON formatter takes over via the auto-format heuristic in
:func:`mgf.common.observability.logging_setup.setup_logging`).

Honours the ``NO_COLOR`` environment variable
(see https://no-color.org/) — when set to any value, colours are
disabled even on a TTY.

Pure stdlib — no other mgf.common imports beyond the type-checking-only
``Redactor`` reference.
"""

from __future__ import annotations

import logging
import os
import sys
import time
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from mgf.common.observability.redaction import Redactor

# ANSI escape codes per level. Bright colours work well on both
# dark and light terminal backgrounds.
_LEVEL_COLORS: dict[str, str] = {
    "DEBUG": "\033[90m",  # bright black
    "INFO": "\033[37m",  # light grey
    "WARNING": "\033[33m",  # yellow
    "ERROR": "\033[31m",  # red
    "CRITICAL": "\033[1;31m",  # bold red
}
_RESET = "\033[0m"

# Same reserved set as JsonFormatter — keep in sync if you ever
# extend either one.
_RESERVED_RECORD_ATTRS: frozenset[str] = frozenset(
    {
        "args",
        "asctime",
        "created",
        "exc_info",
        "exc_text",
        "filename",
        "funcName",
        "levelname",
        "levelno",
        "lineno",
        "message",
        "module",
        "msecs",
        "msg",
        "name",
        "pathname",
        "process",
        "processName",
        "relativeCreated",
        "stack_info",
        "taskName",
        "thread",
        "threadName",
    }
)


class TextFormatter(logging.Formatter):
    """Format records as ``HH:MM:SS LEVEL    logger.name: message [k=v ...]``.

    Extras (passed via ``extra={...}``) are appended as ``key=repr(value)``
    pairs after the message so humans see them too. Exception tracebacks
    follow on subsequent lines.
    """

    def __init__(self, *, redactor: Redactor) -> None:
        super().__init__()
        self._redactor = redactor
        self._supports_color = sys.stderr.isatty() and "NO_COLOR" not in os.environ

    @property
    def redactor(self) -> Redactor:
        """The :class:`Redactor` this formatter applies before emit.

        Public, stable accessor (v0.23.0). Mirror of
        :attr:`JsonFormatter.redactor`."""
        return self._redactor

    def format(self, record: logging.LogRecord) -> str:
        ts = time.strftime("%H:%M:%S", time.localtime(record.created))
        msg = record.getMessage()

        # Collect extras (rule LG-09 visibility — humans should see them).
        extras: dict[str, Any] = {}
        for key, value in record.__dict__.items():
            if key in _RESERVED_RECORD_ATTRS or key.startswith("_"):
                continue
            extras[key] = value

        # Redact in place — same policy as JSON, so a sensitive value
        # never leaks via the human-readable sink either.
        self._redactor.redact(extras)

        tail = " " + " ".join(f"{k}={v!r}" for k, v in extras.items()) if extras else ""

        head = f"{ts} {record.levelname:<8} {record.name}: {msg}{tail}"

        if record.exc_info:
            head = head + "\n" + self.formatException(record.exc_info)
        if record.stack_info:
            head = head + "\n" + self.formatStack(record.stack_info)

        if self._supports_color:
            color = _LEVEL_COLORS.get(record.levelname, "")
            return f"{color}{head}{_RESET}"
        return head


__all__ = ["TextFormatter"]
