"""Redis Session Store 单元测试"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from packages.adapters.redis.session_store import (
    NoopSessionStore,
    RedisConfig,
    SessionStore,
    get_session_store,
)


@pytest.fixture
def mock_redis():
    return MagicMock()


@pytest.fixture
def session_store(mock_redis):
    return SessionStore(redis_client=mock_redis)


class TestRedisConfig:
    """RedisConfig 默认值测试"""

    def test_default_values(self):
        """默认配置"""
        cfg = RedisConfig()
        assert cfg.HOST == "localhost"
        assert cfg.PORT == 6379
        assert cfg.DB == 0
        assert cfg.PASSWORD is None
        assert cfg.DECODE_RESPONSES is True


class TestNoopSessionStore:
    """NoopSessionStore 测试"""

    def test_save_session_returns_false(self):
        """保存返回 False"""
        store = NoopSessionStore()
        assert store.save_session() is False

    def test_get_session_returns_none(self):
        """获取返回 None"""
        store = NoopSessionStore()
        assert store.get_session("sess_123") is None

    def test_get_session_by_refresh_token_returns_none(self):
        """通过 refresh_token 获取返回 None"""
        store = NoopSessionStore()
        assert store.get_session_by_refresh_token("tok_123") is None

    def test_get_refresh_token_returns_none(self):
        """获取 refresh_token 返回 None"""
        store = NoopSessionStore()
        assert store.get_refresh_token("sess_123") is None

    def test_update_last_active_returns_false(self):
        """更新活跃时间返回 False"""
        store = NoopSessionStore()
        assert store.update_last_active("sess_123") is False

    def test_delete_session_returns_false(self):
        """删除返回 False"""
        store = NoopSessionStore()
        assert store.delete_session("sess_123") is False

    def test_get_user_sessions_returns_empty(self):
        """用户 session 列表为空"""
        store = NoopSessionStore()
        assert store.get_user_sessions("user_1") == []

    def test_delete_all_user_sessions_returns_zero(self):
        """删除所有返回 0"""
        store = NoopSessionStore()
        assert store.delete_all_user_sessions("user_1") == 0

    def test_session_exists_returns_false(self):
        """存在性检查返回 False"""
        store = NoopSessionStore()
        assert store.session_exists("sess_123") is False


class TestSessionStoreInit:
    """SessionStore 初始化测试"""

    def test_init_with_redis_client(self, mock_redis):
        """使用注入的 redis client"""
        store = SessionStore(redis_client=mock_redis)
        assert store.redis is mock_redis

    def test_init_with_config(self):
        """使用配置创建 redis client"""
        cfg = RedisConfig()
        cfg.HOST = "redis.example.com"
        cfg.PORT = 6380

        with patch("packages.adapters.redis.session_store.redis.Redis") as mock_redis_cls:
            store = SessionStore(config=cfg)
            mock_redis_cls.assert_called_once_with(
                host="redis.example.com",
                port=6380,
                db=0,
                password=None,
                decode_responses=True,
            )


class TestSessionStoreSave:
    """save_session 测试"""

    def test_save_success(self, session_store, mock_redis):
        """保存成功"""
        result = session_store.save_session(
            session_id="sess_001",
            user_id="user_001",
            refresh_token="refresh_abc",
            device_info="Chrome/Windows",
            ip_address="192.168.1.1",
        )

        assert result is True
        # 验证 session 数据保存
        mock_redis.setex.assert_any_call(
            "session:sess_001", 30 * 24 * 60 * 60, mock_redis.setex.call_args_list[0][0][2]
        )
        # 验证 refresh_token 保存
        mock_redis.setex.assert_any_call("refresh_token:sess_001", 30 * 24 * 60 * 60, "refresh_abc")
        # 验证反向映射
        mock_redis.setex.assert_any_call("refresh_token_map:refresh_abc", 30 * 24 * 60 * 60, "sess_001")
        # 验证用户集合
        mock_redis.sadd.assert_called_once_with("user_sessions:user_001", "sess_001")
        mock_redis.expire.assert_called_once()

    def test_save_custom_expiry(self, session_store, mock_redis):
        """自定义过期时间"""
        session_store.save_session(
            session_id="sess_001",
            user_id="user_001",
            refresh_token="tok",
            device_info="d",
            ip_address="1.1.1.1",
            expires_in_seconds=3600,
        )

        # 验证 TTL 为 3600
        call_args = mock_redis.setex.call_args_list[0]
        assert call_args[0][1] == 3600

    def test_save_returns_false_on_error(self, session_store, mock_redis):
        """Redis 异常时返回 False"""
        mock_redis.setex.side_effect = Exception("Connection error")

        result = session_store.save_session(
            session_id="s1",
            user_id="u1",
            refresh_token="t1",
            device_info="d",
            ip_address="1.1.1.1",
        )
        assert result is False


class TestSessionStoreGet:
    """get_session 测试"""

    def test_get_existing_session(self, session_store, mock_redis):
        """获取存在的 session"""
        import json

        session_data = {
            "session_id": "sess_001",
            "user_id": "user_001",
            "device_info": "Chrome",
            "ip_address": "1.1.1.1",
        }
        mock_redis.get.return_value = json.dumps(session_data)

        result = session_store.get_session("sess_001")
        assert result is not None
        assert result["user_id"] == "user_001"
        assert result["session_id"] == "sess_001"
        mock_redis.get.assert_called_once_with("session:sess_001")

    def test_get_nonexistent_session(self, session_store, mock_redis):
        """获取不存在的 session 返回 None"""
        mock_redis.get.return_value = None

        result = session_store.get_session("nonexistent")
        assert result is None

    def test_get_returns_none_on_error(self, session_store, mock_redis):
        """Redis 异常返回 None"""
        mock_redis.get.side_effect = Exception("error")
        result = session_store.get_session("s1")
        assert result is None


class TestSessionStoreGetByRefreshToken:
    """get_session_by_refresh_token 测试"""

    def test_get_by_refresh_token_success(self, session_store, mock_redis):
        """通过 refresh_token 获取成功"""
        import json

        session_data = {"session_id": "sess_001", "user_id": "user_001"}

        # 第一次调用（反向映射）返回 session_id
        # 第二次调用（session数据）返回 json
        mock_redis.get.side_effect = ["sess_001", json.dumps(session_data)]

        result = session_store.get_session_by_refresh_token("refresh_abc")
        assert result is not None
        assert result["session_id"] == "sess_001"

    def test_get_by_refresh_token_not_found(self, session_store, mock_redis):
        """refresh_token 不存在返回 None"""
        mock_redis.get.return_value = None

        result = session_store.get_session_by_refresh_token("invalid")
        assert result is None


class TestSessionStoreGetRefreshToken:
    """get_refresh_token 测试"""

    def test_get_refresh_token_success(self, session_store, mock_redis):
        """获取 refresh_token 成功"""
        mock_redis.get.return_value = "refresh_abc"

        result = session_store.get_refresh_token("sess_001")
        assert result == "refresh_abc"
        mock_redis.get.assert_called_once_with("refresh_token:sess_001")

    def test_get_refresh_token_not_found(self, session_store, mock_redis):
        """不存在返回 None"""
        mock_redis.get.return_value = None
        assert session_store.get_refresh_token("sess_001") is None


class TestSessionStoreUpdateLastActive:
    """update_last_active 测试"""

    def test_update_success(self, session_store, mock_redis):
        """更新成功"""
        import json

        session_data = {
            "session_id": "sess_001",
            "user_id": "user_001",
            "last_active_at": "2024-01-01T00:00:00+00:00",
        }
        mock_redis.get.return_value = json.dumps(session_data)
        mock_redis.ttl.return_value = 1800

        result = session_store.update_last_active("sess_001")
        assert result is True
        mock_redis.setex.assert_called_once()

    def test_update_session_not_found(self, session_store, mock_redis):
        """session 不存在返回 False"""
        mock_redis.get.return_value = None
        result = session_store.update_last_active("nonexistent")
        assert result is False

    def test_update_expired_session(self, session_store, mock_redis):
        """已过期的 session 返回 False"""
        import json

        session_data = {"session_id": "s1", "user_id": "u1"}
        mock_redis.get.return_value = json.dumps(session_data)
        mock_redis.ttl.return_value = -2  # 已过期

        result = session_store.update_last_active("s1")
        assert result is False


class TestSessionStoreDelete:
    """delete_session 测试"""

    def test_delete_success(self, session_store, mock_redis):
        """删除成功"""
        import json

        session_data = {"session_id": "sess_001", "user_id": "user_001"}
        mock_redis.get.side_effect = [
            json.dumps(session_data),  # get_session
            "refresh_abc",  # get refresh_token
        ]

        result = session_store.delete_session("sess_001")
        assert result is True
        # 删除 session、refresh_token、反向映射、从用户集合移除
        assert mock_redis.delete.call_count >= 3
        mock_redis.srem.assert_called_once_with("user_sessions:user_001", "sess_001")

    def test_delete_not_found(self, session_store, mock_redis):
        """删除不存在的 session 返回 False"""
        mock_redis.get.return_value = None
        result = session_store.delete_session("nonexistent")
        assert result is False


class TestSessionStoreUserSessions:
    """用户 Session 列表测试"""

    def test_get_user_sessions(self, session_store, mock_redis):
        """获取用户所有 session"""
        import json

        mock_redis.smembers.return_value = {"sess_001", "sess_002"}

        session1 = json.dumps({"session_id": "sess_001", "user_id": "u1"})
        session2 = json.dumps({"session_id": "sess_002", "user_id": "u1"})
        mock_redis.get.side_effect = [session1, session2]

        result = session_store.get_user_sessions("user_001")
        assert len(result) == 2

    def test_get_user_sessions_empty(self, session_store, mock_redis):
        """用户无 session"""
        mock_redis.smembers.return_value = set()
        result = session_store.get_user_sessions("user_001")
        assert result == []

    def test_delete_all_user_sessions(self, session_store, mock_redis):
        """删除用户所有 session"""
        import json

        mock_redis.smembers.return_value = {"sess_001", "sess_002"}

        session1 = json.dumps({"session_id": "sess_001", "user_id": "u1"})
        session2 = json.dumps({"session_id": "sess_002", "user_id": "u1"})
        # get 调用顺序：
        # 1-2: get_user_sessions 中两个 session 的 get
        # 3-4: delete sess_001 (get_session + get refresh_token)
        # 5-6: delete sess_002 (get_session + get refresh_token)
        mock_redis.get.side_effect = [
            session1,
            session2,  # get_user_sessions
            session1,
            "tok1",  # delete sess_001
            session2,
            "tok2",  # delete sess_002
        ]

        count = session_store.delete_all_user_sessions("user_001")
        assert count == 2

    def test_delete_all_empty_user(self, session_store, mock_redis):
        """删除无 session 的用户"""
        mock_redis.smembers.return_value = set()
        count = session_store.delete_all_user_sessions("user_001")
        assert count == 0


class TestSessionStoreExists:
    """session_exists 测试"""

    def test_exists_true(self, session_store, mock_redis):
        """存在返回 True"""
        mock_redis.exists.return_value = 1
        assert session_store.session_exists("sess_001") is True

    def test_exists_false(self, session_store, mock_redis):
        """不存在返回 False"""
        mock_redis.exists.return_value = 0
        assert session_store.session_exists("sess_001") is False

    def test_exists_error_returns_false(self, session_store, mock_redis):
        """异常返回 False"""
        mock_redis.exists.side_effect = Exception("error")
        assert session_store.session_exists("sess_001") is False


class TestGetSessionStore:
    """工厂函数测试"""

    def test_disabled_returns_noop(self):
        """禁用返回 NoopSessionStore"""
        store = get_session_store(enabled=False)
        assert isinstance(store, NoopSessionStore)
