# aiops_sdk/exceptions.py

import sys
import traceback
from .client import send_async, send_sync
from .payload import base_payload


def _excepthook(exc_type, exc, tb):
    """
    sys.excepthook replacement — fires when an uncaught exception terminates
    the process.

    Uses send_sync (blocking) because daemon threads do not run after the
    main thread exits. send_async would enqueue the payload but the background
    worker thread would be killed before it can deliver it.
    """
    try:
        payload = base_payload()
        payload.update({
            "signal_type": "exception",
            "status_code": 500,
            "error_type": exc_type.__name__,
            "message": str(exc),
            "stack_trace": "".join(traceback.format_exception(exc_type, exc, tb)),
            "source": "process",
        })
        send_sync("/v1/sdk/exception", payload)
    except Exception:
        pass  # never prevent the interpreter from printing its own traceback


def register_exception_hook():
    sys.excepthook = _excepthook


def capture_exception(exc):
    """
    Manually capture and report an exception.

    Uses send_async — this is called from live request/signal context and
    must never block the caller. The payload is queued and delivered by the
    background worker.

    Wrapped in try/except so any internal SDK failure (bad config, payload
    error) is silently swallowed and never propagates to the host app.
    """
    try:
        payload = base_payload()
        payload.update({
            "signal_type": "exception",
            "status_code": 500,
            "error_type": type(exc).__name__,
            "message": str(exc),
            "stack_trace": "".join(
                traceback.format_exception(type(exc), exc, exc.__traceback__)
            ),
            "source": "manual",
        })
        send_async("/v1/sdk/exception", payload)
    except Exception:
        pass
