"""v0.4 Phase C pin tests.

Three sub-features covered:

  - **C.1** — :meth:`MgfSettings.dev` / :meth:`.production` /
    :meth:`.testing` factory classmethods.

  - **C.3** — composable presets. ``MgfSettings.preset`` accepts
    a list of preset names applied left-to-right; later entries
    win on field conflicts. The ``mgf-common explain`` provenance
    shim renders list presets as ``"a+b+c"``.

  - **C.2** — ``[tool.mgf]`` pyproject.toml source. Custom
    pydantic-settings source wired into ``MgfSettings`` via
    ``settings_customise_sources``.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

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

if TYPE_CHECKING:
    from pathlib import Path


# Wipe MGF_* env vars + JOURNAL_STREAM + OTEL_EXPORTER_OTLP_ENDPOINT
# so each test sees a deterministic environment, regardless of what
# the host shell or earlier tests left behind.
@pytest.fixture(autouse=True)
def _clean_env(monkeypatch: pytest.MonkeyPatch) -> None:
    for key in list(__import__("os").environ.keys()):
        if key.startswith("MGF_"):
            monkeypatch.delenv(key, raising=False)
    monkeypatch.delenv("JOURNAL_STREAM", raising=False)
    monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False)


# ---------------------------------------------------------------------------
# C.1 — factory classmethods
# ---------------------------------------------------------------------------


class TestDevFactory:
    """``MgfSettings.dev()`` is a thin wrapper over the dev preset."""

    def test_no_args_uses_dev_preset(self) -> None:
        s = MgfSettings.dev()
        assert s.preset == "dev"
        # The dev preset's static overrides land:
        assert s.logging.level == "DEBUG"
        assert s.logging.format == "text"
        assert s.otel.enabled is False

    def test_kwargs_override_preset_defaults(self) -> None:
        """User-supplied kwargs win over the factory's preset selection."""
        s = MgfSettings.dev(logging=LoggingSettings(level="WARNING"))
        # User passed an explicit logging sub-model — preset's logging
        # overrides MUST NOT clobber it (per the validator's
        # "skip if model_fields_set" rule).
        assert s.logging.level == "WARNING"

    def test_explicit_preset_wins_over_factory_default(self) -> None:
        """``MgfSettings.dev(preset="systemd")`` is a degenerate but
        valid call — kwargs win over ``setdefault``."""
        s = MgfSettings.dev(preset="systemd")
        assert s.preset == "systemd"


class TestProductionFactory:
    """``MgfSettings.production()`` bundles deterministic shape +
    JSON logging + low OTel sample rate."""

    def test_defaults(self) -> None:
        s = MgfSettings.production()
        assert s.preset == "explicit"
        assert s.logging.format == "json"
        assert s.otel.sample_rate == 0.05

    def test_user_otel_override_wins(self) -> None:
        s = MgfSettings.production(otel=OtelSettings(sample_rate=0.01))
        assert s.otel.sample_rate == 0.01

    def test_user_logging_override_wins(self) -> None:
        s = MgfSettings.production(logging=LoggingSettings(format="text"))
        assert s.logging.format == "text"


class TestTestingFactory:
    """``MgfSettings.testing()`` bundles WARNING-level logging + OTel
    off — defaults that cut test-suite noise without breaking
    consumer assertions on identity / settings shape."""

    def test_defaults(self) -> None:
        s = MgfSettings.testing()
        assert s.preset == "explicit"
        assert s.logging.level == "WARNING"
        assert s.otel.enabled is False

    def test_kwargs_win(self) -> None:
        s = MgfSettings.testing(logging=LoggingSettings(level="DEBUG"))
        assert s.logging.level == "DEBUG"


# ---------------------------------------------------------------------------
# C.3 — composable presets
# ---------------------------------------------------------------------------


class TestComposablePresets:
    """``preset=`` accepts a list applied left-to-right."""

    def test_single_preset_still_works(self) -> None:
        """Backward-compat: a string preset behaves exactly as before."""
        s = MgfSettings(preset="docker")
        assert s.logging.format == "json"

    def test_list_preset_applies_all(self) -> None:
        """``["docker", "dev"]`` → docker's overrides PLUS dev's
        overrides; dev wins on field conflicts (later in list)."""
        s = MgfSettings(preset=["docker", "dev"])
        # dev's logging.format=text wins over docker's json.
        assert s.logging.format == "text"
        # dev's logging.level=DEBUG (no conflict — docker doesn't set level).
        assert s.logging.level == "DEBUG"

    def test_list_order_matters(self) -> None:
        """Reverse the list → opposite winners."""
        s = MgfSettings(preset=["dev", "docker"])
        # docker now wins on format.
        assert s.logging.format == "json"
        # dev's level=DEBUG still applies (no conflict).
        assert s.logging.level == "DEBUG"

    def test_resolve_helper_with_list(self) -> None:
        """Direct helper test — bypasses MgfSettings."""
        result = _resolve_preset_overrides(["docker", "dev"])
        # Both presets contribute logging entries:
        assert "logging" in result
        # dev's format=text wins:
        assert result["logging"]["format"] == "text"
        # dev's level=DEBUG present:
        assert result["logging"]["level"] == "DEBUG"

    def test_resolve_helper_with_single(self) -> None:
        """Single string → same as before."""
        result = _resolve_preset_overrides("docker")
        assert result["logging"]["format"] == "json"

    def test_explicit_field_still_wins_over_list_preset(self) -> None:
        """The ``model_fields_set`` skip-rule still applies under
        composable presets."""
        s = MgfSettings(
            preset=["docker", "dev"],
            logging=LoggingSettings(format="json"),
        )
        # User explicitly set logging.format, so preset's value is
        # dropped — but other fields the user didn't set still
        # follow preset semantics.
        assert s.logging.format == "json"

    def test_provenance_renders_list_as_plus_joined(self) -> None:
        """The diagnostic CLI's provenance shim renders list-shaped
        preset as ``"a+b"`` for the human-readable detail."""
        from mgf.common.cli import field_provenance

        s = MgfSettings(preset=["docker", "dev"])
        prov = field_provenance(s)
        # logging.format is preset-supplied (dev wins). The detail
        # carries the rendered preset label.
        src = prov["logging"]["format"]
        assert src.kind == "preset"
        assert src.detail == "docker+dev"


