"""V04-04 — SmartEnvSource: longest-match env-var parsing.

Closes FEEDBACK V04-04 (single-underscore env nesting that was
deferred during v0.4 Phase A). Both single- and double-underscore
delimiters are honored; the source walks the schema longest-
field-name-first to disambiguate field names containing
underscores (``service_namespace``, ``level_overrides``, etc.).
"""

from __future__ import annotations

import pytest

from mgf.common._env_source import SmartEnvSource
from mgf.common.settings import MgfSettings


@pytest.fixture(autouse=True)
def _clean_env(monkeypatch: pytest.MonkeyPatch) -> None:
    """Wipe MGF_* + auto-detection vars so each test sees a clean slate."""
    import os

    for key in list(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)
    monkeypatch.setenv("MGF_DISABLE_PYPROJECT", "1")


# ---------------------------------------------------------------------------
# Single-underscore form (NEW in v0.4.2)
# ---------------------------------------------------------------------------


class TestSingleUnderscoreNesting:
    """The new ergonomic — single underscore separates segments."""

    def test_simple_nested_field(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("MGF_LOGGING_LEVEL", "DEBUG")
        s = MgfSettings()
        assert s.logging.level == "DEBUG"

    def test_top_level_field(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("MGF_PRESET", "docker")
        s = MgfSettings()
        assert s.preset == "docker"

    def test_underscored_field_name(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """``service_namespace`` is a real field name; the parser
        MUST not split it on the underscore."""
        monkeypatch.setenv("MGF_OTEL_SERVICE_NAMESPACE", "my-team")
        s = MgfSettings()
        assert s.otel.service_namespace == "my-team"

    def test_double_underscored_field_name(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """``rotating_max_bytes`` (one underscore inside) and
        ``rotating_backup_count`` (one underscore) — both must
        survive the parse."""
        monkeypatch.setenv("MGF_LOGGING_ROTATING_MAX_BYTES", "5242880")
        monkeypatch.setenv("MGF_LOGGING_ROTATING_BACKUP_COUNT", "3")
        s = MgfSettings()
        assert s.logging.rotating_max_bytes == 5242880
        assert s.logging.rotating_backup_count == 3

    def test_otel_sample_rate_float(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_OTEL_SAMPLE_RATE", "0.05")
        s = MgfSettings()
        assert s.otel.sample_rate == 0.05

    def test_redaction_strict_mode_bool(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_REDACTION_STRICT_MODE", "true")
        s = MgfSettings()
        assert s.redaction.strict_mode is True


# ---------------------------------------------------------------------------
# Double-underscore form (legacy v0.1-v0.4.1) — MUST still work
# ---------------------------------------------------------------------------


class TestDoubleUnderscoreBackwardCompat:
    """Every existing consumer with ``MGF_*__*`` env vars MUST
    keep working unchanged."""

    def test_double_underscore_simple(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_LOGGING__LEVEL", "ERROR")
        s = MgfSettings()
        assert s.logging.level == "ERROR"

    def test_double_underscore_with_underscored_field_name(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_OTEL__SERVICE_NAMESPACE", "legacy-team")
        s = MgfSettings()
        assert s.otel.service_namespace == "legacy-team"


# ---------------------------------------------------------------------------
# Longest-match-first disambiguation
# ---------------------------------------------------------------------------


class TestLongestMatchSemantics:
    """When a remainder could match multiple field names, longest wins."""

    def test_otel_service_namespace_not_misparsed_as_otel_service(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """If we naively split on ``_``, ``OTEL_SERVICE_NAMESPACE``
        could try ``otel.service.namespace`` (no path). The
        longest-match algorithm must pick ``otel.service_namespace``
        — the real field — instead."""
        monkeypatch.setenv("MGF_OTEL_SERVICE_NAMESPACE", "win")
        s = MgfSettings()
        # If the parser misrouted, this would fail with a
        # ValidationError or default.
        assert s.otel.service_namespace == "win"

    def test_helper_returns_correct_path(self) -> None:
        """Direct unit test of the path-resolution algorithm."""
        src = SmartEnvSource(MgfSettings)
        # Reach in to verify the parse output for a tricky case.
        # We're testing the helper directly to catch regressions
        # in the path-walk algorithm without env-roundtrip noise.
        path = src._parse_path("OTEL_SERVICE_NAMESPACE", MgfSettings)
        assert path == ["otel", "service_namespace"]

    def test_helper_rejects_unknown_path(self) -> None:
        src = SmartEnvSource(MgfSettings)
        path = src._parse_path("LOGGING_NONEXISTENT_FIELD", MgfSettings)
        assert path is None

    def test_helper_rejects_non_basemodel_class(self) -> None:
        """Defensive: ``_parse_path`` returns ``None`` for a class
        that isn't a ``BaseModel`` subclass (recursion termination
        guard at line 105)."""
        src = SmartEnvSource(MgfSettings)
        # ``str`` is a class but not a BaseModel subclass.
        assert src._parse_path("ANYTHING", str) is None
        # A non-class value must also be rejected.
        assert src._parse_path("ANYTHING", "not-a-class") is None  # type: ignore[arg-type]

    def test_env_var_equal_to_prefix_exactly_is_skipped(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Env var that's just the prefix (``MGF_`` with empty
        remainder) is silently skipped — the parser doesn't try to
        match an empty path. Guard at line 86."""
        monkeypatch.setenv("MGF_", "noise")
        # If construction succeeded, the source ignored the bad var.
        s = MgfSettings()
        assert s.logging.level == "INFO"  # default; nothing leaked


# ---------------------------------------------------------------------------
# Defensive coverage of constructor branches
# ---------------------------------------------------------------------------


class TestEdgeCaseConstruction:
    """Direct construction-time edge cases that aren't reachable
    through the public ``MgfSettings`` path but are part of the
    contract for any future settings class that uses
    ``SmartEnvSource``."""

    def test_settings_with_empty_env_prefix_yields_empty_data(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """If the settings class has ``env_prefix = ""``, the source
        returns immediately with empty data (line 76 guard)."""
        from pydantic import ConfigDict
        from pydantic_settings import BaseSettings

        class _NoPrefix(BaseSettings):
            model_config = ConfigDict(env_prefix="")

        src = SmartEnvSource(_NoPrefix)
        assert src() == {}


# ---------------------------------------------------------------------------
# Precedence + edge cases
# ---------------------------------------------------------------------------


class TestPrecedence:
    """Single-underscore env vars feed through the same precedence
    chain as the legacy double-underscore form."""

    def test_explicit_kwarg_still_wins_over_env(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        from mgf.common.settings import LoggingSettings

        monkeypatch.setenv("MGF_LOGGING_LEVEL", "DEBUG")
        s = MgfSettings(logging=LoggingSettings(level="ERROR"))
        assert s.logging.level == "ERROR"

    def test_unknown_mgf_var_silently_ignored(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """An MGF_* env var that doesn't match any field path
        is a no-op (does NOT raise, does NOT pollute the
        settings)."""
        monkeypatch.setenv("MGF_TOTALLY_UNKNOWN_GARBAGE", "x")
        s = MgfSettings()
        # Constructor succeeds; no garbage propagated.
        assert s.logging.level == "INFO"  # default

    def test_multiple_env_vars_compose(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_LOGGING_LEVEL", "DEBUG")
        monkeypatch.setenv("MGF_OTEL_SAMPLE_RATE", "0.1")
        monkeypatch.setenv("MGF_REDACTION_STRICT_MODE", "true")
        s = MgfSettings()
        assert s.logging.level == "DEBUG"
        assert s.otel.sample_rate == 0.1
        assert s.redaction.strict_mode is True

    def test_mixed_single_and_double_underscore_in_same_run(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Some env vars use the new form, others the legacy form
        — both must work in the same construction."""
        monkeypatch.setenv("MGF_LOGGING_LEVEL", "DEBUG")  # new
        monkeypatch.setenv("MGF_OTEL__SAMPLE_RATE", "0.2")  # legacy
        s = MgfSettings()
        assert s.logging.level == "DEBUG"
        assert s.otel.sample_rate == 0.2
