"""Feature Flag 单元测试。

测试 FeatureFlagConfig、InMemoryFeatureFlagStore、RedisFeatureFlagStore 的核心逻辑。
"""

from __future__ import annotations

from unittest.mock import MagicMock

from packages.adapters.redis.feature_flag_store import (
    FeatureFlagConfig,
    InMemoryFeatureFlagStore,
)

# ── FeatureFlagConfig 测试 ──────────────────────────────────────────────────


class TestFeatureFlagConfig:
    """FeatureFlagConfig 核心逻辑测试。"""

    def test_default_disabled(self):
        """默认配置为关闭状态。"""
        config = FeatureFlagConfig(name="test_flag")
        assert config.enabled is False
        assert config.percentage == 0
        assert config.whitelist == set()
        assert config.is_active() is False
        assert config.is_active("user1") is False

    def test_global_enabled_100_percent(self):
        """100% + 启用 = 全部命中。"""
        config = FeatureFlagConfig(name="test_flag", enabled=True, percentage=100)
        assert config.is_active() is True
        assert config.is_active("user1") is True
        assert config.is_active("any_user") is True

    def test_global_enabled_0_percent_no_whitelist(self):
        """启用但 0% 且无白名单 = 不命中。"""
        config = FeatureFlagConfig(name="test_flag", enabled=True, percentage=0)
        assert config.is_active() is False
        assert config.is_active("user1") is False

    def test_whitelist_takes_priority(self):
        """白名单优先级高于百分比。"""
        config = FeatureFlagConfig(
            name="test_flag",
            enabled=True,
            percentage=0,
            whitelist={"user1", "user2"},
        )
        assert config.is_active("user1") is True
        assert config.is_active("user2") is True
        assert config.is_active("user3") is False

    def test_whitelist_with_percentage(self):
        """白名单用户即使百分比为0也命中，非白名单按百分比。"""
        config = FeatureFlagConfig(
            name="test_flag",
            enabled=True,
            percentage=100,  # 100% 所有人命中
            whitelist={"user1"},
        )
        assert config.is_active("user1") is True
        assert config.is_active("user999") is True  # 100% 命中

    def test_percentage_consistency_same_user(self):
        """同一用户多次调用结果一致（哈希确定性）。"""
        config = FeatureFlagConfig(name="test_flag", enabled=True, percentage=50)
        results = [config.is_active("user_fixed") for _ in range(100)]
        assert all(r == results[0] for r in results)

    def test_percentage_different_users_distributed(self):
        """不同用户分布大致符合百分比（统计检验，宽松阈值）。"""
        config = FeatureFlagConfig(name="test_flag", enabled=True, percentage=50)
        active_count = sum(1 for i in range(1000) if config.is_active(f"user_{i}"))
        # 50% 上下浮动 10% 都算合理
        assert 400 <= active_count <= 600, f"Expected ~500, got {active_count}"

    def test_percentage_boundary_0_and_100(self):
        """0% 和 100% 的边界情况。"""
        config_0 = FeatureFlagConfig(name="test", enabled=True, percentage=0)
        config_100 = FeatureFlagConfig(name="test", enabled=True, percentage=100)

        for i in range(100):
            assert config_0.is_active(f"user_{i}") is False
            assert config_100.is_active(f"user_{i}") is True

    def test_disabled_ignores_all_other_settings(self):
        """关闭时忽略白名单和百分比。"""
        config = FeatureFlagConfig(
            name="test_flag",
            enabled=False,
            percentage=100,
            whitelist={"user1"},
        )
        assert config.is_active("user1") is False
        assert config.is_active() is False

    def test_none_identifier_with_percentage(self):
        """无 identifier 时按随机比例（0% 和 100% 是确定的）。"""
        config_0 = FeatureFlagConfig(name="test", enabled=True, percentage=0)
        config_100 = FeatureFlagConfig(name="test", enabled=True, percentage=100)
        assert config_0.is_active(None) is False
        assert config_100.is_active(None) is True

    def test_to_dict_and_from_dict(self):
        """序列化和反序列化对称。"""
        original = FeatureFlagConfig(
            name="test_flag",
            enabled=True,
            percentage=30,
            whitelist={"user_a", "user_b", "user_c"},
        )
        data = original.to_dict()
        restored = FeatureFlagConfig.from_dict(data)
        assert restored.name == original.name
        assert restored.enabled == original.enabled
        assert restored.percentage == original.percentage
        assert restored.whitelist == original.whitelist

    def test_from_dict_with_missing_fields(self):
        """from_dict 缺失字段时使用默认值。"""
        config = FeatureFlagConfig.from_dict({"name": "minimal"})
        assert config.name == "minimal"
        assert config.enabled is False
        assert config.percentage == 0
        assert config.whitelist == set()


# ── InMemoryFeatureFlagStore 测试 ───────────────────────────────────────────