# ---------------------------------------------------------------------------
# C.2 — pyproject.toml source
# ---------------------------------------------------------------------------


class TestPyprojectSource:
    """``[tool.mgf]`` from the nearest pyproject.toml feeds into
    :class:`MgfSettings`."""

    def test_returns_empty_when_no_tool_mgf_section(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """A pyproject.toml without ``[tool.mgf]`` MUST contribute
        nothing — defaults survive. This is the protective behaviour
        for every test in this repo: the project's own pyproject.toml
        has no ``[tool.mgf]``, so existing tests stay deterministic."""
        (tmp_path / "pyproject.toml").write_text("[project]\nname = 'x'\n")
        monkeypatch.chdir(tmp_path)
        s = MgfSettings()
        # Defaults intact — no leak from pyproject.
        assert s.logging.level == "INFO"

    def test_reads_top_level_field(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """``preset = "docker"`` in [tool.mgf] → settings.preset == "docker"."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf]\npreset = 'docker'\n",
        )
        monkeypatch.chdir(tmp_path)
        s = MgfSettings()
        assert s.preset == "docker"
        # docker preset's effects flow through the validator:
        assert s.logging.format == "json"

    def test_reads_nested_section(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """``[tool.mgf.logging]`` → settings.logging fields."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf.logging]\nlevel = 'WARNING'\nformat = 'json'\n",
        )
        monkeypatch.chdir(tmp_path)
        s = MgfSettings()
        assert s.logging.level == "WARNING"
        assert s.logging.format == "json"

    def test_explicit_kwarg_wins_over_pyproject(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """Source precedence: explicit kwargs > pyproject."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf.logging]\nlevel = 'WARNING'\n",
        )
        monkeypatch.chdir(tmp_path)
        s = MgfSettings(logging=LoggingSettings(level="ERROR"))
        assert s.logging.level == "ERROR"

    def test_env_var_wins_over_pyproject(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """Source precedence: env > pyproject."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf.logging]\nlevel = 'WARNING'\n",
        )
        monkeypatch.chdir(tmp_path)
        monkeypatch.setenv("MGF_LOGGING__LEVEL", "ERROR")
        s = MgfSettings()
        assert s.logging.level == "ERROR"

    def test_walks_up_to_find_pyproject(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """If CWD is a subdirectory, pyproject.toml in a parent is
        still found."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf.logging]\nlevel = 'WARNING'\n",
        )
        sub = tmp_path / "sub" / "deeper"
        sub.mkdir(parents=True)
        monkeypatch.chdir(sub)
        s = MgfSettings()
        assert s.logging.level == "WARNING"

    def test_disable_via_env_var(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """``MGF_DISABLE_PYPROJECT=1`` → source returns empty even
        when pyproject.toml has [tool.mgf]."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool.mgf.logging]\nlevel = 'WARNING'\n",
        )
        monkeypatch.chdir(tmp_path)
        monkeypatch.setenv("MGF_DISABLE_PYPROJECT", "1")
        s = MgfSettings()
        assert s.logging.level == "INFO"  # default — pyproject ignored

    def test_malformed_pyproject_is_silent(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """A pyproject.toml that fails to parse MUST NOT crash
        construction. Defaults apply; the diagnostic CLI's
        ``validate`` subcommand is the right place to surface the
        TOML error."""
        (tmp_path / "pyproject.toml").write_text(
            "this is = = not valid {{ toml",
        )
        monkeypatch.chdir(tmp_path)
        s = MgfSettings()  # MUST NOT raise
        assert s.logging.level == "INFO"

    def test_no_pyproject_anywhere_is_silent(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """No pyproject.toml in the directory chain → empty source."""
        # tmp_path is empty + isolated. The only ancestor that may
        # have a pyproject is /tmp/... which won't.
        monkeypatch.chdir(tmp_path)
        # Ensure no inherited MGF_DISABLE flag triggers — we're testing
        # the "no file found" path specifically.
        monkeypatch.delenv("MGF_DISABLE_PYPROJECT", raising=False)
        s = MgfSettings()  # MUST NOT raise
        # Defaults intact:
        assert s.logging.level == "INFO"

    def test_non_dict_tool_mgf_section_is_ignored(
        self,
        monkeypatch: pytest.MonkeyPatch,
        tmp_path: Path,
    ) -> None:
        """If someone writes ``[tool] mgf = "string"``, treat it as
        empty rather than crashing — defensive."""
        (tmp_path / "pyproject.toml").write_text(
            "[tool]\nmgf = 'just a string'\n",
        )
        monkeypatch.chdir(tmp_path)
        s = MgfSettings()
        assert s.logging.level == "INFO"
