"""``mgf-fed explain`` — render the active settings with provenance.

Usage::

    mgf-fed explain --app myapp --version 1.0
    mgf-fed explain --preset systemd --app myapp --version 1.0

Reads :class:`mgf.common.settings.MgfSettings` (no config file —
``validate`` does that) plus every ``MGF_*`` env var that
pydantic-settings would consult, applies the named preset, and
prints a human-readable dump where every field is annotated with
its source (``default`` / ``env`` / ``preset`` / ``init``).

The output is the canonical "where did this value come from?"
answer for an operator who is troubleshooting why their
configuration didn't take effect. The second canonical use case is
"document the live config so a bug report can include it"; we
redact secrets-shaped fields at the boundary.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from mgf.common import app_name as _read_app_name
from mgf.common import app_version as _read_app_version
from mgf.common.cli._provenance import (
    FieldSource,
    env_vars_known_to_mgf,
    field_provenance,
)
from mgf.common.settings import MgfSettings

if TYPE_CHECKING:
    import io

# Field names whose values MUST be redacted in the explain output —
# the explain output is meant to be safe to paste into a bug report.
# The list is conservative: any field whose name suggests it could
# carry a credential.
_REDACT_FIELDS: frozenset[str] = frozenset(
    {
        "sentry_dsn",
        "passphrase",
        "http_url",  # crash sink webhook may carry a secret token
        "syslog_address",  # may include credentials in URL form
        "http_sink",  # log webhook may carry a secret token
    }
)

_REDACTED = "***REDACTED***"


def explain(
    *,
    app_name: str | None = None,
    app_version: str | None = None,
    preset: str | None = None,
    out: io.TextIOBase | None = None,
) -> int:
    """Render the active settings to ``out`` (default ``sys.stdout``).

    Returns the exit code (always ``0`` — explain never fails to
    render; a malformed env var would already have prevented
    :class:`MgfSettings` from constructing).

    Parameters
    ----------
    app_name, app_version
        Identity to display in the header. ``None`` falls back
        to :func:`mgf.common.app_name` / :func:`mgf.common.app_version`.
        The explain subcommand does NOT call ``bootstrap()`` —
        it does not want to install excepthooks or wire logging
        for what is effectively a print statement — so it reads
        identity directly from the cache.
    preset
        Override the preset (``--preset`` CLI flag). When
        provided the value is reported as ``"init"`` provenance.
    out
        Stream to write to. Defaults to ``sys.stdout``.
    """
    if out is None:
        import sys

        out = sys.stdout  # type: ignore[assignment]
    assert out is not None  # type narrowing for mypy

    init_overrides: dict[str, Any] = {}
    if preset is not None:
        init_overrides["preset"] = preset

    settings = MgfSettings(**init_overrides) if init_overrides else MgfSettings()
    provenance = field_provenance(settings, init_overrides=init_overrides)

    label_app = app_name or _read_app_name()
    label_version = app_version or _read_app_version()

    from mgf.common import __version__ as lib_version

    print(
        f"mgf-common v{lib_version}  active configuration for {label_app} v{label_version}",
        file=out,
    )
    print(file=out)

    for sub_name in ("logging", "otel", "crash", "redaction", "vault"):
        sub_model = getattr(settings, sub_name)
        sub_prov = provenance[sub_name]
        _render_section(sub_name, sub_model, sub_prov, out=out)

    # Top-level scalars get their own [meta] section at the end.
    print("[meta]", file=out)
    for field in ("preset", "version"):
        value = getattr(settings, field)
        src = provenance["_root"][field]
        _render_field(field, value, src, out=out)
    print(file=out)

    print("Environment variables read by mgf-common:", file=out)
    for env_var, env_value in env_vars_known_to_mgf():
        if env_value is None:
            print(f"  {env_var:40} (unset)", file=out)
        else:
            display = _maybe_redact(env_var, env_value)
            print(f"  {env_var:40} (set: {display})", file=out)

    return 0


def _render_section(
    sub_name: str,
    sub_model: Any,
    sub_prov: dict[str, FieldSource],
    *,
    out: io.TextIOBase,
) -> None:
    print(f"[{sub_name}]", file=out)
    for field_name in type(sub_model).model_fields:
        value = getattr(sub_model, field_name)
        src = sub_prov[field_name]
        _render_field(field_name, value, src, out=out)
    print(file=out)


def _render_field(
    field: str,
    value: Any,
    src: FieldSource,
    *,
    out: io.TextIOBase,
) -> None:
    """One ``  field            value            (source)`` line."""
    rendered_value = _format_value(field, value)
    annotation = _format_source(src)
    print(f"  {field:25} {rendered_value:25} {annotation}", file=out)


def _format_value(field: str, value: Any) -> str:
    if value is None:
        return "(none)"
    if field in _REDACT_FIELDS and value:
        return _REDACTED
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (list, tuple, frozenset, set)):
        if not value:
            return "[]"
        return "[" + ", ".join(str(v) for v in value) + "]"
    if isinstance(value, dict):
        if not value:
            return "{}"
        return "{" + ", ".join(f"{k}={v}" for k, v in value.items()) + "}"
    return str(value)


def _format_source(src: FieldSource) -> str:
    if src.kind == "default":
        return "(default)"
    if src.kind == "env":
        return f"(env: {_maybe_redact_kv(src.detail)})"
    if src.kind == "preset":
        return f"(preset: {src.detail})"
    if src.kind == "init":
        return "(init)"
    if src.kind == "file":
        return "(file)"
    return f"({src.kind})"  # pragma: no cover — exhaustive cases above


def _maybe_redact(env_var: str, value: str) -> str:
    """Redact env values for vars whose name suggests a credential."""
    lower = env_var.lower()
    for needle in _REDACT_FIELDS:
        if needle in lower:
            return _REDACTED
    return value


def _maybe_redact_kv(detail: str) -> str:
    """Redact the value half of an ``ENV_VAR=value`` detail string."""
    if "=" not in detail:
        return detail
    name, _, value = detail.partition("=")
    return f"{name}={_maybe_redact(name, value)}"


def add_explain_args(subparser: Any) -> None:
    """Wire up the ``explain`` subcommand to an argparse subparser.

    Called from :func:`mgf.fed.cli._mgfcli.build_cli`.
    """
    subparser.add_argument(
        "--app",
        dest="app_name",
        default=None,
        help="App name to display in the header (default: read from "
        "the active mgf.common identity, else 'app').",
    )
    subparser.add_argument(
        "--version",
        dest="app_version",
        default=None,
        help="App version to display in the header (default: read "
        "from the active mgf.common identity, else 'unknown').",
    )
    subparser.add_argument(
        "--preset",
        choices=["explicit", "dev", "systemd", "docker", "auto"],
        default=None,
        help="Override the preset; reported as 'init' provenance.",
    )


def run_explain(args: Any) -> int:
    """Argparse-callback wiring for the ``explain`` subcommand."""
    return explain(
        app_name=args.app_name,
        app_version=args.app_version,
        preset=args.preset,
    )


__all__ = [
    "add_explain_args",
    "explain",
    "run_explain",
]
