"""
Unit tests for communication/messaging/views.py

Tests broadcast message views, inbox views, and helper functions.
"""

from unittest.mock import Mock, patch, MagicMock

import pytest
from django.utils import timezone

from smoothschedule.communication.messaging.views import (
    CanSendMessagesPermission,
    send_websocket_notification,
    create_notification_for_user,
    BroadcastMessageViewSet,
    InboxViewSet,
)


class TestCanSendMessagesPermission:
    """Tests for CanSendMessagesPermission."""

    def test_denies_unauthenticated_users(self):
        """Should deny unauthenticated users."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = False

        assert permission.has_permission(request, None) is False

    def test_allows_get_for_authenticated_users(self):
        """Should allow GET for authenticated users."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.method = 'GET'

        assert permission.has_permission(request, None) is True

    def test_allows_head_for_authenticated_users(self):
        """Should allow HEAD for authenticated users."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.method = 'HEAD'

        assert permission.has_permission(request, None) is True

    def test_allows_options_for_authenticated_users(self):
        """Should allow OPTIONS for authenticated users."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.method = 'OPTIONS'

        assert permission.has_permission(request, None) is True

    def test_denies_post_without_permission(self):
        """Should deny POST when user cannot send messages."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.can_send_messages.return_value = False
        request.method = 'POST'

        assert permission.has_permission(request, None) is False

    def test_allows_post_with_permission(self):
        """Should allow POST when user can send messages."""
        permission = CanSendMessagesPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.can_send_messages.return_value = True
        request.method = 'POST'

        assert permission.has_permission(request, None) is True


class TestSendWebsocketNotification:
    """Tests for send_websocket_notification helper function."""

    @patch('smoothschedule.communication.messaging.views.get_channel_layer')
    def test_skips_when_no_channel_layer(self, mock_get_layer):
        """Should skip when channel layer is not configured."""
        mock_get_layer.return_value = None

        # Should not raise
        send_websocket_notification(123, {'message': 'test'})

    @patch('smoothschedule.communication.messaging.views.get_channel_layer')
    @patch('smoothschedule.communication.messaging.views.async_to_sync')
    def test_sends_to_correct_group(self, mock_async_to_sync, mock_get_layer):
        """Should send to user's notification group."""
        mock_channel_layer = Mock()
        mock_get_layer.return_value = mock_channel_layer
        mock_async_to_sync.return_value = Mock()

        send_websocket_notification(456, {'type': 'test'})

        mock_async_to_sync.assert_called_once()

    @patch('smoothschedule.communication.messaging.views.get_channel_layer')
    @patch('smoothschedule.communication.messaging.views.logger')
    def test_handles_exception_gracefully(self, mock_logger, mock_get_layer):
        """Should log error when exception occurs."""
        mock_get_layer.side_effect = Exception("Connection failed")

        # Should not raise
        send_websocket_notification(789, {'message': 'test'})

        mock_logger.error.assert_called_once()


class TestCreateNotificationForUser:
    """Tests for create_notification_for_user helper function."""

    def test_creates_notification(self):
        """Should create notification for user."""
        # The function has a try/except that catches ImportErrors gracefully
        # So we just test that it doesn't raise and handles the import
        recipient = Mock(id=10)
        message = Mock(id=5, sender=Mock(), subject='Test Subject')

        # The function will either create a notification or return None if import fails
        # Since we can't easily mock the internal import, we just verify it doesn't crash
        result = create_notification_for_user(recipient, message)
        # Result is either a notification object or None
        assert result is None or hasattr(result, 'id')

    @patch('smoothschedule.communication.messaging.views.logger')
    def test_handles_exception_gracefully(self, mock_logger):
        """Should return None and log error on exception."""
        recipient = Mock(id=10)
        message = Mock(id=5, sender=Mock(), subject='Test Subject')

        # The import inside the function will fail
        with patch.dict('sys.modules', {
            'smoothschedule.communication.notifications.models': None
        }):
            result = create_notification_for_user(recipient, message)

        assert result is None
        mock_logger.error.assert_called()


