"""``mgf-fed validate <config.yaml>`` — schema-validate a YAML file.

Usage::

    mgf-fed validate ~/.config/myapp/config.yaml
    mgf-fed validate ./mgf-config.yaml --app myapp --version 1.0

Loads ``<config.yaml>`` with :func:`yaml.safe_load`, validates it
against :class:`mgf.common.settings.MgfSettings`, and prints a
pass/fail report. On success, dumps the resolved active
configuration in the same shape as :func:`mgf-fed explain` so
the operator sees the post-preset, post-default state.

Exit codes:
  - ``0`` — schema valid + dump succeeded.
  - ``EXIT_CONFIG_ERROR (1)`` — schema validation failed; per-error
    report on stderr.
  - ``EXIT_OPERATION_ERROR (2)`` — file unreadable or YAML parse error.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any

import yaml
from mgf.common.cli import (
    EXIT_CONFIG_ERROR,
    EXIT_OPERATION_ERROR,
    EXIT_SUCCESS,
)
from mgf.common.cli._provenance import field_provenance
from mgf.common.settings import MgfSettings
from pydantic import ValidationError

from mgf.fed.cli._explain import _format_source, _format_value, _render_section

if TYPE_CHECKING:
    import io


def validate(
    config_path: Path,
    *,
    app_name: str | None = None,
    app_version: str | None = None,
    out: io.TextIOBase | None = None,
    err: io.TextIOBase | None = None,
) -> int:
    """Validate ``config_path`` against MgfSettings; render the result.

    Returns the exit code. NEVER raises — every failure path is
    converted to an exit code + a stderr message.
    """
    if out is None or err is None:
        import sys

        if out is None:
            out = sys.stdout  # type: ignore[assignment]
        if err is None:
            err = sys.stderr  # type: ignore[assignment]
    assert out is not None and err is not None  # type narrowing

    # File / parse errors → EXIT_OPERATION_ERROR.
    try:
        text = config_path.read_text(encoding="utf-8")
    except OSError as e:
        print(f"✗ failed to read {config_path}: {e}", file=err)
        return EXIT_OPERATION_ERROR

    try:
        loaded = yaml.safe_load(text)
    except yaml.YAMLError as e:
        print(f"✗ YAML parse error in {config_path}: {e}", file=err)
        return EXIT_OPERATION_ERROR

    if loaded is None:
        loaded = {}
    if not isinstance(loaded, dict):
        print(
            f"✗ {config_path}: top-level YAML node MUST be a mapping (got {type(loaded).__name__})",
            file=err,
        )
        return EXIT_CONFIG_ERROR

    # Schema validation.
    # v0.5 Phase C: BaseAppSettings.__init__ wraps pydantic
    # ValidationError into AppConfigError; recover the original via
    # __cause__ for field-by-field formatting.
    from mgf.common.exceptions import AppConfigError as _AppConfigError

    try:
        settings = MgfSettings.model_validate(loaded)
    except (ValidationError, _AppConfigError) as e:
        ve: ValidationError | None
        if isinstance(e, ValidationError):
            ve = e
        elif isinstance(e.__cause__, ValidationError):
            ve = e.__cause__
        else:
            ve = None

        print(f"✗ Schema validation FAILED for {config_path}", file=err)
        print(file=err)
        if ve is not None:
            for err_dict in ve.errors():
                loc = ".".join(str(p) for p in err_dict["loc"])
                msg = err_dict["msg"]
                print(f"  {loc}: {msg}", file=err)
            print(file=err)
            print(
                f"  ({len(ve.errors())} validation "
                f"{'error' if len(ve.errors()) == 1 else 'errors'})",
                file=err,
            )
        else:
            print(f"  {e}", file=err)
        return EXIT_CONFIG_ERROR

    # Success — header + per-section dump.
    from mgf.common import __version__ as lib_version

    label_app = app_name or "(unspecified)"
    label_version = app_version or "(unspecified)"

    print(f"✓ Schema valid (MgfSettings v{settings.version})", file=out)
    print("✓ All field constraints pass", file=out)
    print("✓ No unknown keys", file=out)
    print(file=out)
    print(
        f"mgf-common v{lib_version}  resolved configuration from "
        f"{config_path} for {label_app} v{label_version}",
        file=out,
    )
    print(file=out)

    provenance = field_provenance(settings, file_overrides=loaded)
    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)

    print("[meta]", file=out)
    for field in ("preset", "version"):
        value = getattr(settings, field)
        src = provenance["_root"][field]
        rendered_value = _format_value(field, value)
        annotation = _format_source(src)
        print(f"  {field:25} {rendered_value:25} {annotation}", file=out)

    return EXIT_SUCCESS


def add_validate_args(subparser: Any) -> None:
    """Wire up the ``validate`` subcommand to an argparse subparser."""
    subparser.add_argument(
        "config_path",
        type=Path,
        help="Path to the YAML config file to validate.",
    )
    subparser.add_argument(
        "--app",
        dest="app_name",
        default=None,
        help="App name to display in the header.",
    )
    subparser.add_argument(
        "--version",
        dest="app_version",
        default=None,
        help="App version to display in the header.",
    )


def run_validate(args: Any) -> int:
    """Argparse-callback wiring for the ``validate`` subcommand."""
    return validate(
        args.config_path,
        app_name=args.app_name,
        app_version=args.app_version,
    )


__all__ = [
    "add_validate_args",
    "run_validate",
    "validate",
]
