"""Unit tests for ``mgf.common.flags`` — feature-flag primitive (0.35.0)."""

from __future__ import annotations

import logging

import pytest

from mgf.common.flags import (
    EnvVarFlags,
    FeatureFlag,
    StaticFlags,
    configure_flags,
    get_flags,
    is_enabled,
)


@pytest.fixture(autouse=True)
def _restore_default_backend():
    """Every test runs with EnvVarFlags() installed, regardless of
    what the previous test left in place."""
    yield
    configure_flags(EnvVarFlags())


# ---------------------------------------------------------------------------
# Protocol conformance
# ---------------------------------------------------------------------------


class TestProtocolConformance:
    def test_envvar_flags_is_feature_flag(self) -> None:
        assert isinstance(EnvVarFlags(), FeatureFlag)

    def test_static_flags_is_feature_flag(self) -> None:
        assert isinstance(StaticFlags(), FeatureFlag)

    def test_duck_typed_impl_passes(self) -> None:
        class MyImpl:
            def is_enabled(self, name, *, default=False, context=None):
                return True

        assert isinstance(MyImpl(), FeatureFlag)

    def test_non_conformant_impl_rejected(self) -> None:
        class Bogus:
            pass

        with pytest.raises(TypeError, match="FeatureFlag-conformant"):
            configure_flags(Bogus())  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# EnvVarFlags — the default backend
# ---------------------------------------------------------------------------


class TestEnvVarFlags:
    def test_unset_returns_default_false(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.delenv("MGF_FLAG_NEW_FLOW", raising=False)
        assert EnvVarFlags().is_enabled("new-flow") is False

    def test_unset_returns_explicit_default(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.delenv("MGF_FLAG_NEW_FLOW", raising=False)
        assert EnvVarFlags().is_enabled("new-flow", default=True) is True

    @pytest.mark.parametrize("raw", ["1", "true", "True", "TRUE", "yes", "on", "enabled", " ON "])
    def test_truthy_values(
        self,
        monkeypatch: pytest.MonkeyPatch,
        raw: str,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_X", raw)
        assert EnvVarFlags().is_enabled("x") is True

    @pytest.mark.parametrize("raw", ["0", "false", "False", "no", "off", "disabled", ""])
    def test_falsy_values(
        self,
        monkeypatch: pytest.MonkeyPatch,
        raw: str,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_X", raw)
        assert EnvVarFlags().is_enabled("x") is False

    def test_invalid_value_returns_default_and_warns(
        self,
        monkeypatch: pytest.MonkeyPatch,
        caplog: pytest.LogCaptureFixture,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_X", "maybe")
        with caplog.at_level(logging.WARNING, logger="mgf.common.flags"):
            result = EnvVarFlags().is_enabled("x", default=True)
        assert result is True  # default returned, not crashed
        warn_records = [r for r in caplog.records if r.levelno == logging.WARNING]
        assert len(warn_records) == 1
        assert warn_records[0].event == "flag.invalid_env_value"
        assert warn_records[0].flag_name == "x"

    def test_name_normalisation_dashes_dots(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_FOO_BAR_BAZ", "1")
        assert EnvVarFlags().is_enabled("foo-bar.baz") is True

    def test_context_ignored(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_X", "1")
        # EnvVarFlags ignores context (per Protocol — backends MAY use it).
        assert EnvVarFlags().is_enabled("x", context={"user_id": "u1"}) is True


# ---------------------------------------------------------------------------
# StaticFlags — tests / scratch
# ---------------------------------------------------------------------------


class TestStaticFlags:
    def test_initial_dict(self) -> None:
        impl = StaticFlags({"a": True, "b": False})
        assert impl.is_enabled("a") is True
        assert impl.is_enabled("b") is False

    def test_unset_returns_default(self) -> None:
        impl = StaticFlags({})
        assert impl.is_enabled("missing", default=True) is True
        assert impl.is_enabled("missing", default=False) is False

    def test_set_updates(self) -> None:
        impl = StaticFlags({})
        impl.set("x", True)
        assert impl.is_enabled("x") is True
        impl.set("x", False)
        assert impl.is_enabled("x") is False

    def test_unset_reverts_to_default(self) -> None:
        impl = StaticFlags({"x": True})
        assert impl.is_enabled("x", default=False) is True
        impl.unset("x")
        assert impl.is_enabled("x", default=False) is False

    def test_unset_missing_is_noop(self) -> None:
        impl = StaticFlags({})
        impl.unset("never-existed")  # must not raise


# ---------------------------------------------------------------------------
# Module-level state: configure_flags + is_enabled + get_flags
# ---------------------------------------------------------------------------


class TestModuleLevelDispatch:
    def test_default_backend_is_envvar(self) -> None:
        # The autouse fixture restored EnvVarFlags() before this test.
        assert isinstance(get_flags(), EnvVarFlags)

    def test_module_is_enabled_delegates(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_FOO", "1")
        assert is_enabled("foo") is True

    def test_configure_swaps_backend(self) -> None:
        impl = StaticFlags({"new-checkout": True})
        configure_flags(impl)
        assert get_flags() is impl
        assert is_enabled("new-checkout") is True

    def test_configure_emits_audit_log(
        self,
        caplog: pytest.LogCaptureFixture,
    ) -> None:
        with caplog.at_level(logging.INFO, logger="mgf.common.flags"):
            configure_flags(StaticFlags({}))

        audit_records = [
            r for r in caplog.records if getattr(r, "event", None) == "flag.backend_configured"
        ]
        assert len(audit_records) == 1
        rec = audit_records[0]
        assert rec.audit is True
        assert rec.new_backend == "static"
        assert rec.previous_backend == "env"

    def test_is_enabled_does_not_audit_per_call(
        self,
        monkeypatch: pytest.MonkeyPatch,
        caplog: pytest.LogCaptureFixture,
    ) -> None:
        monkeypatch.setenv("MGF_FLAG_X", "1")
        with caplog.at_level(logging.DEBUG, logger="mgf.common.flags"):
            for _ in range(50):
                is_enabled("x")
        # No audit records — per-call audit-on-read is forbidden.
        audit_records = [r for r in caplog.records if getattr(r, "audit", False)]
        assert audit_records == []
