"""Per-field source attribution for :class:`mgf.common.settings.MgfSettings`.

The diagnostic CLI's ``mgf-common explain`` subcommand wants to
report, for every field on the active settings, *where the value
came from*: a typed default in the schema, an applied preset, an
environment variable, or an explicit init / file argument.

Pydantic-settings does not expose per-field provenance natively;
this module is a small shim that reconstructs it. The strategy:

  1. Re-run :class:`pydantic_settings.sources.EnvSettingsSource`
     to get the env-only dict (the same source pydantic-settings
     used at construction time, called separately so we can ask
     "did the env supply this field?").
  2. Re-run :func:`mgf.common.settings._resolve_preset_overrides`
     to get the preset's would-have-been overrides (the same
     table the preset validator applied, called separately so we
     can ask "did the preset supply this field?").
  3. For each field, walk the precedence ladder
     env → preset → init → default and report the first source
     that supplied a value.

The shim is best-effort — pydantic-settings' precedence is
*defined* by the order of sources, not by per-field metadata, so
two sources supplying the same value cannot be distinguished.
The annotations are correct in every realistic case (env or
preset supplies a value the default would NOT have produced).

Phase 6a's ``__pydantic_fields_set__`` mutation by the preset
validator means preset-applied fields ARE in
``model_fields_set`` — this shim relies on that and returns
``"preset"`` for fields whose value matches the preset table.
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Literal

from pydantic_settings.sources import EnvSettingsSource

from mgf.common.settings import (
    MgfSettings,
    _resolve_preset_overrides,
)

if TYPE_CHECKING:
    from collections.abc import Mapping


SourceKind = Literal["default", "env", "preset", "init", "file"]
"""Where a settings field's value came from.

  - ``default`` — the field's declared default (no env / preset
    / init / file override active).
  - ``env`` — supplied by an environment variable matching the
    schema's prefix + name shape (e.g. ``MGF_LOGGING__LEVEL``).
  - ``preset`` — applied by ``MgfSettings.preset`` semantics
    (``dev`` / ``systemd`` / ``docker`` / ``auto``).
  - ``init`` — supplied as a constructor kwarg
    (``MgfSettings(logging=LoggingSettings(level="DEBUG"))``).
  - ``file`` — loaded from a YAML config file (only set by
    ``mgf-common validate <path>``; ``explain`` does not load
    files).
