"""Logging filters shared across Mnemosyne processes.

These are project-level (not tied to a Django app) so Celery workers and
the FastMCP ASGI app can reuse them without importing app modules.
"""

from __future__ import annotations

import logging
import re


# Paths that should not show up in INFO when the response is a success.
# Anything >= 400 still flows through — a failing probe is a real signal.
_SUPPRESS_PATHS = frozenset(
    {
        "/live/",
        "/live",
        "/ready/",
        "/ready",
        "/healthz",
        "/metrics",
        "/mcp/health",
    }
)


class SuppressHealthAccessFilter(logging.Filter):
    """Demote successful access-log records for health endpoints to DEBUG.

    Applied to ``django.server`` (runserver) and ``gunicorn.access`` via
    the ``access`` handler in :data:`mnemosyne.settings.LOGGING`.  The filter
    returns ``False`` (drop the record) only when the request path is a
    health endpoint AND the HTTP status is 1xx/2xx/3xx.  Any failure on
    ``/ready/`` or ``/live/`` still propagates so an operator sees
    readiness flaps.

    The two access loggers format their messages differently:

    * ``django.server`` emits ``'"GET /live/ HTTP/1.1" 200 0'`` as the
      message with no args.
    * ``gunicorn.access`` typically has the path in ``record.args`` when
      the access log format is configured, but many deployments fall
      back to a pre-formatted message.  We parse the final rendered
      message in both cases to keep the filter portable across Mnemosyne
      containers (which run gunicorn) and local dev (``runserver``).
    """

    # Matches the path portion of the quoted request line inside either
    # format.  Tolerant of missing trailing slashes and query strings.
    _REQUEST_RE = re.compile(r'"\s*(?:GET|POST|HEAD|OPTIONS|PUT|PATCH|DELETE)\s+(\S+)')
    _STATUS_RE = re.compile(r'"\s+(\d{3})\b')

    def filter(self, record: logging.LogRecord) -> bool:
        msg = record.getMessage()
        path_match = self._REQUEST_RE.search(msg)
        status_match = self._STATUS_RE.search(msg)
        if not path_match or not status_match:
            return True

        path = path_match.group(1).split("?", 1)[0]
        status = int(status_match.group(1))

        # Only suppress successful probes; surface any 4xx/5xx on a
        # health endpoint so operators see readiness flaps.
        if path in _SUPPRESS_PATHS and status < 400:
            return False
        return True
