"""
Edge case and error handling tests for notifications.
"""
from unittest.mock import Mock, patch, MagicMock
import pytest


class TestNotificationEdgeCases:
    """Test edge cases in notification handling."""

    def test_notification_with_very_long_verb(self):
        """Test notification with verb at max length."""
        from smoothschedule.communication.notifications.models import Notification

        # Arrange
        long_verb = 'a' * 255  # Max length is 255

        # Assert - field allows up to 255 characters
        verb_field = Notification._meta.get_field('verb')
        assert verb_field.max_length == 255
        assert len(long_verb) == 255

    def test_notification_with_empty_data_field(self):
        """Test notification with empty data dictionary."""
        # Arrange
        notification = Mock()
        notification.data = {}

        # Assert
        assert notification.data == {}
        assert isinstance(notification.data, dict)

    def test_notification_with_complex_nested_data(self):
        """Test notification with deeply nested JSON data."""
        # Arrange
        complex_data = {
            'level1': {
                'level2': {
                    'level3': {
                        'value': 'deeply nested'
                    }
                },
                'array': [1, 2, 3]
            },
            'metadata': {
                'timestamp': '2024-01-15T10:00:00Z',
                'user_agent': 'Mozilla/5.0'
            }
        }

        notification = Mock()
        notification.data = complex_data

        # Assert
        assert notification.data['level1']['level2']['level3']['value'] == 'deeply nested'
        assert notification.data['level1']['array'] == [1, 2, 3]

    def test_notification_with_unicode_characters(self):
        """Test notification with unicode characters in verb."""
        # Arrange
        unicode_verb = 'sent you a message 你好 🎉'

        notification = Mock()
        notification.verb = unicode_verb

        # Assert
        assert '你好' in notification.verb
        assert '🎉' in notification.verb

    def test_notification_with_null_actor_and_target(self):
        """Test system notification with no actor or target."""
        from smoothschedule.communication.notifications.serializers import NotificationSerializer

        # Arrange
        mock_notification = Mock()
        mock_notification.actor = None
        mock_notification.actor_content_type = None
        mock_notification.target = None
        mock_notification.target_content_type = None

        serializer = NotificationSerializer()

        # Act
        actor_display = serializer.get_actor_display(mock_notification)
        actor_type = serializer.get_actor_type(mock_notification)
        target_display = serializer.get_target_display(mock_notification)
        target_type = serializer.get_target_type(mock_notification)

        # Assert
        assert actor_display == 'System'
        assert actor_type is None
        assert target_display is None
        assert target_type is None

    def test_notification_count_with_zero_results(self):
        """Test unread count when no notifications exist."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory

        # Arrange
        factory = APIRequestFactory()
        request = factory.get('/api/notifications/unread_count/')

        mock_user = Mock()
        request.user = mock_user

        viewset = NotificationViewSet()
        viewset.request = request

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            mock_qs = Mock()
            mock_filtered = Mock()
            mock_filtered.count.return_value = 0
            mock_qs.filter.return_value = mock_filtered
            mock_get_qs.return_value = mock_qs

            response = viewset.unread_count(request)

        # Assert
        assert response.data == {'count': 0}

    def test_list_with_limit_zero(self):
        """Test list with limit parameter set to zero."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory
        from rest_framework.request import Request

        # Arrange
        factory = APIRequestFactory()
        django_request = factory.get('/api/notifications/?limit=0')

        mock_user = Mock()
        django_request.user = mock_user

        request = Request(django_request)

        viewset = NotificationViewSet()
        viewset.request = request
        viewset.format_kwarg = None

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            with patch.object(viewset, 'get_serializer') as mock_serializer:
                mock_qs = Mock()
                mock_qs.__getitem__ = Mock(return_value=[])
                mock_get_qs.return_value = mock_qs
                mock_serializer.return_value.data = []

                response = viewset.list(request)

        # Assert
        # Should slice with [:0] which returns empty list
        call_args = mock_qs.__getitem__.call_args[0][0]
        assert call_args == slice(None, 0, None)

    def test_list_with_very_large_limit(self):
        """Test list with extremely large limit."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory
        from rest_framework.request import Request

        # Arrange
        factory = APIRequestFactory()
        django_request = factory.get('/api/notifications/?limit=9999999')

        mock_user = Mock()
        django_request.user = mock_user

        request = Request(django_request)

        viewset = NotificationViewSet()
        viewset.request = request
        viewset.format_kwarg = None

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            with patch.object(viewset, 'get_serializer') as mock_serializer:
                mock_qs = Mock()
                mock_qs.__getitem__ = Mock(return_value=[])
                mock_get_qs.return_value = mock_qs
                mock_serializer.return_value.data = []

                response = viewset.list(request)

        # Assert
        call_args = mock_qs.__getitem__.call_args[0][0]
        assert call_args == slice(None, 9999999, None)


class TestNotificationViewSetErrorHandling:
    """Test error handling in NotificationViewSet."""

    def test_mark_read_with_invalid_notification_id(self):
        """Test mark_read with non-existent notification."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory

        # Arrange
        factory = APIRequestFactory()
        request = factory.post('/api/notifications/99999/mark_read/')

        viewset = NotificationViewSet()
        viewset.request = request

        # Act
        with patch.object(viewset, 'get_object', side_effect=Exception('Not found')):
            with pytest.raises(Exception) as exc_info:
                viewset.mark_read(request, pk=99999)

        # Assert
        assert 'Not found' in str(exc_info.value)

    def test_mark_all_read_when_already_all_read(self):
        """Test mark_all_read when all notifications are already read."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory

        # Arrange
        factory = APIRequestFactory()
        request = factory.post('/api/notifications/mark_all_read/')

        viewset = NotificationViewSet()
        viewset.request = request

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            mock_qs = Mock()
            mock_filtered = Mock()
            mock_filtered.update.return_value = 0  # None updated
            mock_qs.filter.return_value = mock_filtered
            mock_get_qs.return_value = mock_qs

            response = viewset.mark_all_read(request)

        # Assert
        assert response.data == {'status': 'marked 0 notifications as read'}

    def test_clear_all_when_no_read_notifications(self):
        """Test clear_all when there are no read notifications."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory

        # Arrange
        factory = APIRequestFactory()
        request = factory.delete('/api/notifications/clear_all/')

        viewset = NotificationViewSet()
        viewset.request = request

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            mock_qs = Mock()
            mock_filtered = Mock()
            mock_filtered.delete.return_value = (0, {})
            mock_qs.filter.return_value = mock_filtered
            mock_get_qs.return_value = mock_qs

            response = viewset.clear_all(request)

        # Assert
        assert response.data == {'status': 'deleted 0 notifications'}

    def test_list_with_invalid_read_parameter(self):
        """Test list with invalid read parameter value."""
        from smoothschedule.communication.notifications.views import NotificationViewSet
        from rest_framework.test import APIRequestFactory
        from rest_framework.request import Request

        # Arrange
        factory = APIRequestFactory()
        django_request = factory.get('/api/notifications/?read=invalid')

        mock_user = Mock()
        django_request.user = mock_user

        request = Request(django_request)

        viewset = NotificationViewSet()
        viewset.request = request
        viewset.format_kwarg = None

        # Act
        with patch.object(viewset, 'get_queryset') as mock_get_qs:
            with patch.object(viewset, 'get_serializer') as mock_serializer:
                # Create a mock queryset that supports slicing
                mock_qs = MagicMock()
                mock_qs.__getitem__ = MagicMock(return_value=[])
                mock_get_qs.return_value = mock_qs
                mock_serializer.return_value.data = []

                response = viewset.list(request)

        # Assert
        # 'invalid'.lower() != 'true', so filter should not be applied
        # Queryset should not be filtered by read status
        assert response.status_code == 200


