"""Internal helpers for consumer-friendly error messages.

The strategic review (v0.5 Phase A) identified the #1 adoption friction
as **error attribution**: when ``mgf-common`` raises, the stack trace
points inside the library, leaving the consumer to guess what THEY
did wrong. This module ships internal helpers that wrap common error
paths with PEP 678 ``__notes__`` — short, actionable hints that name
the env var, point at the doc page, or suggest the fix.

Public helpers consumers MAY use:

- :func:`add_help_note` — attach a hint to any exception in flight
  via :pep:`678` notes. Useful for project-specific exception
  augmentation.

Internal helpers (underscore-prefixed in their declaring module):

- :func:`_wrap_pydantic_validation_error` — convert pydantic
  ``ValidationError`` into :class:`AppConfigError` with one note per
  field, naming the env var the consumer most likely needs to fix.

Discipline: every helper preserves the original exception via
``__cause__`` so debuggers + traceback printers show the full chain.
The friendly wrapper is added as the *outer* exception; the original
is preserved underneath.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pydantic import ValidationError

    from mgf.common.exceptions import AppConfigError as _AppConfigErrorType


def add_help_note(
    exc: BaseException,
    hint: str,
    *,
    doc_ref: str = "",
) -> None:
    """Append a help note to ``exc``'s :pep:`678` ``__notes__`` list.

    ``hint`` is a one-sentence actionable tip (e.g.,
    ``"Set MYAPP_DB_URL or pass db_url=... to the settings constructor."``).
    ``doc_ref`` is an optional path to a doc page or a CLI command
    that helps further (e.g.,
    ``"docs/reference/config.md"`` or ``"mgf-common explain settings.db_url"``).

    Mutates ``exc`` in-place. Safe to call multiple times — each call
    appends a new note. Notes survive ``raise ... from ...`` chains
    and show up in the standard traceback printer (CPython 3.11+).

    Example::

        try:
            cls(**values)
        except ValidationError as exc:
            add_help_note(
                exc,
                "Set MYAPP_DB_URL or pass db_url=... to the constructor.",
                doc_ref="docs/reference/config.md",
            )
            raise
    """
    note = hint
    if doc_ref:
        note = f"{hint} See: {doc_ref}"
    if not hasattr(exc, "__notes__"):
        # PEP 678 was added in 3.11; we ship 3.11+ but be defensive.
        exc.__notes__ = []
    exc.__notes__.append(note)


def _wrap_pydantic_validation_error(
    exc: ValidationError,
    *,
    settings_class_name: str,
    env_prefix: str,
) -> _AppConfigErrorType:
    """Convert pydantic ``ValidationError`` → ``AppConfigError`` with notes.

    Internal — called by :class:`mgf.common.config.BaseAppSettings.__init__`
    when the underlying pydantic constructor raises. Produces a
    consumer-friendly error that names the env var most likely
    responsible (``<env_prefix><FIELD>``) for each failed field.

    Returns the wrapped exception ready to ``raise ... from exc``.
    """
    from mgf.common.exceptions import AppConfigError

    errors = exc.errors()
    field_paths: list[str] = []
    notes: list[str] = []
    for err in errors:
        loc = err.get("loc", ())
        if not loc:
            continue
        field_path = ".".join(str(p) for p in loc)
        field_paths.append(field_path)

        # Top-level field → env var name.
        top_field = str(loc[0]).upper()
        env_var = f"{env_prefix}{top_field}"
        msg = err.get("msg", "validation failed")
        type_ = err.get("type", "")
        if type_ == "missing":
            notes.append(
                f"Field {field_path!r}: missing. Set env var "
                f"{env_var}=... or pass {field_path}=... to the "
                f"constructor."
            )
        else:
            notes.append(
                f"Field {field_path!r}: {msg}. Check env var "
                f"{env_var} (current value may be malformed)."
            )

    summary = ", ".join(field_paths) if field_paths else "validation failed"
    wrapped = AppConfigError(f"{settings_class_name}: invalid configuration ({summary})")
    for note in notes:
        add_help_note(wrapped, note)
    if env_prefix:
        add_help_note(
            wrapped,
            f"Env vars are prefixed {env_prefix!r}; run `mgf-common explain` "
            f"to see resolved values.",
        )
    return wrapped


__all__ = ["add_help_note"]
