import re
import logging
import traceback
from .client import send_async
from .payload import base_payload

# Werkzeug HTTP server access log format: "IP - - [DD/Mon/YYYY HH:MM:SS] ..."
# These are HTTP protocol-level rejections (e.g. bots sending HTTP/2 to an
# HTTP/1.1 server). They are never caused by application code and must not
# create incidents.
_WERKZEUG_ACCESS_LOG = re.compile(r'\d+\.\d+\.\d+\.\d+ - - \[')

# Logger name prefixes that emit infrastructure noise — never application bugs.
#
#   urllib3 / requests.packages.urllib3:
#     When the AIOps platform is temporarily unreachable, urllib3 logs
#     connection pool errors at ERROR level. Without this filter those errors
#     would be picked up by our handler, sent to the platform (which is down),
#     fail, and log again — a partial feedback loop that wastes retries.
#
#   werkzeug.serving / werkzeug._internal:
#     SSL handshake errors, connection resets, and "abort" signals that Werkzeug
#     logs when clients disconnect mid-request. These are always infra/network
#     events, never application logic failures.
_SKIP_LOGGER_PREFIXES = (
    "urllib3.",
    "requests.packages.urllib3.",
    "werkzeug.serving",
    "werkzeug._internal",
)


class AIOpsLogHandler(logging.Handler):
    def emit(self, record):
        # Only forward ERROR and CRITICAL — lower levels are not incidents.
        if record.levelno < logging.ERROR:
            return

        # Self-reference guard: ignore log records emitted by the SDK itself.
        # The background worker logs at DEBUG on failure, but guard all SDK
        # loggers defensively to prevent any feedback loop regardless of
        # future log-level changes.
        if record.name.startswith("aiops_sdk"):
            return

        # Skip known infrastructure/library noise loggers (see module-level comment).
        if record.name.startswith(_SKIP_LOGGER_PREFIXES):
            return

        # Fetch the formatted message once — used for both content check and payload.
        try:
            msg = record.getMessage()
        except Exception:
            return  # Malformed log record — skip safely

        # Skip empty messages (can happen with badly constructed logging calls).
        if not msg or not msg.strip():
            return

        # Skip Werkzeug HTTP protocol noise (bot/scanner traffic).
        # Uses the pre-fetched msg to avoid calling getMessage() twice.
        if _WERKZEUG_ACCESS_LOG.search(msg):
            return

        try:
            # Extract stack trace from exc_info when available.
            if record.exc_info:
                stack_trace = "".join(traceback.format_exception(*record.exc_info))
            else:
                stack_trace = record.exc_text or ""

            payload = base_payload()
            payload.update({
                "signal_type": "log_error",
                "error_type": record.levelname,
                "message": msg,
                "stack_trace": stack_trace,
                "source": "logging",
            })

            # send_async: never blocks the thread that emitted the log record.
            # Blocking here would cause every logger.error() call to stall for
            # up to several seconds — unacceptable in request-handling context.
            send_async("/v1/sdk/exception", payload)
        except Exception:
            pass  # handler must never raise


def attach_logging_handler():
    root = logging.getLogger()
    # Guard against duplicate registration (init() called twice, hot-reload).
    for handler in root.handlers:
        if isinstance(handler, AIOpsLogHandler):
            return
    root.addHandler(AIOpsLogHandler())
