"""Tests for save_user_context tool."""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from redis.exceptions import RedisError

from src.geaicontext.tools.save import save_user_context, _async_save_to_mongodb


class TestAsyncSaveToMongoDB:
    """Test cases for _async_save_to_mongodb helper function."""

    @pytest.fixture
    def mock_mongo_client_instance(self):
        """Create a mock MongoClient instance."""
        client = MagicMock()
        client.connect = AsyncMock()
        client.disconnect = AsyncMock()
        client.save_context = AsyncMock(return_value=True)
        return client

    @pytest.mark.asyncio
    async def test_async_save_success(
        self, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test successful async save to MongoDB."""
        with patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            await _async_save_to_mongodb(sample_customer_id, sample_context)
            
            mock_mongo_client_instance.connect.assert_called_once()
            mock_mongo_client_instance.save_context.assert_called_once_with(
                sample_customer_id, sample_context
            )
            mock_mongo_client_instance.disconnect.assert_called_once()

    @pytest.mark.asyncio
    async def test_async_save_failure(
        self, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test async save when MongoDB save returns False."""
        mock_mongo_client_instance.save_context = AsyncMock(return_value=False)
        
        with patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            # Should not raise, just log error
            await _async_save_to_mongodb(sample_customer_id, sample_context)
            
            mock_mongo_client_instance.disconnect.assert_called_once()

    @pytest.mark.asyncio
    async def test_async_save_exception(
        self, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test async save when MongoDB raises exception."""
        mock_mongo_client_instance.save_context = AsyncMock(
            side_effect=Exception("MongoDB error")
        )
        
        with patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            # Should not raise, just log error
            await _async_save_to_mongodb(sample_customer_id, sample_context)
            
            mock_mongo_client_instance.disconnect.assert_called_once()


class TestSaveUserContext:
    """Test cases for save_user_context function."""

    @pytest.fixture
    def mock_redis_client_instance(self):
        """Create a mock RedisClient instance."""
        client = MagicMock()
        client.connect = AsyncMock()
        client.disconnect = AsyncMock()
        client.set_context = AsyncMock(return_value=True)
        return client

    @pytest.fixture
    def mock_mongo_client_instance(self):
        """Create a mock MongoClient instance."""
        client = MagicMock()
        client.connect = AsyncMock()
        client.disconnect = AsyncMock()
        client.save_context = AsyncMock(return_value=True)
        return client

    @pytest.mark.asyncio
    async def test_save_empty_customer_id(self, sample_context):
        """Test save with empty customer_id."""
        success, message = await save_user_context("", sample_context)
        
        assert success is False
        assert "customer_id is required" in message

    @pytest.mark.asyncio
    async def test_save_empty_context(self, sample_customer_id):
        """Test save with empty context."""
        success, message = await save_user_context(sample_customer_id, {})
        
        assert success is False
        assert "context data is required" in message

    @pytest.mark.asyncio
    async def test_save_none_context(self, sample_customer_id):
        """Test save with None context."""
        success, message = await save_user_context(sample_customer_id, None)
        
        assert success is False
        assert "context data is required" in message

    @pytest.mark.asyncio
    async def test_save_success(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test successful save."""
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            assert success is True
            assert sample_customer_id in message
            mock_redis_client_instance.set_context.assert_called_once_with(
                sample_customer_id, sample_context
            )

    @pytest.mark.asyncio
    async def test_save_redis_write_fails(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test save when Redis write returns False."""
        mock_redis_client_instance.set_context = AsyncMock(return_value=False)
        
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            assert success is False
            assert "Failed to write to Redis cache" in message

    @pytest.mark.asyncio
    async def test_save_redis_error(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test save when Redis raises error."""
        mock_redis_client_instance.set_context = AsyncMock(
            side_effect=RedisError("Redis connection failed")
        )
        
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            assert success is False
            assert "Redis error" in message

    @pytest.mark.asyncio
    async def test_save_redis_unexpected_error(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test save when Redis raises unexpected error."""
        mock_redis_client_instance.set_context = AsyncMock(
            side_effect=Exception("Unexpected error")
        )
        
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            assert success is False
            assert "Unexpected error" in message

    @pytest.mark.asyncio
    async def test_save_mongodb_async_fails(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test save succeeds even when MongoDB async write fails."""
        mock_mongo_client_instance.save_context = AsyncMock(
            side_effect=Exception("MongoDB error")
        )
        
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            # Should still succeed because Redis write succeeded
            assert success is True

    @pytest.mark.asyncio
    async def test_save_disconnects_redis(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test that Redis client is disconnected after save."""
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            await save_user_context(sample_customer_id, sample_context)
            
            mock_redis_client_instance.disconnect.assert_called_once()

    @pytest.mark.asyncio
    async def test_save_disconnects_redis_on_error(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test that Redis client is disconnected even on error."""
        mock_redis_client_instance.set_context = AsyncMock(
            side_effect=RedisError("Error")
        )
        
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            await save_user_context(sample_customer_id, sample_context)
            
            mock_redis_client_instance.disconnect.assert_called_once()

    @pytest.mark.asyncio
    async def test_save_schedules_mongodb_write(
        self, mock_redis_client_instance, mock_mongo_client_instance, sample_context, sample_customer_id
    ):
        """Test that MongoDB write is scheduled after Redis success."""
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save.get_mongo_client",
            AsyncMock(return_value=mock_mongo_client_instance)
        ):
            await save_user_context(sample_customer_id, sample_context)
            
            mock_mongo_client_instance.save_context.assert_called_once_with(
                sample_customer_id, sample_context
            )

    @pytest.mark.asyncio
    async def test_save_mongodb_schedule_exception(
        self, mock_redis_client_instance, sample_context, sample_customer_id
    ):
        """Test save succeeds even when scheduling MongoDB write raises exception."""
        with patch(
            "src.geaicontext.tools.save.get_redis_client",
            AsyncMock(return_value=mock_redis_client_instance)
        ), patch(
            "src.geaicontext.tools.save._async_save_to_mongodb",
            AsyncMock(side_effect=Exception("Schedule failed"))
        ):
            success, message = await save_user_context(sample_customer_id, sample_context)
            
            # Should still succeed because Redis write succeeded
            assert success is True
