""":class:`DjangoSettingsBridge` — read-only view over ``django.conf.settings``.

Exposes a pydantic-validated subset of Django's global settings
object as a :class:`BaseAppSettings`-compatible object so
``mgf-common explain settings.X`` works for Django apps.

The bridge is **read-only**. It snapshots the configured Django
settings at construction time; later mutation of
``django.conf.settings`` (rare in production, occasional in tests
via ``override_settings``) is not reflected in the bridge.
Construct a fresh bridge if you need a fresh snapshot.
"""

from __future__ import annotations

from typing import Any

from mgf.common.config import BaseAppSettings, settings_config

from mgf.django._errors import DjangoSettingTypeError


class DjangoSettingsBridge(BaseAppSettings):
    """Read-only :class:`BaseAppSettings` view over Django settings.

    Exposes a curated subset of ``django.conf.settings`` keys —
    those most useful to ``mgf-common explain`` and to consumers
    who want a typed, validated read of the live Django config.

    Construct from the live Django settings::

        from mgf.django import DjangoSettingsBridge

        bridge = DjangoSettingsBridge.from_django()
        print(bridge.debug)
        print(bridge.secret_key_present)

    The fields exposed are deliberately narrow — adding a new key
    to the bridge is an additive contract change. Consumers who
    need a key not exposed here should read
    ``django.conf.settings.X`` directly.

    Fields:
        debug: Django ``DEBUG``.
        allowed_hosts: Tuple of allowed hosts.
        installed_apps: Tuple of installed app dotted paths.
        time_zone: Django ``TIME_ZONE``.
        use_tz: Django ``USE_TZ``.
        language_code: Django ``LANGUAGE_CODE``.
        secret_key_present: Whether ``SECRET_KEY`` is non-empty
            (the key itself is NEVER in the bridge — too sensitive
            to ever fall into ``mgf-common explain`` output).
        mgf_app_name: ``MGF_COMMON_APP_NAME`` if set.
        mgf_app_version: ``MGF_COMMON_APP_VERSION`` if set.
    """

    model_config = settings_config(
        env_prefix="DJANGO_BRIDGE_",
        extra="ignore",
    )

    debug: bool = False
    allowed_hosts: tuple[str, ...] = ()
    installed_apps: tuple[str, ...] = ()
    time_zone: str = "UTC"
    use_tz: bool = True
    language_code: str = "en-us"
    secret_key_present: bool = False
    mgf_app_name: str | None = None
    mgf_app_version: str | None = None

    @classmethod
    def from_django(cls) -> DjangoSettingsBridge:
        """Construct a bridge by snapshotting ``django.conf.settings``.

        Reads the configured Django settings once and returns a
        validated :class:`DjangoSettingsBridge` instance.
        """
        from django.conf import settings

        secret_key = getattr(settings, "SECRET_KEY", "") or ""
        return cls(
            debug=bool(getattr(settings, "DEBUG", False)),
            allowed_hosts=tuple(getattr(settings, "ALLOWED_HOSTS", ())),
            installed_apps=tuple(getattr(settings, "INSTALLED_APPS", ())),
            time_zone=str(getattr(settings, "TIME_ZONE", "UTC")),
            use_tz=bool(getattr(settings, "USE_TZ", True)),
            language_code=str(getattr(settings, "LANGUAGE_CODE", "en-us")),
            secret_key_present=bool(secret_key),
            mgf_app_name=_str_or_none(getattr(settings, "MGF_COMMON_APP_NAME", None)),
            mgf_app_version=_str_or_none(getattr(settings, "MGF_COMMON_APP_VERSION", None)),
        )


def _str_or_none(value: Any) -> str | None:
    """Accept ``str | None`` from Django settings; reject other types.

    RA-04: previously this called ``str(value)`` on any non-None
    input, so a misconfigured ``MGF_COMMON_APP_NAME = ["foo"]``
    silently became ``"['foo']"`` in the bridge. That defeats
    pydantic's validation at the construction boundary (CF-06).

    The bridge now requires the consumer to use a ``str`` (or
    ``None``); any other type raises ``TypeError`` with the
    setting's type name so the misconfiguration fails loud at
    app-config time rather than poisoning downstream consumers.
    """
    if value is None:
        return None
    if isinstance(value, str):
        return value
    raise DjangoSettingTypeError(
        f"Django setting must be a str or None; got "
        f"{type(value).__name__}. RA-04: tighten the setting in "
        f"settings.py to a str literal (or remove it to use the "
        f"default).",
        received_type=type(value).__name__,
    )
