"""
RedisMemoryProvider 單元測試
==============================

測試案例:
- test_save_and_load: 儲存並讀取
- test_delete: 刪除
- test_ttl_expiry: TTL 相關 (mock)
- test_key_prefix: Key 前綴
- test_exists: 存在檢查
- test_update: 部分更新
- test_graceful_degradation: 優雅降級
"""

import pytest
from unittest.mock import patch, AsyncMock
from pydantic import BaseModel

from lewooogo_data.providers.redis_memory import (
    RedisMemoryProvider,
    DEFAULT_TTL_SECONDS,
)


class SampleModel(BaseModel):
    """測試用的 Pydantic Model"""
    id: str
    name: str
    value: int = 0


# =============================================================================
# 基本功能測試
# =============================================================================

class TestRedisMemoryProviderBasic:
    """基本功能測試"""

    @pytest.mark.asyncio
    async def test_save_and_load(self, patch_redis_pool, mock_redis):
        """測試儲存並讀取資料"""
        # Arrange
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="item-1", name="Test Item", value=42)

        # Act
        save_result = await provider.save("item-1", data)

        # 驗證儲存成功
        assert save_result is True

        # 驗證資料已存入 mock storage
        full_key = "test:item-1"
        assert full_key in mock_redis._storage

        # 載入並驗證
        loaded = await provider.load("item-1")

        # Assert
        assert loaded is not None
        assert loaded.id == "item-1"
        assert loaded.name == "Test Item"
        assert loaded.value == 42

    @pytest.mark.asyncio
    async def test_delete(self, patch_redis_pool, mock_redis):
        """測試刪除資料"""
        # Arrange
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="item-del", name="To Delete", value=0)

        # 先儲存
        await provider.save("item-del", data)
        assert "test:item-del" in mock_redis._storage

        # Act
        delete_result = await provider.delete("item-del")

        # Assert
        assert delete_result is True
        assert "test:item-del" not in mock_redis._storage

    @pytest.mark.asyncio
    async def test_delete_nonexistent(self, patch_redis_pool, mock_redis):
        """測試刪除不存在的資料"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        delete_result = await provider.delete("nonexistent")

        # Assert
        assert delete_result is False

    @pytest.mark.asyncio
    async def test_load_nonexistent(self, patch_redis_pool, mock_redis):
        """測試載入不存在的資料"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        loaded = await provider.load("nonexistent")

        # Assert
        assert loaded is None


# =============================================================================
# TTL 測試
# =============================================================================

class TestRedisMemoryProviderTTL:
    """TTL 相關測試"""

    @pytest.mark.asyncio
    async def test_default_ttl(self, patch_redis_pool, mock_redis):
        """測試預設 TTL (7 天)"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="ttl-1", name="TTL Test", value=1)

        # Act
        await provider.save("ttl-1", data)

        # Assert - 驗證 TTL 為預設值
        assert mock_redis._ttls["test:ttl-1"] == DEFAULT_TTL_SECONDS

    @pytest.mark.asyncio
    async def test_custom_ttl(self, patch_redis_pool, mock_redis):
        """測試自訂 TTL"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="ttl-2", name="Custom TTL", value=2)

        # Act - 設定 1 小時 TTL
        custom_ttl = 3600
        await provider.save("ttl-2", data, ttl_seconds=custom_ttl)

        # Assert
        assert mock_redis._ttls["test:ttl-2"] == custom_ttl

    @pytest.mark.asyncio
    async def test_get_ttl(self, patch_redis_pool, mock_redis):
        """測試取得剩餘 TTL"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="ttl-3", name="Get TTL", value=3)

        await provider.save("ttl-3", data, ttl_seconds=1800)

        # Act
        ttl = await provider.get_ttl("ttl-3")

        # Assert
        assert ttl == 1800

    @pytest.mark.asyncio
    async def test_get_ttl_nonexistent(self, patch_redis_pool, mock_redis):
        """測試取得不存在 key 的 TTL"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        ttl = await provider.get_ttl("nonexistent")

        # Assert
        assert ttl == -2

    @pytest.mark.asyncio
    async def test_extend_ttl(self, patch_redis_pool, mock_redis):
        """測試延長 TTL"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="ttl-4", name="Extend TTL", value=4)

        await provider.save("ttl-4", data, ttl_seconds=1000)

        # Act - 延長 500 秒
        result = await provider.extend_ttl("ttl-4", 500)

        # Assert
        assert result is True
        assert mock_redis._ttls["test:ttl-4"] == 1500


# =============================================================================
# Key Prefix 測試
# =============================================================================

class TestRedisMemoryProviderKeyPrefix:
    """Key 前綴測試"""

    @pytest.mark.asyncio
    async def test_key_prefix(self, patch_redis_pool, mock_redis):
        """測試 Key 前綴正確套用"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="myapp:incidents",
        )
        data = SampleModel(id="inc-1", name="Incident", value=100)

        # Act
        await provider.save("inc-1", data)

        # Assert
        expected_key = "myapp:incidents:inc-1"
        assert expected_key in mock_redis._storage

    @pytest.mark.asyncio
    async def test_different_prefixes_isolated(self, patch_redis_pool, mock_redis):
        """測試不同前綴的資料隔離"""
        provider_a = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="app-a",
        )
        provider_b = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="app-b",
        )

        data_a = SampleModel(id="shared-key", name="App A", value=1)
        data_b = SampleModel(id="shared-key", name="App B", value=2)

        # Act
        await provider_a.save("shared-key", data_a)
        await provider_b.save("shared-key", data_b)

        # Assert - 兩個 provider 的資料分開
        assert "app-a:shared-key" in mock_redis._storage
        assert "app-b:shared-key" in mock_redis._storage

        loaded_a = await provider_a.load("shared-key")
        loaded_b = await provider_b.load("shared-key")

        assert loaded_a.name == "App A"
        assert loaded_b.name == "App B"