class TestBroadcastMessageViewSet:
    """Tests for BroadcastMessageViewSet."""

    def test_get_queryset_filters_by_sender(self):
        """Should return messages sent by current user."""
        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=1)

        with patch('smoothschedule.communication.messaging.views.BroadcastMessage') as MockMessage:
            mock_qs = Mock()
            MockMessage.objects.filter.return_value.select_related.return_value = mock_qs

            result = viewset.get_queryset()

            MockMessage.objects.filter.assert_called_once_with(sender=viewset.request.user)

    def test_get_serializer_class_for_list(self):
        """Should use BroadcastMessageListSerializer for list."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageListSerializer

        viewset = BroadcastMessageViewSet()
        viewset.action = 'list'

        assert viewset.get_serializer_class() == BroadcastMessageListSerializer

    def test_get_serializer_class_for_create(self):
        """Should use BroadcastMessageCreateSerializer for create."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageCreateSerializer

        viewset = BroadcastMessageViewSet()
        viewset.action = 'create'

        assert viewset.get_serializer_class() == BroadcastMessageCreateSerializer

    def test_get_serializer_class_for_retrieve(self):
        """Should use BroadcastMessageDetailSerializer for retrieve."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        viewset = BroadcastMessageViewSet()
        viewset.action = 'retrieve'

        assert viewset.get_serializer_class() == BroadcastMessageDetailSerializer

    def test_perform_create_sets_sender(self):
        """Should set sender to current user on create."""
        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=5)

        mock_serializer = Mock()
        viewset.perform_create(mock_serializer)

        mock_serializer.save.assert_called_once_with(sender=viewset.request.user)

    def test_send_returns_400_if_already_sent(self):
        """Should return 400 if message already sent."""
        from smoothschedule.communication.messaging.models import BroadcastMessage

        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.kwargs = {'pk': 1}

        mock_message = Mock()
        mock_message.status = BroadcastMessage.Status.SENT

        with patch.object(viewset, 'get_object', return_value=mock_message):
            response = viewset.send(viewset.request, pk=1)

        assert response.status_code == 400
        assert 'already been sent' in str(response.data)

    def test_send_returns_400_if_no_recipients(self):
        """Should return 400 if no recipients found."""
        from smoothschedule.communication.messaging.models import BroadcastMessage

        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.kwargs = {'pk': 1}

        mock_message = Mock()
        mock_message.status = BroadcastMessage.Status.DRAFT

        with patch.object(viewset, 'get_object', return_value=mock_message):
            with patch.object(viewset, '_get_target_recipients', return_value=[]):
                response = viewset.send(viewset.request, pk=1)

        assert response.status_code == 400
        assert 'No recipients found' in str(response.data)

    def test_get_target_recipients_returns_empty_without_tenant(self):
        """Should return empty list if user has no tenant."""
        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.tenant = None

        mock_message = Mock()

        result = viewset._get_target_recipients(mock_message)

        assert result == []

    def test_get_target_recipients_filters_by_roles(self):
        """Should filter by targeted role groups."""
        from smoothschedule.identity.users.models import User

        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=1)
        viewset.request.user.tenant = Mock()

        mock_message = Mock()
        mock_message.target_owners = True
        mock_message.target_managers = False
        mock_message.target_staff = False
        mock_message.target_customers = False
        mock_message.individual_recipients.all.return_value = []

        with patch('smoothschedule.communication.messaging.views.User') as MockUser:
            mock_qs = Mock()
            mock_qs.filter.return_value.exclude.return_value = []
            MockUser.objects.filter.return_value = mock_qs
            MockUser.Role = User.Role

            result = viewset._get_target_recipients(mock_message)

            MockUser.objects.filter.assert_called()

    def test_recipients_action_returns_recipients(self):
        """Should return recipients for a message."""
        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.kwargs = {'pk': 1}

        mock_message = Mock()
        mock_recipients = Mock()
        mock_message.recipients.all.return_value.select_related.return_value = mock_recipients

        with patch.object(viewset, 'get_object', return_value=mock_message):
            with patch('smoothschedule.communication.messaging.views.MessageRecipientSerializer') as MockSerializer:
                MockSerializer.return_value.data = []

                response = viewset.recipients(viewset.request, pk=1)

        assert response.status_code == 200

    def test_recipient_options_returns_400_without_tenant(self):
        """Should return 400 if user has no tenant."""
        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.tenant = None

        response = viewset.recipient_options(viewset.request)

        assert response.status_code == 400
        assert 'No tenant found' in str(response.data)

    def test_recipient_options_returns_groups_and_users(self):
        """Should return role groups counts and user list."""
        from smoothschedule.identity.users.models import User

        viewset = BroadcastMessageViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=1)
        viewset.request.user.tenant = Mock()

        with patch('smoothschedule.communication.messaging.views.User') as MockUser:
            MockUser.Role = User.Role

            mock_base_query = Mock()
            MockUser.objects.filter.return_value.exclude.return_value = mock_base_query

            # Set up count returns
            mock_base_query.filter.return_value.count.return_value = 5
            mock_base_query.order_by.return_value = []

            response = viewset.recipient_options(viewset.request)

        assert response.status_code == 200
        assert 'groups' in response.data
        assert 'users' in response.data


class TestInboxViewSet:
    """Tests for InboxViewSet."""

    def test_get_queryset_filters_by_recipient(self):
        """Should return messages where user is recipient."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)

        with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
            with patch('smoothschedule.communication.messaging.views.BroadcastMessage') as MockMessage:
                MockRecipient.objects.filter.return_value.values_list.return_value = [1, 2, 3]

                mock_qs = Mock()
                MockMessage.objects.filter.return_value.select_related.return_value.order_by.return_value = mock_qs

                result = viewset.get_queryset()

                MockRecipient.objects.filter.assert_called_once_with(user=viewset.request.user)

    def test_mark_read_returns_404_if_not_recipient(self):
        """Should return 404 if message not in user's inbox."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)
        viewset.kwargs = {'pk': 1}

        mock_message = Mock(id=1)

        with patch.object(viewset, 'get_object', return_value=mock_message):
            with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
                MockRecipient.objects.filter.return_value.first.return_value = None

                response = viewset.mark_read(viewset.request, pk=1)

        assert response.status_code == 404
        assert 'not found' in str(response.data)

    def test_mark_read_marks_as_read(self):
        """Should mark message as read for user."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)
        viewset.kwargs = {'pk': 1}

        mock_message = Mock(id=1)
        mock_recipient = Mock(is_read=False)

        with patch.object(viewset, 'get_object', return_value=mock_message):
            with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
                MockRecipient.objects.filter.return_value.first.return_value = mock_recipient
                MockRecipient.objects.filter.return_value.count.return_value = 5

                response = viewset.mark_read(viewset.request, pk=1)

        assert response.status_code == 200
        assert mock_recipient.is_read is True

    def test_mark_read_skips_if_already_read(self):
        """Should skip update if already read."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)
        viewset.kwargs = {'pk': 1}

        mock_message = Mock(id=1)
        mock_recipient = Mock(is_read=True)

        with patch.object(viewset, 'get_object', return_value=mock_message):
            with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
                MockRecipient.objects.filter.return_value.first.return_value = mock_recipient

                response = viewset.mark_read(viewset.request, pk=1)

        assert response.status_code == 200
        # save should not be called because already read
        mock_recipient.save.assert_not_called()

    def test_mark_all_read_updates_unread(self):
        """Should mark all unread messages as read."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)

        with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
            MockRecipient.objects.filter.return_value.update.return_value = 3

            response = viewset.mark_all_read(viewset.request)

        assert response.status_code == 200
        assert '3 messages' in str(response.data)

    def test_unread_count_returns_count(self):
        """Should return count of unread messages."""
        viewset = InboxViewSet()
        viewset.request = Mock()
        viewset.request.user = Mock(id=10)

        with patch('smoothschedule.communication.messaging.views.MessageRecipient') as MockRecipient:
            MockRecipient.objects.filter.return_value.count.return_value = 7

            response = viewset.unread_count(viewset.request)

        assert response.status_code == 200
        assert response.data['count'] == 7