class TestSerializerEdgeCases:
    """Test edge cases in notification serializer."""

    def test_get_target_display_with_all_attributes_missing(self):
        """Test target display when object has no subject, title, or name."""
        from smoothschedule.communication.notifications.serializers import NotificationSerializer

        # Arrange
        mock_target = Mock(spec=['__str__'])
        mock_target.__str__ = Mock(return_value='Generic Object')

        mock_notification = Mock()
        mock_notification.target = mock_target

        serializer = NotificationSerializer()

        # Act
        result = serializer.get_target_display(mock_notification)

        # Assert
        assert result == 'Generic Object'

    def test_get_actor_display_with_none_full_name(self):
        """Test actor display when full_name is None."""
        from smoothschedule.communication.notifications.serializers import NotificationSerializer

        # Arrange
        mock_actor = Mock()
        mock_actor.full_name = None
        mock_actor.email = 'user@example.com'

        mock_notification = Mock()
        mock_notification.actor = mock_actor

        serializer = NotificationSerializer()

        # Act
        result = serializer.get_actor_display(mock_notification)

        # Assert
        assert result == 'user@example.com'

    def test_get_target_url_with_unknown_model(self):
        """Test target URL with unmapped model type."""
        from smoothschedule.communication.notifications.serializers import NotificationSerializer

        # Arrange
        mock_content_type = Mock()
        mock_content_type.model = 'unknown_model_type'

        mock_notification = Mock()
        mock_notification.target_content_type = mock_content_type
        mock_notification.target_object_id = '123'

        serializer = NotificationSerializer()

        # Act
        result = serializer.get_target_url(mock_notification)

        # Assert
        assert result is None

    def test_serializer_with_minimal_notification(self):
        """Test serializer with minimal notification data."""
        from smoothschedule.communication.notifications.serializers import NotificationSerializer

        # Arrange
        mock_notification = Mock()
        mock_notification.id = 1
        mock_notification.verb = 'test'
        mock_notification.read = False
        mock_notification.timestamp = Mock()
        mock_notification.data = {}
        mock_notification.actor = None
        mock_notification.actor_content_type = None
        mock_notification.target = None
        mock_notification.target_content_type = None

        serializer = NotificationSerializer()

        # Act
        actor_display = serializer.get_actor_display(mock_notification)
        target_display = serializer.get_target_display(mock_notification)
        target_url = serializer.get_target_url(mock_notification)

        # Assert
        assert actor_display == 'System'
        assert target_display is None
        assert target_url is None


