"""
Tests for Phase 94: Feature Flags for Gradual Rollouts.
"""

import json

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect feature flag storage to a temp directory."""
    ff_dir = tmp_path / "feature_flags"
    ff_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.feature_flags as mod
    monkeypatch.setattr(mod, "FF_DIR", ff_dir)
    monkeypatch.setattr(mod, "FLAGS_FILE", ff_dir / "flags.json")
    monkeypatch.setattr(mod, "STATS_FILE", ff_dir / "stats.json")


# ── 1. Create Flag ────────────────────────────────────────────────────────


class TestCreateFlag:
    def test_create_basic(self):
        from brynq.feature_flags import create_flag, get_flag
        result = create_flag("dark_mode", description="Dark mode toggle")
        assert result["ok"] is True
        flag = get_flag("dark_mode")
        assert flag is not None
        assert flag["name"] == "dark_mode"
        assert flag["enabled"] is False

    def test_create_enabled(self):
        from brynq.feature_flags import create_flag, get_flag
        create_flag("feature_x", default=True)
        flag = get_flag("feature_x")
        assert flag["enabled"] is True

    def test_create_with_rollout(self):
        from brynq.feature_flags import create_flag, get_flag
        create_flag("gradual", rollout_pct=50)
        flag = get_flag("gradual")
        assert flag["rollout_pct"] == 50

    def test_create_duplicate(self):
        from brynq.feature_flags import create_flag
        create_flag("dup")
        result = create_flag("dup")
        assert "error" in result

    def test_create_invalid_rollout(self):
        from brynq.feature_flags import create_flag
        result = create_flag("bad", rollout_pct=101)
        assert "error" in result

    def test_create_negative_rollout(self):
        from brynq.feature_flags import create_flag
        result = create_flag("bad", rollout_pct=-1)
        assert "error" in result


# ── 2. Is Enabled ─────────────────────────────────────────────────────────


class TestIsEnabled:
    def test_disabled_by_default(self):
        from brynq.feature_flags import create_flag, is_enabled
        create_flag("f1")
        assert is_enabled("f1") is False

    def test_enabled(self):
        from brynq.feature_flags import create_flag, is_enabled
        create_flag("f1", default=True)
        assert is_enabled("f1") is True

    def test_nonexistent_flag(self):
        from brynq.feature_flags import is_enabled
        assert is_enabled("nope") is False

    def test_rollout_deterministic(self):
        from brynq.feature_flags import create_flag, is_enabled
        create_flag("rollout", rollout_pct=50)
        r1 = is_enabled("rollout", entity_id="user1")
        r2 = is_enabled("rollout", entity_id="user1")
        assert r1 == r2

    def test_rollout_distribution(self):
        from brynq.feature_flags import create_flag, is_enabled
        create_flag("half", rollout_pct=50)
        enabled = sum(1 for i in range(200) if is_enabled("half", entity_id=f"u{i}"))
        # With 200 samples and 50% rollout, we expect roughly 100
        assert 60 < enabled < 140

    def test_zero_rollout_uses_global(self):
        from brynq.feature_flags import create_flag, enable, is_enabled
        create_flag("zr", rollout_pct=0)
        assert is_enabled("zr", entity_id="user1") is False
        enable("zr")
        assert is_enabled("zr", entity_id="user1") is True

    def test_100_pct_rollout(self):
        from brynq.feature_flags import create_flag, is_enabled
        create_flag("full", rollout_pct=100)
        assert all(is_enabled("full", entity_id=f"u{i}") for i in range(50))


# ── 3. Enable / Disable ──────────────────────────────────────────────────


class TestEnableDisable:
    def test_enable(self):
        from brynq.feature_flags import create_flag, enable, is_enabled
        create_flag("f")
        enable("f")
        assert is_enabled("f") is True

    def test_disable(self):
        from brynq.feature_flags import create_flag, enable, disable, is_enabled
        create_flag("f", default=True)
        disable("f")
        assert is_enabled("f") is False

    def test_enable_nonexistent(self):
        from brynq.feature_flags import enable
        result = enable("nope")
        assert "error" in result

    def test_disable_nonexistent(self):
        from brynq.feature_flags import disable
        result = disable("nope")
        assert "error" in result


# ── 4. Set Rollout ────────────────────────────────────────────────────────


class TestSetRollout:
    def test_set_rollout(self):
        from brynq.feature_flags import create_flag, set_rollout, get_flag
        create_flag("f")
        result = set_rollout("f", 75)
        assert result["ok"] is True
        assert get_flag("f")["rollout_pct"] == 75

    def test_set_rollout_invalid(self):
        from brynq.feature_flags import create_flag, set_rollout
        create_flag("f")
        result = set_rollout("f", 150)
        assert "error" in result

    def test_set_rollout_nonexistent(self):
        from brynq.feature_flags import set_rollout
        result = set_rollout("nope", 50)
        assert "error" in result


# ── 5. List / Delete ─────────────────────────────────────────────────────


class TestListDelete:
    def test_list_all(self):
        from brynq.feature_flags import create_flag, list_flags
        create_flag("a")
        create_flag("b", default=True)
        flags = list_flags()
        assert len(flags) == 2

    def test_list_enabled(self):
        from brynq.feature_flags import create_flag, list_flags
        create_flag("a")
        create_flag("b", default=True)
        enabled = list_flags(status="enabled")
        assert len(enabled) == 1
        assert enabled[0]["name"] == "b"

    def test_list_disabled(self):
        from brynq.feature_flags import create_flag, list_flags
        create_flag("a")
        create_flag("b", default=True)
        disabled = list_flags(status="disabled")
        assert len(disabled) == 1
        assert disabled[0]["name"] == "a"

    def test_delete(self):
        from brynq.feature_flags import create_flag, delete_flag, get_flag
        create_flag("f")
        result = delete_flag("f")
        assert result["deleted"] is True
        assert get_flag("f") is None

    def test_delete_nonexistent(self):
        from brynq.feature_flags import delete_flag
        result = delete_flag("nope")
        assert "error" in result


# ── 6. Stats ──────────────────────────────────────────────────────────────


class TestFlagStats:
    def test_initial_stats(self):
        from brynq.feature_flags import get_flag_stats
        stats = get_flag_stats("nonexistent")
        assert stats["checks"] == 0

    def test_stats_after_checks(self):
        from brynq.feature_flags import create_flag, is_enabled, get_flag_stats
        create_flag("f", default=True)
        is_enabled("f")
        is_enabled("f")
        stats = get_flag_stats("f")
        assert stats["checks"] == 2
        assert stats["enabled_count"] == 2

    def test_stats_disabled_count(self):
        from brynq.feature_flags import create_flag, is_enabled, get_flag_stats
        create_flag("f", default=False)
        is_enabled("f")
        stats = get_flag_stats("f")
        assert stats["disabled_count"] == 1