class TestInMemoryFeatureFlagStore:
    """内存存储实现测试。"""

    def test_get_nonexistent_returns_default(self):
        """获取不存在的 flag 返回默认配置（关闭）。"""
        store = InMemoryFeatureFlagStore()
        config = store.get("nonexistent")
        assert config.name == "nonexistent"
        assert config.enabled is False

    def test_set_and_get(self):
        """设置后可以读取。"""
        store = InMemoryFeatureFlagStore()
        config = FeatureFlagConfig(name="test", enabled=True, percentage=50, whitelist={"u1"})
        store.set(config)

        got = store.get("test")
        assert got.enabled is True
        assert got.percentage == 50
        assert got.whitelist == {"u1"}

    def test_delete_existing(self):
        """删除存在的 flag 返回 True。"""
        store = InMemoryFeatureFlagStore()
        store.set(FeatureFlagConfig(name="test", enabled=True))
        assert store.delete("test") is True
        assert store.get("test").enabled is False

    def test_delete_nonexistent(self):
        """删除不存在的 flag 返回 False。"""
        store = InMemoryFeatureFlagStore()
        assert store.delete("nonexistent") is False

    def test_list_all(self):
        """列出所有 flag。"""
        store = InMemoryFeatureFlagStore()
        store.set(FeatureFlagConfig(name="flag_a", enabled=True))
        store.set(FeatureFlagConfig(name="flag_b", percentage=10))

        all_flags = store.list_all()
        assert len(all_flags) == 2
        assert "flag_a" in all_flags
        assert "flag_b" in all_flags
        assert all_flags["flag_a"].enabled is True

    def test_is_active_convenience(self):
        """is_active 便捷方法。"""
        store = InMemoryFeatureFlagStore()
        store.set(FeatureFlagConfig(name="render", enabled=True, percentage=0, whitelist={"vip_user"}))
        assert store.is_active("render", "vip_user") is True
        assert store.is_active("render", "normal_user") is False
        assert store.is_active("nonexistent") is False


# ── RedisFeatureFlagStore 降级测试（无 Redis 环境） ───────────────────────


class TestRedisStoreDegradation:
    """Redis 不可用时的降级行为测试。"""

    def test_get_returns_default_when_redis_unavailable(self):
        """Redis 连接失败时返回默认关闭配置，不抛异常。"""

        from packages.adapters.redis import feature_flag_store as ff_module

        # 模拟 redis 模块不存在的场景不好做，这里直接测试异常捕获逻辑
        store = ff_module.RedisFeatureFlagStore.__new__(ff_module.RedisFeatureFlagStore)
        store._redis = MagicMock()
        store._redis.hgetall.side_effect = ConnectionError("Redis down")
        store._key_prefix = ff_module.FEATURE_FLAG_REDIS_PREFIX
        store._cache = {}
        store._cache_ttl = 5.0
        import threading

        store._lock = threading.Lock()

        config = store.get("render_engine")
        assert config.enabled is False
        assert config.name == "render_engine"

    def test_list_all_returns_empty_on_redis_error(self):
        """Redis 错误时 list_all 返回空字典。"""

        from packages.adapters.redis import feature_flag_store as ff_module

        store = ff_module.RedisFeatureFlagStore.__new__(ff_module.RedisFeatureFlagStore)
        store._redis = MagicMock()
        store._redis.scan.side_effect = ConnectionError("Redis down")
        store._key_prefix = ff_module.FEATURE_FLAG_REDIS_PREFIX
        store._cache = {}
        store._cache_ttl = 5.0
        import threading

        store._lock = threading.Lock()

        result = store.list_all()
        assert result == {}


class TestRedisStoreListAll:
    """RedisFeatureFlagStore list_all 正常路径测试。"""

    def _make_store(self):
        from packages.adapters.redis import feature_flag_store as ff_module

        store = ff_module.RedisFeatureFlagStore.__new__(ff_module.RedisFeatureFlagStore)
        store._redis = MagicMock()
        store._key_prefix = ff_module.FEATURE_FLAG_REDIS_PREFIX
        store._cache = {}
        store._cache_ttl = 5.0
        import threading

        store._lock = threading.Lock()
        return store

    def test_list_all_scan_with_match_param(self):
        """list_all 调用 redis.scan 时使用正确的 match 参数名。"""
        store = self._make_store()
        prefix = store._key_prefix

        # 模拟 scan 返回 2 个 key，分 2 次游标
        store._redis.scan.side_effect = [
            (10, [f"{prefix}render_engine", f"{prefix}other_flag"]),
            (0, []),
        ]
        # 模拟 hgetall 返回配置
        store._redis.hgetall.return_value = {
            b"enabled": b"true",
            b"percentage": b"50",
            b"whitelist": b'["user1","user2"]',
        }

        result = store.list_all()

        # 验证 scan 被调用了 2 次（游标遍历）
        assert store._redis.scan.call_count == 2
        # 验证参数名是 match（不是 match_pattern）
        first_call_kwargs = store._redis.scan.call_args_list[0][1]
        assert "match" in first_call_kwargs
        assert "match_pattern" not in first_call_kwargs
        assert first_call_kwargs["match"] == f"{prefix}*"
        # 验证返回了 2 个 flag
        assert len(result) == 2
        assert "render_engine" in result
        assert "other_flag" in result
