"""
PgMemoryProvider 單元測試
===========================

測試案例:
- test_save_and_load: 儲存並讀取
- test_upsert: 更新已存在的資料
- test_delete: 刪除
- test_exists: 存在檢查
- test_sqlite_forbidden: SQLite 禁止測試
"""

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

from lewooogo_data.providers.pg_memory import (
    PgMemoryProvider,
    get_database_url,
    DEFAULT_PG_URL,
)


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


# =============================================================================
# 資料庫 URL 測試
# =============================================================================

class TestDatabaseUrl:
    """資料庫 URL 相關測試"""

    def test_default_url(self):
        """測試預設 URL"""
        with patch.dict("os.environ", {}, clear=True):
            url = get_database_url()
            assert url == DEFAULT_PG_URL

    def test_env_url(self):
        """測試環境變數 URL"""
        custom_url = "postgresql+asyncpg://user:pass@localhost:5432/mydb"
        with patch.dict("os.environ", {"DATABASE_URL": custom_url}):
            url = get_database_url()
            assert url == custom_url

    def test_sqlite_forbidden(self):
        """測試 SQLite 被禁止"""
        sqlite_url = "sqlite:///test.db"
        with patch.dict("os.environ", {"DATABASE_URL": sqlite_url}):
            url = get_database_url()
            # 統帥鐵律: SQLite 被禁止，應返回預設 PostgreSQL
            assert url == DEFAULT_PG_URL
            assert "sqlite" not in url.lower()


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

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

    @pytest.mark.asyncio
    async def test_load(self, patch_pg_session, mock_session):
        """測試載入資料"""
        # Arrange
        provider = PgMemoryProvider(model_class=SampleModel)

        # 預先放入資料
        expected_data = SampleModel(id="pg-1", name="PG Test", value=100)
        mock_session._storage["pg-1"] = expected_data

        # Act
        result = await provider.load("pg-1")

        # Assert
        assert result is not None
        assert result.id == "pg-1"
        assert result.name == "PG Test"
        assert result.value == 100

    @pytest.mark.asyncio
    async def test_load_nonexistent(self, patch_pg_session, mock_session):
        """測試載入不存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

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

        # Assert
        assert result is None

    @pytest.mark.asyncio
    async def test_save(self, patch_pg_session, mock_session):
        """測試儲存資料"""
        provider = PgMemoryProvider(model_class=SampleModel)
        data = SampleModel(id="pg-2", name="Save Test", value=200)

        # Act
        result = await provider.save("pg-2", data)

        # Assert
        assert result is True
        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()

    @pytest.mark.asyncio
    async def test_save_ttl_ignored(self, patch_pg_session, mock_session):
        """測試儲存時 TTL 被忽略 (PostgreSQL 永久保存)"""
        provider = PgMemoryProvider(model_class=SampleModel)
        data = SampleModel(id="pg-3", name="TTL Ignored", value=300)

        # Act - 傳入 TTL 應被忽略
        result = await provider.save("pg-3", data, ttl_seconds=3600)

        # Assert
        assert result is True
        mock_session.add.assert_called_once()


# =============================================================================
# 刪除測試
# =============================================================================

class TestPgMemoryProviderDelete:
    """刪除相關測試"""

    @pytest.mark.asyncio
    async def test_delete_existing(self, patch_pg_session, mock_session):
        """測試刪除存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

        # 預先放入資料
        existing_data = SampleModel(id="del-1", name="To Delete", value=0)
        mock_session._storage["del-1"] = existing_data

        # Act
        result = await provider.delete("del-1")

        # Assert
        assert result is True
        mock_session.delete.assert_called_once()
        mock_session.commit.assert_called()

    @pytest.mark.asyncio
    async def test_delete_nonexistent(self, patch_pg_session, mock_session):
        """測試刪除不存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

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

        # Assert
        assert result is False


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

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

    @pytest.mark.asyncio
    async def test_exists_true(self, patch_pg_session, mock_session):
        """測試存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

        # 預先放入資料
        existing_data = SampleModel(id="exist-1", name="Exists", value=1)
        mock_session._storage["exist-1"] = existing_data

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

        # Assert
        assert result is True

    @pytest.mark.asyncio
    async def test_exists_false(self, patch_pg_session, mock_session):
        """測試不存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

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

        # Assert
        assert result is False


# =============================================================================
# 更新測試
# =============================================================================

class TestPgMemoryProviderUpdate:
    """更新相關測試"""

    @pytest.mark.asyncio
    async def test_update_existing(self, patch_pg_session, mock_session):
        """測試更新存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

        # 預先放入資料 (使用可變物件)
        class MutableModel:
            def __init__(self):
                self.id = "upd-1"
                self.name = "Original"
                self.value = 10

        existing = MutableModel()
        mock_session._storage["upd-1"] = existing

        # Act
        result = await provider.update("upd-1", {"value": 99, "name": "Updated"})

        # Assert
        assert result is True
        assert existing.value == 99
        assert existing.name == "Updated"
        mock_session.commit.assert_called()

    @pytest.mark.asyncio
    async def test_update_nonexistent(self, patch_pg_session, mock_session):
        """測試更新不存在的資料"""
        provider = PgMemoryProvider(model_class=SampleModel)

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

        # Assert
        assert result is False


# =============================================================================
# 錯誤處理測試
# =============================================================================

class TestPgMemoryProviderErrors:
    """錯誤處理測試"""

    @pytest.mark.asyncio
    async def test_load_without_model_class(self, patch_pg_session):
        """測試無 model_class 時 load 拋異常"""
        provider = PgMemoryProvider(model_class=None)

        # Act & Assert
        with pytest.raises(ValueError, match="model_class is required"):
            await provider.load("any-key")

    @pytest.mark.asyncio
    async def test_delete_without_model_class(self, patch_pg_session):
        """測試無 model_class 時 delete 拋異常"""
        provider = PgMemoryProvider(model_class=None)

        # Act & Assert
        with pytest.raises(ValueError, match="model_class is required"):
            await provider.delete("any-key")

    @pytest.mark.asyncio
    async def test_update_without_model_class(self, patch_pg_session):
        """測試無 model_class 時 update 拋異常"""
        provider = PgMemoryProvider(model_class=None)

        # Act & Assert
        with pytest.raises(ValueError, match="model_class is required"):
            await provider.update("any-key", {"value": 1})
