from rest_framework import viewsets, permissions, status from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from django.db.models import Q, Count, Sum from django.utils import timezone from datetime import timedelta from django.contrib.auth import get_user_model from ..models import ( Message, MessageRecipient, NotificationTemplate, AlertRule, AlertInstance, CommunicationChannel, DeliveryLog ) from .serializers import ( MessageSerializer, MessageRecipientSerializer, NotificationTemplateSerializer, AlertRuleSerializer, AlertInstanceSerializer, CommunicationChannelSerializer, DeliveryLogSerializer, CommunicationsStatsSerializer, MessageCreateSerializer, BulkMessageSerializer, AlertCreateSerializer, AlertAcknowledgeSerializer, AlertResolveSerializer, NotificationSendSerializer, ChannelTestSerializer ) from core.utils import AuditLogger User = get_user_model() class BaseViewSet(viewsets.ModelViewSet): """Base ViewSet with common functionality""" permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] def get_queryset(self): # Filter by tenant if user has one if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset def perform_create(self, serializer): if hasattr(self.request.user, 'tenant'): serializer.save(tenant=self.request.user.tenant) else: serializer.save() class CommunicationChannelViewSet(BaseViewSet): """ViewSet for CommunicationChannel model""" queryset = CommunicationChannel.objects.all() serializer_class = CommunicationChannelSerializer filterset_fields = ['channel_type', 'is_active', 'is_default'] search_fields = ['name', 'description'] ordering_fields = ['name', 'priority'] ordering = ['priority', 'name'] @action(detail=False, methods=['get']) def active(self, request): """Get active channels""" queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def test(self, request, pk=None): """Test communication channel""" channel = self.get_object() serializer = ChannelTestSerializer(data=request.data) if serializer.is_valid(): try: # Mock channel test - in real implementation, would test actual channel test_result = { 'success': True, 'message': 'Channel test successful', 'response_time': 0.25, 'test_recipient': serializer.validated_data['test_recipient'] } # Log the action AuditLogger.log_action( user=request.user, action='CHANNEL_TESTED', model='CommunicationChannel', object_id=str(channel.channel_id), details={ 'channel_name': channel.name, 'test_result': 'SUCCESS' } ) return Response(test_result) except Exception as e: return Response({ 'success': False, 'message': f'Channel test failed: {str(e)}', 'error': str(e) }, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class NotificationTemplateViewSet(BaseViewSet): """ViewSet for NotificationTemplate model""" queryset = NotificationTemplate.objects.all() serializer_class = NotificationTemplateSerializer filterset_fields = ['template_type', 'is_active'] search_fields = ['name', 'description', 'subject_template'] ordering_fields = ['name', 'template_type'] ordering = ['name'] @action(detail=False, methods=['get']) def active(self, request): """Get active templates""" queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def by_type(self, request): """Get templates by type""" template_type = request.query_params.get('type') if template_type: queryset = self.get_queryset().filter( template_type=template_type, is_active=True ) else: queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class MessageViewSet(BaseViewSet): """ViewSet for Message model""" queryset = Message.objects.all() serializer_class = MessageSerializer filterset_fields = ['sender', 'message_type', 'priority', 'is_draft'] search_fields = ['subject', 'body', 'sender__first_name', 'sender__last_name'] ordering_fields = ['created_at', 'scheduled_at', 'sent_at'] ordering = ['-created_at'] @action(detail=False, methods=['post']) def create_message(self, request): """Create and send a message""" serializer = MessageCreateSerializer(data=request.data) if serializer.is_valid(): # Create message message = Message.objects.create( sender=request.user, subject=serializer.validated_data['subject'], body=serializer.validated_data['body'], message_type=serializer.validated_data['message_type'], priority=serializer.validated_data['priority'], scheduled_at=serializer.validated_data.get('scheduled_at'), expires_at=serializer.validated_data.get('expires_at'), is_draft=serializer.validated_data.get('is_draft', False), tenant=getattr(request.user, 'tenant', None) ) # Create recipients recipients = User.objects.filter(id__in=serializer.validated_data['recipients']) for recipient in recipients: MessageRecipient.objects.create( message=message, recipient=recipient, status='PENDING', tenant=getattr(request.user, 'tenant', None) ) # Send immediately if not draft and not scheduled if not message.is_draft and not message.scheduled_at: message.sent_at = timezone.now() message.save() # Update recipient status to sent MessageRecipient.objects.filter(message=message).update( status='SENT', delivered_at=timezone.now() ) # Log the action AuditLogger.log_action( user=request.user, action='MESSAGE_CREATED', model='Message', object_id=str(message.message_id), details={ 'subject': message.subject, 'recipient_count': len(serializer.validated_data['recipients']), 'is_draft': message.is_draft } ) return Response({ 'message': 'Message created successfully', 'message_data': MessageSerializer(message).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['post']) def bulk_message(self, request): """Send bulk message to groups""" serializer = BulkMessageSerializer(data=request.data) if serializer.is_valid(): # Get recipients based on groups recipients = set() for group in serializer.validated_data['recipient_groups']: if group == 'ALL_STAFF': recipients.update(User.objects.filter(is_active=True)) elif group == 'DOCTORS': # Mock - in real implementation, would filter by role recipients.update(User.objects.filter(is_active=True)[:10]) elif group == 'NURSES': recipients.update(User.objects.filter(is_active=True)[10:20]) # Add more group logic as needed # Create message message = Message.objects.create( sender=request.user, subject=serializer.validated_data['subject'], body=serializer.validated_data['body'], message_type=serializer.validated_data['message_type'], priority=serializer.validated_data['priority'], sent_at=timezone.now(), tenant=getattr(request.user, 'tenant', None) ) # Create recipients for recipient in recipients: MessageRecipient.objects.create( message=message, recipient=recipient, status='SENT', delivered_at=timezone.now(), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='BULK_MESSAGE_SENT', model='Message', object_id=str(message.message_id), details={ 'subject': message.subject, 'recipient_count': len(recipients), 'groups': serializer.validated_data['recipient_groups'] } ) return Response({ 'message': 'Bulk message sent successfully', 'recipient_count': len(recipients), 'message_data': MessageSerializer(message).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def inbox(self, request): """Get user's inbox messages""" queryset = self.get_queryset().filter( recipients__recipient=request.user ).distinct() serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def sent(self, request): """Get user's sent messages""" queryset = self.get_queryset().filter(sender=request.user) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def mark_read(self, request, pk=None): """Mark message as read""" message = self.get_object() try: recipient = MessageRecipient.objects.get( message=message, recipient=request.user ) recipient.status = 'READ' recipient.read_at = timezone.now() recipient.save() return Response({'message': 'Message marked as read'}) except MessageRecipient.DoesNotExist: return Response( {'error': 'You are not a recipient of this message'}, status=status.HTTP_403_FORBIDDEN ) class AlertRuleViewSet(BaseViewSet): """ViewSet for AlertRule model""" queryset = AlertRule.objects.all() serializer_class = AlertRuleSerializer filterset_fields = ['rule_type', 'severity', 'is_active'] search_fields = ['name', 'description'] ordering_fields = ['name', 'severity'] ordering = ['name'] @action(detail=False, methods=['get']) def active(self, request): """Get active alert rules""" queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class AlertInstanceViewSet(BaseViewSet): """ViewSet for AlertInstance model""" queryset = AlertInstance.objects.all() serializer_class = AlertInstanceSerializer filterset_fields = ['rule', 'severity', 'status'] search_fields = ['title', 'description', 'rule__name'] ordering_fields = ['triggered_at', 'severity'] ordering = ['-triggered_at'] @action(detail=False, methods=['post']) def create_alert(self, request): """Create a new alert instance""" serializer = AlertCreateSerializer(data=request.data) if serializer.is_valid(): rule = AlertRule.objects.get(id=serializer.validated_data['rule_id']) # Create alert instance alert = AlertInstance.objects.create( rule=rule, title=serializer.validated_data['title'], description=serializer.validated_data['description'], severity=serializer.validated_data['severity'], status='ACTIVE', triggered_at=timezone.now(), data=serializer.validated_data.get('data', {}), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='ALERT_CREATED', model='AlertInstance', object_id=str(alert.alert_id), details={ 'rule_name': rule.name, 'title': alert.title, 'severity': alert.severity } ) return Response({ 'message': 'Alert created successfully', 'alert': AlertInstanceSerializer(alert).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def active(self, request): """Get active alerts""" queryset = self.get_queryset().filter(status='ACTIVE') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def critical(self, request): """Get critical alerts""" queryset = self.get_queryset().filter( severity='CRITICAL', status__in=['ACTIVE', 'ACKNOWLEDGED'] ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def acknowledge(self, request, pk=None): """Acknowledge an alert""" alert = self.get_object() serializer = AlertAcknowledgeSerializer(data=request.data) if serializer.is_valid(): alert.status = 'ACKNOWLEDGED' alert.acknowledged_at = timezone.now() alert.acknowledged_by = request.user alert.save() # Log the action AuditLogger.log_action( user=request.user, action='ALERT_ACKNOWLEDGED', model='AlertInstance', object_id=str(alert.alert_id), details={ 'title': alert.title, 'notes': serializer.validated_data.get('notes', '') } ) return Response({'message': 'Alert acknowledged successfully'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def resolve(self, request, pk=None): """Resolve an alert""" alert = self.get_object() serializer = AlertResolveSerializer(data=request.data) if serializer.is_valid(): alert.status = 'RESOLVED' alert.resolved_at = timezone.now() alert.resolved_by = request.user alert.save() # Log the action AuditLogger.log_action( user=request.user, action='ALERT_RESOLVED', model='AlertInstance', object_id=str(alert.alert_id), details={ 'title': alert.title, 'resolution_notes': serializer.validated_data['resolution_notes'] } ) return Response({'message': 'Alert resolved successfully'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class MessageRecipientViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for MessageRecipient model (read-only)""" queryset = MessageRecipient.objects.all() serializer_class = MessageRecipientSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['message', 'recipient', 'status'] search_fields = ['recipient__first_name', 'recipient__last_name'] ordering_fields = ['delivered_at', 'read_at'] ordering = ['-delivered_at'] def get_queryset(self): if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset class DeliveryLogViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for DeliveryLog model (read-only)""" queryset = DeliveryLog.objects.all() serializer_class = DeliveryLogSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['message', 'channel', 'status'] search_fields = ['error_message'] ordering_fields = ['last_attempt_at', 'delivered_at'] ordering = ['-last_attempt_at'] def get_queryset(self): if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset class CommunicationsStatsViewSet(viewsets.ViewSet): """ViewSet for communications statistics""" permission_classes = [permissions.IsAuthenticated] @action(detail=False, methods=['get']) def dashboard(self, request): """Get communications dashboard statistics""" tenant_filter = {} if hasattr(request.user, 'tenant') and request.user.tenant: tenant_filter['tenant'] = request.user.tenant today = timezone.now().date() # Message statistics messages = Message.objects.filter(**tenant_filter) total_messages = messages.count() messages_today = messages.filter(created_at__date=today).count() pending_messages = messages.filter( is_draft=False, sent_at__isnull=True ).count() # Delivery statistics recipients = MessageRecipient.objects.filter(**tenant_filter) delivered_messages = recipients.filter(status='DELIVERED').count() failed_messages = recipients.filter(status='FAILED').count() # Alert statistics alerts = AlertInstance.objects.filter(**tenant_filter) active_alerts = alerts.filter(status='ACTIVE').count() # Delivery rate total_sent = recipients.filter(status__in=['DELIVERED', 'FAILED']).count() delivery_rate = (delivered_messages / total_sent * 100) if total_sent > 0 else 0 # Message types breakdown message_types = messages.values('message_type').annotate( count=Count('id') ).order_by('-count') # Channel usage (mock data) channels = CommunicationChannel.objects.filter(**tenant_filter, is_active=True) channel_usage = {} for channel in channels: # Mock usage data usage_count = DeliveryLog.objects.filter( channel=channel, last_attempt_at__date=today ).count() channel_usage[channel.name] = usage_count stats = { 'total_messages': total_messages, 'messages_today': messages_today, 'pending_messages': pending_messages, 'delivered_messages': delivered_messages, 'failed_messages': failed_messages, 'active_alerts': active_alerts, 'delivery_rate': round(delivery_rate, 1), 'message_types': {item['message_type']: item['count'] for item in message_types}, 'channel_usage': channel_usage } serializer = CommunicationsStatsSerializer(stats) return Response(serializer.data) @action(detail=False, methods=['post']) def send_notification(self, request): """Send notification using template""" serializer = NotificationSendSerializer(data=request.data) if serializer.is_valid(): template = NotificationTemplate.objects.get( id=serializer.validated_data['template_id'] ) recipients = User.objects.filter( id__in=serializer.validated_data['recipients'] ) variables = serializer.validated_data.get('variables', {}) # Create message using template subject = template.subject_template.format(**variables) if variables else template.subject_template body = template.body_template.format(**variables) if variables else template.body_template message = Message.objects.create( sender=request.user, subject=subject, body=body, message_type=template.template_type, priority='NORMAL', sent_at=timezone.now(), tenant=getattr(request.user, 'tenant', None) ) # Create recipients for recipient in recipients: MessageRecipient.objects.create( message=message, recipient=recipient, status='SENT', delivered_at=timezone.now(), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='NOTIFICATION_SENT', model='Message', object_id=str(message.message_id), details={ 'template_name': template.name, 'recipient_count': len(recipients) } ) return Response({ 'message': 'Notification sent successfully', 'recipient_count': len(recipients), 'message_data': MessageSerializer(message).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)