"""Tests for :class:`mgf.common.versioned.VersionedFileStore`.

Covers:

- Round-trip: save → load preserves the document.
- Atomic write: a crash mid-write doesn't leave a corrupt destination.
- Schema rejection: schema without ``extra='forbid'`` raises early.
- Schema rejection: schema without the version field raises early.
- Missing file: ``load()`` raises with a PEP 678 hint.
- Malformed file: ``load()`` raises with a PEP 678 hint.
- Missing version field on disk: ``load()`` raises.
- Wrong type for version field on disk: ``load()`` raises.
- Version mismatch + no migrate: raises with a PEP 678 hint.
- Migration: single-step. Migration: multi-step (consumer chains internally).
- Migration callback raises: store wraps in AppConfigError.
- Validation failure: each pydantic error becomes a PEP 678 note.
- Save stamps current_version: even when the model carries an old one.
- Format auto-detection: yaml + json suffixes.
- Format override: explicit ``file_format=`` wins.
- Unknown extension + no override: raises early.
- Unknown keys on disk: rejected with a clear message.
- ``exists()`` returns the truth.
- ``path`` property exposes the original path.
- The destination dir is created on save when missing.
- Wrong type passed to save: raises.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import pytest
from pydantic import BaseModel, ConfigDict

from mgf.common.exceptions import AppConfigError, AppConfigErrorKind
from mgf.common.versioned import VersionedFileStore

if TYPE_CHECKING:
    from pathlib import Path

# ---------------------------------------------------------------------------
# Fixtures + sample schemas
# ---------------------------------------------------------------------------


class _Hook(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)
    name: str
    command: str


class HooksFile(BaseModel):
    """Sample document schema with a typed ``version`` field."""

    model_config = ConfigDict(extra="forbid", frozen=True)
    version: int
    hooks: list[_Hook] = []


class CaseFile(BaseModel):
    """Sample document with a non-default version-field name."""

    model_config = ConfigDict(extra="forbid", frozen=True)
    schema_version: int
    case_id: str


class _LooseFile(BaseModel):
    """Schema WITHOUT extra='forbid' — used to verify early rejection."""

    version: int
    payload: str = ""


class _NoVersionField(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)
    payload: str = ""


@pytest.fixture
def hooks_yaml_path(tmp_path: Path) -> Path:
    return tmp_path / "hooks.yaml"


@pytest.fixture
def hooks_json_path(tmp_path: Path) -> Path:
    return tmp_path / "hooks.json"


# ---------------------------------------------------------------------------
# Constructor validation
# ---------------------------------------------------------------------------


class TestSchemaContractValidation:
    def test_schema_without_extra_forbid_raises_early(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        with pytest.raises(AppConfigError, match="extra='forbid'"):
            VersionedFileStore(
                hooks_yaml_path,
                schema=_LooseFile,
                current_version=1,
            )

    def test_schema_without_version_field_raises_early(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        with pytest.raises(AppConfigError, match="no field 'version'"):
            VersionedFileStore(
                hooks_yaml_path,
                schema=_NoVersionField,
                current_version=1,
            )

    def test_unknown_extension_without_format_override_raises(
        self,
        tmp_path: Path,
    ) -> None:
        with pytest.raises(AppConfigError, match="cannot auto-detect format"):
            VersionedFileStore(
                tmp_path / "hooks.weird",
                schema=HooksFile,
                current_version=1,
            )

    def test_unknown_extension_with_explicit_format_works(
        self,
        tmp_path: Path,
    ) -> None:
        store = VersionedFileStore(
            tmp_path / "hooks.weird",
            schema=HooksFile,
            current_version=1,
            file_format="yaml",
        )
        assert store.path == tmp_path / "hooks.weird"


# ---------------------------------------------------------------------------
# Round-trip
# ---------------------------------------------------------------------------


class TestRoundTrip:
    def test_yaml_round_trip(self, hooks_yaml_path: Path) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        original = HooksFile(
            version=1,
            hooks=[
                _Hook(name="post-deploy", command="echo deployed"),
                _Hook(name="post-test", command="echo tested"),
            ],
        )
        store.save(original)
        assert hooks_yaml_path.exists()
        loaded = store.load()
        assert loaded == original

    def test_json_round_trip(self, hooks_json_path: Path) -> None:
        store = VersionedFileStore(
            hooks_json_path,
            schema=HooksFile,
            current_version=1,
        )
        original = HooksFile(
            version=1,
            hooks=[_Hook(name="x", command="y")],
        )
        store.save(original)
        assert hooks_json_path.read_text().startswith("{")
        loaded = store.load()
        assert loaded == original

    def test_save_creates_parent_directory(self, tmp_path: Path) -> None:
        path = tmp_path / "nested" / "deeply" / "hooks.yaml"
        store = VersionedFileStore(
            path,
            schema=HooksFile,
            current_version=1,
        )
        store.save(HooksFile(version=1))
        assert path.exists()

    def test_save_writes_mode_0o600(self, hooks_yaml_path: Path) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        store.save(HooksFile(version=1))
        mode = hooks_yaml_path.stat().st_mode & 0o777
        assert mode == 0o600

    def test_alternate_version_field_round_trip(self, tmp_path: Path) -> None:
        path = tmp_path / "case.yaml"
        store = VersionedFileStore(
            path,
            schema=CaseFile,
            current_version=1,
            version_field="schema_version",
        )
        original = CaseFile(schema_version=1, case_id="case-001")
        store.save(original)
        assert store.load() == original


# ---------------------------------------------------------------------------
# Save semantics
# ---------------------------------------------------------------------------


class TestSaveSemantics:
    def test_save_stamps_current_version_overwriting_stale(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        """Even if the model carries version=1 and the store says
        current_version=5, the file on disk is stamped 5."""
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=5,
        )
        # Construct with an old version on the model.
        stale = HooksFile(version=1, hooks=[_Hook(name="x", command="y")])
        store.save(stale)
        raw = _read_yaml(hooks_yaml_path)
        assert raw["version"] == 5

    def test_save_rejects_wrong_type(self, hooks_yaml_path: Path) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="expected HooksFile"):
            store.save("not a model")  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# Atomic write
# ---------------------------------------------------------------------------


class TestAtomicWrite:
    def test_no_temp_file_remains_after_successful_save(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        store.save(HooksFile(version=1))
        # No leftover temp files in the parent directory.
        siblings = list(hooks_yaml_path.parent.iterdir())
        assert siblings == [hooks_yaml_path]

    def test_existing_file_unchanged_when_serialise_raises(
        self,
        hooks_yaml_path: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        original = HooksFile(version=1, hooks=[_Hook(name="x", command="y")])
        store.save(original)
        original_bytes = hooks_yaml_path.read_bytes()

        # Force the serialiser to raise mid-write.
        def _boom(*_a: Any, **_kw: Any) -> str:
            raise OSError("disk full")

        monkeypatch.setattr(store, "_serialize", _boom)

        with pytest.raises(AppConfigError, match="could not be saved"):
            store.save(HooksFile(version=1))

        # Original file unchanged; no temp file left behind.
        assert hooks_yaml_path.read_bytes() == original_bytes
        siblings = list(hooks_yaml_path.parent.iterdir())
        assert siblings == [hooks_yaml_path]


# ---------------------------------------------------------------------------
# Load failures
# ---------------------------------------------------------------------------


class TestLoadFailures:
    def test_missing_file_raises_with_help_note(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert "not found" in str(info.value)
        assert any("if store.exists()" in n for n in info.value.__notes__)

    def test_malformed_yaml_raises_with_help_note(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        hooks_yaml_path.write_text("not: valid: yaml:\n  - [unclosed\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="malformed"):
            store.load()

    def test_malformed_json_raises(self, hooks_json_path: Path) -> None:
        hooks_json_path.write_text("{not valid json}")
        store = VersionedFileStore(
            hooks_json_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="malformed"):
            store.load()

    def test_root_not_a_mapping_raises(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("- one\n- two\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="must be a mapping"):
            store.load()

    def test_missing_version_field_raises(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        hooks_yaml_path.write_text("hooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="missing a typed 'version'"):
            store.load()

    def test_non_int_version_raises(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("version: 'one'\nhooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="missing a typed 'version'"):
            store.load()

    def test_unknown_keys_rejected(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\nrogue: 'unknown key'\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="failed schema validation"):
            store.load()


# ---------------------------------------------------------------------------
# Migration
# ---------------------------------------------------------------------------


class TestMigration:
    def test_version_mismatch_without_migrate_raises_with_help(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert "version=1" in str(info.value)
        assert any("migrate=" in n for n in info.value.__notes__)

    def test_single_step_migration_succeeds(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # v1 had `cmd` instead of `command`.
        hooks_yaml_path.write_text("version: 1\nhooks:\n  - {name: x, cmd: y}\n")

        def migrate(raw: Any, from_version: int) -> Any:
            assert from_version == 1
            return {
                **raw,
                "hooks": [{"name": h["name"], "command": h["cmd"]} for h in raw["hooks"]],
            }

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
        )
        loaded = store.load()
        assert list(loaded.hooks) == [_Hook(name="x", command="y")]

    def test_multi_step_migration_consumer_chained(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # On-disk v1 → consumer's migrate handles v1 → v2 → v3 chain.
        hooks_yaml_path.write_text("version: 1\nhooks:\n  - {name: x, cmd: y}\n")

        def migrate(raw: Any, from_version: int) -> Any:
            data = dict(raw)
            if from_version <= 1:
                # v1 → v2: rename cmd → command.
                data = {
                    **data,
                    "hooks": [{"name": h["name"], "command": h["cmd"]} for h in data["hooks"]],
                }
            if from_version <= 2:
                # v2 → v3: no schema change in this fixture; pass.
                pass
            return data

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=3,
            migrate=migrate,
        )
        loaded = store.load()
        assert len(loaded.hooks) == 1
        assert loaded.hooks[0].command == "y"

    def test_migrate_callback_raising_wraps_in_app_config_error(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")

        def migrate(raw: Any, from_version: int) -> Any:
            raise RuntimeError("kaboom")

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
        )
        with pytest.raises(AppConfigError, match="migrate callback raised"):
            store.load()

    def test_migrate_returning_invalid_payload_validates_loudly(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")

        def migrate(raw: Any, from_version: int) -> Any:
            # Inject a rogue key that should fail extra='forbid'.
            return {**raw, "rogue": "x"}

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
        )
        with pytest.raises(AppConfigError, match="failed schema validation"):
            store.load()


# ---------------------------------------------------------------------------
# Properties + helpers
# ---------------------------------------------------------------------------


class TestStoreProperties:
    def test_path_property(self, hooks_yaml_path: Path) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        assert store.path == hooks_yaml_path

    def test_exists_returns_false_when_missing(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        assert store.exists() is False

    def test_exists_returns_true_after_save(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        store.save(HooksFile(version=1))
        assert store.exists() is True


# ---------------------------------------------------------------------------
# tolerate_missing_version (PAPER-09 — grace mode for legacy files)
# ---------------------------------------------------------------------------


class TestTolerateMissingVersion:
    """Opt-in grace mode for consumers with pre-versioning legacy files.

    Default (False) keeps today's strict-by-default contract.
    Opt-in (True) tolerates a missing version field with a single
    WARNING; the next save() stamps the file.
    """

    def test_default_still_rejects_missing_version(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # Sanity: tolerate_missing_version=False (the default) still
        # raises today's error. This is the strict-by-default contract
        # — fresh consumers benefit.
        hooks_yaml_path.write_text("hooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError, match="missing a typed 'version'"):
            store.load()

    def test_grace_mode_tolerates_missing_version_with_warning(
        self,
        hooks_yaml_path: Path,
        caplog: pytest.LogCaptureFixture,
    ) -> None:
        # Legacy file (no version field) loads successfully when
        # tolerate_missing_version=True; one WARNING is emitted.
        hooks_yaml_path.write_text("hooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            tolerate_missing_version=True,
        )
        with caplog.at_level("WARNING", logger="mgf.common.versioned"):
            doc = store.load()
        assert isinstance(doc, HooksFile)
        # The injected version is the constructor's current_version.
        assert doc.version == 2
        warnings = [
            r
            for r in caplog.records
            if r.name == "mgf.common.versioned" and r.levelname == "WARNING"
        ]
        assert len(warnings) == 1
        # LG-15/16: assert on the structured fields, not the prose
        # message text — message wording is allowed to drift; the
        # event name + extras are the alert-target contract.
        record = warnings[0]
        assert getattr(record, "event", None) == "versioned.grace_mode"
        assert getattr(record, "version_field", None) == "version"
        assert getattr(record, "tolerated_as_version", None) == 2
        assert getattr(record, "audit", None) is True

    def test_grace_mode_does_not_call_migrate_for_missing_version(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # Spec: missing version → treat as current_version, skip
        # migrate. A migrate callback registered for upgrades from
        # OTHER versions stays uncalled when the file is "missing".
        hooks_yaml_path.write_text("hooks: []\n")
        migrate_calls: list[tuple[Any, int]] = []

        def migrate(raw: Any, from_version: int) -> Any:
            migrate_calls.append((raw, from_version))
            return raw

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
            tolerate_missing_version=True,
        )
        store.load()
        assert migrate_calls == [], "migrate must NOT run when the version field was missing"

    def test_grace_mode_still_calls_migrate_for_real_version_mismatch(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # Spec: tolerate_missing_version=True does NOT disable
        # migration for files that DO carry an older version. The
        # consumer mixes "missing means current" with real upgrades.
        hooks_yaml_path.write_text("version: 1\nhooks:\n  - {name: x, cmd: y}\n")

        def migrate(raw: Any, from_version: int) -> Any:
            assert from_version == 1
            return {
                **raw,
                "version": 2,
                "hooks": [{"name": h["name"], "command": h["cmd"]} for h in raw.get("hooks", [])],
            }

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
            tolerate_missing_version=True,
        )
        doc = store.load()
        assert doc.version == 2
        assert len(doc.hooks) == 1
        assert doc.hooks[0].command == "y"

    def test_grace_mode_save_stamps_current_version(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # After a grace-mode load + save round-trip, the file ends up
        # with version stamped. The next load is a normal load — the
        # WARNING fires at most once per file's lifetime.
        hooks_yaml_path.write_text("hooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            tolerate_missing_version=True,
        )
        doc = store.load()
        store.save(doc)

        on_disk = _read_yaml(hooks_yaml_path)
        assert on_disk["version"] == 2

    def test_grace_mode_does_not_tolerate_wrong_type(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        # Spec: tolerate ONLY when the field is absent. Present-but-
        # wrong-type is a corruption signal and still raises even
        # when the flag is True.
        hooks_yaml_path.write_text("version: 'one'\nhooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
            tolerate_missing_version=True,
        )
        with pytest.raises(AppConfigError, match="missing a typed 'version'"):
            store.load()


# ---------------------------------------------------------------------------
# AppConfigError.kind discriminator (PAPER-11 — typed error_kind)
# ---------------------------------------------------------------------------


class TestErrorKind:
    """Every load-path raise sets a typed `kind` so consumers can
    discriminate without string-matching on private message format.

    Pinning each kind value here is the contract: any reword of a
    message text MUST NOT change the `kind` value, and renaming a
    `kind` value is a breaking change to the public surface.
    """

    def test_file_not_found_kind(self, hooks_yaml_path: Path) -> None:
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.FILE_NOT_FOUND

    def test_yaml_parse_failure_kind(self, hooks_yaml_path: Path) -> None:
        # Unmatched YAML structure that yaml.safe_load rejects.
        hooks_yaml_path.write_text("version: 1\n  - not valid: : :\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.YAML_PARSE_FAILURE

    def test_json_parse_failure_kind(self, hooks_json_path: Path) -> None:
        hooks_json_path.write_text("{not valid json")
        store = VersionedFileStore(
            hooks_json_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.JSON_PARSE_FAILURE

    def test_non_dict_root_kind(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("- one\n- two\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.NON_DICT_ROOT

    def test_missing_version_kind(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("hooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.MISSING_VERSION

    def test_unsupported_version_kind(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.UNSUPPORTED_VERSION

    def test_migrate_failed_kind(self, hooks_yaml_path: Path) -> None:
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")

        def migrate(raw: Any, from_version: int) -> Any:
            raise RuntimeError("migrate explodes")

        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
            migrate=migrate,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.MIGRATE_FAILED

    def test_schema_validation_kind(self, hooks_yaml_path: Path) -> None:
        # Unknown key triggers extra='forbid' validation failure.
        hooks_yaml_path.write_text("version: 1\nhooks: []\nrogue: 'x'\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=1,
        )
        with pytest.raises(AppConfigError) as info:
            store.load()
        assert info.value.kind is AppConfigErrorKind.SCHEMA_VALIDATION

    def test_kind_enables_match_case_translation(
        self,
        hooks_yaml_path: Path,
    ) -> None:
        """The end-to-end shape: consumer pattern-matches on `kind`
        instead of string-matching the message. Pin the table from
        PAPER-11's example so future contract drift is visible."""
        hooks_yaml_path.write_text("version: 1\nhooks: []\n")
        store = VersionedFileStore(
            hooks_yaml_path,
            schema=HooksFile,
            current_version=2,
        )

        def translate(e: AppConfigError) -> str:
            match e.kind:
                case AppConfigErrorKind.JSON_PARSE_FAILURE | AppConfigErrorKind.YAML_PARSE_FAILURE:
                    return "unreadable"
                case AppConfigErrorKind.UNSUPPORTED_VERSION | AppConfigErrorKind.MISSING_VERSION:
                    return "unsupported_schema_version"
                case _:
                    return "other"

        with pytest.raises(AppConfigError) as info:
            store.load()
        assert translate(info.value) == "unsupported_schema_version"

    def test_default_kind_is_none_for_bare_construction(self) -> None:
        # An AppConfigError raised by code that hasn't opted in (legacy
        # / general-purpose sites) carries `kind = None`. The default
        # is the contract every consumer's `_` arm relies on.
        exc = AppConfigError("some message")
        assert exc.kind is None


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _read_yaml(path: Path) -> dict[str, Any]:
    import yaml as _yaml

    raw = _yaml.safe_load(path.read_text())
    assert isinstance(raw, dict)
    return raw