# =============================================================================
# 存在檢查測試
# =============================================================================

class TestRedisMemoryProviderExists:
    """存在檢查測試"""

    @pytest.mark.asyncio
    async def test_exists_true(self, patch_redis_pool, mock_redis):
        """測試存在的 key"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="exist-1", name="Exists", value=1)
        await provider.save("exist-1", data)

        # Act
        result = await provider.exists("exist-1")

        # Assert
        assert result is True

    @pytest.mark.asyncio
    async def test_exists_false(self, patch_redis_pool, mock_redis):
        """測試不存在的 key"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        result = await provider.exists("nonexistent")

        # Assert
        assert result is False


# =============================================================================
# 部分更新測試
# =============================================================================

class TestRedisMemoryProviderUpdate:
    """部分更新測試"""

    @pytest.mark.asyncio
    async def test_update(self, patch_redis_pool, mock_redis):
        """測試部分更新"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="upd-1", name="Original", value=10)
        await provider.save("upd-1", data, ttl_seconds=3600)

        # Act - 只更新 value
        result = await provider.update("upd-1", {"value": 20})

        # Assert
        assert result is True

        loaded = await provider.load("upd-1")
        assert loaded.name == "Original"  # 保持不變
        assert loaded.value == 20  # 已更新

    @pytest.mark.asyncio
    async def test_update_nonexistent(self, patch_redis_pool, mock_redis):
        """測試更新不存在的資料"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        result = await provider.update("nonexistent", {"value": 999})

        # Assert
        assert result is False


# =============================================================================
# 優雅降級測試
# =============================================================================

class TestRedisMemoryProviderGracefulDegradation:
    """優雅降級測試"""

    @pytest.mark.asyncio
    async def test_load_without_init(self):
        """測試未初始化時 load 回傳 None"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act - 沒有 patch_redis_pool，pool 未初始化
        result = await provider.load("any-key")

        # Assert - 優雅降級，返回 None 而非拋異常
        assert result is None

    @pytest.mark.asyncio
    async def test_save_without_init(self):
        """測試未初始化時 save 回傳 False"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )
        data = SampleModel(id="x", name="X", value=0)

        # Act
        result = await provider.save("x", data)

        # Assert
        assert result is False

    @pytest.mark.asyncio
    async def test_delete_without_init(self):
        """測試未初始化時 delete 回傳 False"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        result = await provider.delete("any-key")

        # Assert
        assert result is False

    @pytest.mark.asyncio
    async def test_exists_without_init(self):
        """測試未初始化時 exists 回傳 False"""
        provider = RedisMemoryProvider(
            model_class=SampleModel,
            key_prefix="test",
        )

        # Act
        result = await provider.exists("any-key")

        # Assert
        assert result is False