class TestModelEdgeCases:
    """Test edge cases in notification model."""

    def test_str_method_with_long_verb(self):
        """Test __str__ method with very long verb."""
        from smoothschedule.communication.notifications.models import Notification

        # Arrange
        mock_user = Mock()
        mock_user.email = "user@example.com"

        mock_timestamp = Mock()
        mock_timestamp.strftime.return_value = "2024-01-15 14:30"

        notification = Mock(spec=Notification)
        notification.recipient = mock_user
        notification.verb = 'a' * 100  # Very long verb
        notification.timestamp = mock_timestamp

        # Act
        result = Notification.__str__(notification)

        # Assert
        assert 'user@example.com' in result
        assert len(notification.verb) == 100

    def test_notification_with_multiple_indexes(self):
        """Test that notification model has proper indexes defined."""
        from smoothschedule.communication.notifications.models import Notification

        # Get indexes
        indexes = Notification._meta.indexes

        # Assert
        assert len(indexes) == 2

        # Check that indexes cover common query patterns
        index_fields = [tuple(idx.fields) for idx in indexes]
        assert ('recipient', 'read', 'timestamp') in index_fields
        assert ('recipient', 'timestamp') in index_fields

    def test_notification_cascade_behavior(self):
        """Test that cascade delete is properly configured."""
        from smoothschedule.communication.notifications.models import Notification
        from django.db.models import CASCADE

        # Check recipient field
        recipient_field = Notification._meta.get_field('recipient')
        assert recipient_field.remote_field.on_delete == CASCADE

        # Check content type fields
        actor_ct_field = Notification._meta.get_field('actor_content_type')
        assert actor_ct_field.remote_field.on_delete == CASCADE


class TestConcurrentOperations:
    """Test concurrent notification operations."""

    def test_multiple_users_marking_different_notifications(self):
        """Test multiple users marking their own notifications simultaneously."""
        from smoothschedule.communication.notifications.views import NotificationViewSet

        # Arrange
        user1 = Mock(id=1)
        user2 = Mock(id=2)

        notif1 = Mock(id=10, recipient=user1, read=False)
        notif1.save = Mock()

        notif2 = Mock(id=20, recipient=user2, read=False)
        notif2.save = Mock()

        # Act - Simulate concurrent operations
        notif1.read = True
        notif1.save(update_fields=['read'])

        notif2.read = True
        notif2.save(update_fields=['read'])

        # Assert
        assert notif1.read is True
        assert notif2.read is True
        notif1.save.assert_called_with(update_fields=['read'])
        notif2.save.assert_called_with(update_fields=['read'])

    def test_concurrent_notification_creation(self):
        """Test creating multiple notifications concurrently."""
        from smoothschedule.communication.notifications.models import Notification

        # Arrange
        recipients = [Mock(id=i) for i in range(1, 4)]

        # Act - Simulate bulk creation by mocking the notification objects
        with patch.object(Notification.objects, 'bulk_create') as mock_bulk:
            # Create mock notification objects instead of real ones
            notifications = [
                Mock(spec=Notification, recipient=recipient, verb='concurrent notification')
                for recipient in recipients
            ]

            Notification.objects.bulk_create(notifications)

        # Assert
        mock_bulk.assert_called_once()
        assert len(mock_bulk.call_args[0][0]) == 3


class TestDataIntegrity:
    """Test data integrity constraints."""

    def test_notification_requires_recipient(self):
        """Test that recipient is a required field."""
        from smoothschedule.communication.notifications.models import Notification

        # Get the recipient field
        recipient_field = Notification._meta.get_field('recipient')

        # Assert - field should not allow null
        assert recipient_field.null is False

    def test_notification_requires_verb(self):
        """Test that verb is a required field."""
        from smoothschedule.communication.notifications.models import Notification

        # Get the verb field
        verb_field = Notification._meta.get_field('verb')

        # Assert - field should not allow null/blank
        assert verb_field.null is False
        assert verb_field.blank is False

    def test_generic_fk_fields_allow_null(self):
        """Test that generic foreign key fields properly allow null."""
        from smoothschedule.communication.notifications.models import Notification

        # Check object ID fields
        actor_id = Notification._meta.get_field('actor_object_id')
        action_id = Notification._meta.get_field('action_object_object_id')
        target_id = Notification._meta.get_field('target_object_id')

        # Assert
        assert actor_id.null is True
        assert action_id.null is True
        assert target_id.null is True
