""":class:`MgfCommonConfig` — Django ``AppConfig`` that bootstraps mgf-common.

Add ``"mgf.django"`` to ``INSTALLED_APPS`` and Django calls
:meth:`AppConfig.ready` once during app-registry build. The handler
runs :func:`mgf.common.bootstrap` exactly once per process via a
module-level guard.

Configure via Django settings (read at ``ready()`` time):

  - ``MGF_COMMON_APP_NAME`` — passed to :func:`bootstrap`.
  - ``MGF_COMMON_APP_VERSION`` — passed to :func:`bootstrap`.

Both default to ``None`` (the bootstrap helper auto-derives from
package metadata).

The bootstrap context manager is held open on a module-level
:class:`ExitStack` for the process lifetime; an :func:`atexit` hook
unwinds it cleanly so OTel flushes, log handlers detach, and crash
hooks reset.
"""

from __future__ import annotations

import atexit
import threading
from contextlib import ExitStack

from django.apps import AppConfig

# Module-level guard. Django re-creates the app registry between
# tests when ``override_settings(INSTALLED_APPS=...)`` is used, but
# the same Python process is reused — so a one-shot guard is right.
_BOOTSTRAP_LOCK = threading.Lock()
_BOOTSTRAP_STACK: ExitStack | None = None


class MgfCommonConfig(AppConfig):
    """Django ``AppConfig`` that runs :func:`mgf.common.bootstrap`.

    Add to ``INSTALLED_APPS``::

        INSTALLED_APPS = [
            # ... your apps ...
            "mgf.django",
        ]

    Settings (all optional)::

        MGF_COMMON_APP_NAME = "myapp"
        MGF_COMMON_APP_VERSION = "1.0.0"

    The bootstrap call is idempotent — calling :meth:`ready` twice
    in the same process is a no-op (Django doesn't normally do this,
    but safer to be defensive than to risk double-bootstrap on
    runserver --noreload combinations).
    """

    name = "mgf.django"

    # Django app labels can't contain dots. Pick something stable +
    # unlikely to collide with consumer app labels. Renamed at the
    # v0.31 sibling extraction from ``mgf_common`` to ``mgf_django``
    # to match the new module path; consumer config keying off the
    # old label MUST update.
    label = "mgf_django"

    verbose_name = "mgf-common Django integration (mgf-django sibling)"

    def ready(self) -> None:
        """Bootstrap mgf-common exactly once per process."""
        from mgf.common import bootstrap

        from django.conf import settings

        global _BOOTSTRAP_STACK

        with _BOOTSTRAP_LOCK:
            if _BOOTSTRAP_STACK is not None:
                # Already bootstrapped — Django re-imported the app
                # config (test override_settings, autoreload, etc.).
                return

            app_name = getattr(settings, "MGF_COMMON_APP_NAME", None)
            app_version = getattr(settings, "MGF_COMMON_APP_VERSION", None)

            stack = ExitStack()
            stack.enter_context(
                bootstrap(
                    app_name=app_name,
                    app_version=app_version,
                ),
            )
            _BOOTSTRAP_STACK = stack
            atexit.register(_cleanup_bootstrap)


def _cleanup_bootstrap() -> None:
    """Unwind the bootstrap stack at process exit.

    Called via :func:`atexit.register` so OTel flushes, log handlers
    detach, and crash hooks reset cleanly. Tests can call this
    directly to reset state between cases.
    """
    global _BOOTSTRAP_STACK

    with _BOOTSTRAP_LOCK:
        if _BOOTSTRAP_STACK is None:
            return
        try:
            _BOOTSTRAP_STACK.close()
        finally:
            _BOOTSTRAP_STACK = None