"""


@dataclass(frozen=True)
class FieldSource:
    """The provenance + a human-readable detail for a single field."""

    kind: SourceKind
    """One of :data:`SourceKind`."""

    detail: str = ""
    """Human-readable extra context. For ``"env"`` this is the env
    var name and value (e.g. ``"MGF_LOGGING__LEVEL=DEBUG"``); for
    ``"preset"`` it is the preset name (e.g. ``"systemd"``); for
    ``"default"`` it is empty (or a hint such as ``"derived from
    app_name"`` for fields that resolve at runtime)."""


def field_provenance(
    settings: MgfSettings,
    *,
    init_overrides: Mapping[str, Any] | None = None,
    file_overrides: Mapping[str, Any] | None = None,
) -> dict[str, dict[str, FieldSource]]:
    """Return per-field provenance for ``settings``.

    Returns ``{sub_name: {field_name: FieldSource}}`` for each
    sub-model on :class:`MgfSettings` (``logging`` / ``otel`` /
    ``crash`` / ``redaction`` / ``vault``) plus a ``"_root"`` key
    containing the top-level scalar fields (``preset`` /
    ``version``).

    Parameters
    ----------
    settings
        The settings instance to introspect. Typically built by
        the explain subcommand via ``MgfSettings()`` (no init
        kwargs) so the env + preset + default ladder is the only
        source of variation.
    init_overrides
        Optional mapping of ``{sub_name: {field: value}}`` that
        the caller passed at construction time (CLI-supplied
        overrides in future, or dict from validate's YAML load).
        Fields appearing here are reported as ``"init"`` (or
        ``"file"`` if ``file_overrides`` is supplied).
    file_overrides
        Same shape as ``init_overrides`` but reported as
        ``"file"``. Used by ``mgf-common validate <path>`` to
        attribute YAML-supplied fields correctly.
    """
    env_source = EnvSettingsSource(MgfSettings)
    env_data = env_source()
    preset_data = _resolve_preset_overrides(settings.preset)

    # v0.4 Phase C: preset can be a list — render as "a+b+c" for the
    # human-readable provenance detail. Single string passes through
    # unchanged.
    preset_label: str = (
        settings.preset if isinstance(settings.preset, str) else "+".join(settings.preset)
    )

    out: dict[str, dict[str, FieldSource]] = {}

    # Sub-models: logging / otel / crash / redaction / vault.
    for sub_name in ("logging", "otel", "crash", "redaction", "vault"):
        sub_model = getattr(settings, sub_name)
        sub_env = env_data.get(sub_name) or {}
        sub_preset = preset_data.get(sub_name) or {}
        sub_init = (init_overrides or {}).get(sub_name) or {}
        sub_file = (file_overrides or {}).get(sub_name) or {}

        out[sub_name] = {}
        for field_name in type(sub_model).model_fields:
            out[sub_name][field_name] = _attribute_field(
                env_value=sub_env.get(field_name) if field_name in sub_env else _MISSING,
                preset_value=sub_preset.get(field_name) if field_name in sub_preset else _MISSING,
                init_value=sub_init.get(field_name) if field_name in sub_init else _MISSING,
                file_value=sub_file.get(field_name) if field_name in sub_file else _MISSING,
                env_var_name=_env_var_for(sub_name, field_name),
                preset_name=preset_label,
            )

    # Top-level scalars: preset, version.
    root: dict[str, FieldSource] = {}
    for field_name in ("preset", "version"):
        env_val = env_data.get(field_name, _MISSING)
        if env_val is not _MISSING:
            root[field_name] = FieldSource(
                "env",
                f"{_env_var_for(None, field_name)}={env_val}",
            )
        elif (init_overrides or {}).get(field_name, _MISSING) is not _MISSING:
            root[field_name] = FieldSource("init")
        elif (file_overrides or {}).get(field_name, _MISSING) is not _MISSING:
            root[field_name] = FieldSource("file")
        else:
            root[field_name] = FieldSource("default")
    out["_root"] = root

    return out


# Sentinel — distinguishes "field not in source" from "field is None".
_MISSING: Any = object()


def _attribute_field(
    *,
    env_value: Any,
    preset_value: Any,
    init_value: Any,
    file_value: Any,
    env_var_name: str,
    preset_name: str,
) -> FieldSource:
    """Walk the precedence ladder and pick the first hit.

    Order matches pydantic-settings' source precedence:
    init > env > file > defaults. Preset is layered AFTER all of
    those (the validator runs ``mode='after'``) but only for
    fields that no other source supplied — so in the provenance
    report, ``preset`` appears between ``env`` and ``default``.
    """
    if init_value is not _MISSING:
        return FieldSource("init")
    if env_value is not _MISSING:
        return FieldSource("env", f"{env_var_name}={env_value}")
    if file_value is not _MISSING:
        return FieldSource("file")
    if preset_value is not _MISSING:
        return FieldSource("preset", preset_name)
    return FieldSource("default")


def _env_var_for(sub_name: str | None, field_name: str) -> str:
    """Reconstruct the env-var name for ``sub_name.field_name``.

    Uses :class:`MgfSettings`'s configured env_prefix + nested
    delimiter (currently ``MGF_`` + ``__``). Always uppercase.
    """
    config = MgfSettings.model_config
    prefix = config.get("env_prefix", "") or ""
    delim = config.get("env_nested_delimiter", "") or ""

    if sub_name is None:
        return f"{prefix}{field_name.upper()}"
    return f"{prefix}{sub_name.upper()}{delim}{field_name.upper()}"


def env_vars_known_to_mgf() -> list[tuple[str, str | None]]:
    """List every env var :class:`MgfSettings` would consult.

    Returns ``[(env_var_name, current_value | None), ...]`` for
    the ``preset`` field plus every nested
    ``MGF_<SUB>__<FIELD>`` triple. Used by the explain
    subcommand's "Environment variables read by mgf-common"
    footer.

    Sorted alphabetically for deterministic output.
    """
    out: list[tuple[str, str | None]] = []

    out.append(
        (_env_var_for(None, "preset"), os.environ.get(_env_var_for(None, "preset"))),
    )

    for sub_name in ("logging", "otel", "crash", "redaction", "vault"):
        sub_cls = MgfSettings.model_fields[sub_name].annotation
        for field_name in sub_cls.model_fields:  # type: ignore[union-attr]
            env_var = _env_var_for(sub_name, field_name)
            out.append((env_var, os.environ.get(env_var)))

    return sorted(out, key=lambda pair: pair[0])


__all__ = [
    "FieldSource",
    "SourceKind",
    "env_vars_known_to_mgf",
    "field_provenance",
]
