# -*- coding: utf-8 -*-
"""
Notifications Models

This module defines the database models for the notifications system,
including notification types, notifications, user preferences, templates,
and queue management.

Models:
    - NotificationType: Categories of notifications
    - Notification: Individual notification instances
    - NotificationPreference: User notification preferences
    - NotificationTemplate: Email and in-app templates
    - NotificationQueue: Queue for batch processing
    - NotificationHistory: Archive of sent notifications

Author: AdTlas Development Team
Version: 1.0.0
Last Updated: 2024
"""

import uuid
import json
from datetime import datetime, timedelta
from django.db import models
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.validators import EmailValidator
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.core.exceptions import ValidationError
from django.db.models import Q, Count, Avg
from django.template import Template, Context
from django.template.loader import render_to_string
from django.conf import settings

User = get_user_model()


class NotificationTypeManager(models.Manager):
    """
    Custom manager for NotificationType model.
    
    Provides methods for querying active notification types
    and managing type-specific operations.
    """
    
    def active(self):
        """
        Get all active notification types.
        
        Returns:
            QuerySet: Active notification types
        """
        return self.filter(is_active=True)
    
    def for_user_preferences(self):
        """
        Get notification types that can be configured by users.
        
        Returns:
            QuerySet: User-configurable notification types
        """
        return self.active().filter(can_be_disabled=True)
    
    def system_only(self):
        """
        Get notification types that cannot be disabled by users.
        
        Returns:
            QuerySet: System-only notification types
        """
        return self.active().filter(can_be_disabled=False)


class NotificationType(models.Model):
    """
    Model representing different types of notifications.
    
    This model categorizes notifications and defines their
    default behavior, appearance, and user control options.
    
    Attributes:
        code (str): Unique identifier for the notification type
        name (str): Human-readable name
        description (str): Detailed description
        icon (str): CSS icon class
        color (str): Color theme
        is_active (bool): Whether the type is active
        default_enabled (bool): Default user preference
        can_be_disabled (bool): Whether users can disable
        priority (int): Priority level (1-5)
        retention_days (int): How long to keep notifications
    """
    
    # Priority choices
    PRIORITY_CHOICES = [
        (1, _('Very Low')),
        (2, _('Low')),
        (3, _('Normal')),
        (4, _('High')),
        (5, _('Critical')),
    ]
    
    # Color choices
    COLOR_CHOICES = [
        ('primary', _('Primary')),
        ('secondary', _('Secondary')),
        ('success', _('Success')),
        ('info', _('Info')),
        ('warning', _('Warning')),
        ('danger', _('Danger')),
        ('light', _('Light')),
        ('dark', _('Dark')),
    ]
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the notification type')
    )
    
    code = models.CharField(
        max_length=50,
        unique=True,
        db_index=True,
        help_text=_('Unique code for the notification type (e.g., "user_login")')
    )
    
    name = models.CharField(
        max_length=100,
        help_text=_('Human-readable name for the notification type')
    )
    
    description = models.TextField(
        blank=True,
        help_text=_('Detailed description of when this notification is sent')
    )
    
    # Appearance
    icon = models.CharField(
        max_length=50,
        default='fas fa-bell',
        help_text=_('CSS icon class (e.g., "fas fa-bell")')
    )
    
    color = models.CharField(
        max_length=20,
        choices=COLOR_CHOICES,
        default='primary',
        help_text=_('Color theme for the notification')
    )
    
    # Behavior
    is_active = models.BooleanField(
        default=True,
        help_text=_('Whether this notification type is active')
    )
    
    default_enabled = models.BooleanField(
        default=True,
        help_text=_('Default preference for new users')
    )
    
    can_be_disabled = models.BooleanField(
        default=True,
        help_text=_('Whether users can disable this notification type')
    )
    
    priority = models.IntegerField(
        choices=PRIORITY_CHOICES,
        default=3,
        help_text=_('Priority level (1=Very Low, 5=Critical)')
    )
    
    retention_days = models.PositiveIntegerField(
        default=30,
        help_text=_('Number of days to keep notifications of this type')
    )
    
    # Delivery options
    supports_email = models.BooleanField(
        default=True,
        help_text=_('Whether this type supports email delivery')
    )
    
    supports_in_app = models.BooleanField(
        default=True,
        help_text=_('Whether this type supports in-app delivery')
    )
    
    # Metadata
    created_at = models.DateTimeField(
        auto_now_add=True,
        help_text=_('When the notification type was created')
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,
        help_text=_('When the notification type was last updated')
    )
    
    # Manager
    objects = NotificationTypeManager()
    
    class Meta:
        db_table = 'notifications_type'
        verbose_name = _('Notification Type')
        verbose_name_plural = _('Notification Types')
        ordering = ['priority', 'name']
        indexes = [
            models.Index(fields=['code']),
            models.Index(fields=['is_active']),
            models.Index(fields=['priority']),
        ]
    
    def __str__(self):
        return f'{self.name} ({self.code})'
    
    def clean(self):
        """
        Validate the model data.
        
        Raises:
            ValidationError: If validation fails
        """
        super().clean()
        
        # Validate that at least one delivery method is supported
        if not self.supports_email and not self.supports_in_app:
            raise ValidationError(
                _('At least one delivery method (email or in-app) must be supported')
            )
        
        # Validate code format
        if not self.code.replace('_', '').replace('-', '').isalnum():
            raise ValidationError(
                _('Code can only contain letters, numbers, underscores, and hyphens')
            )
    
    def get_absolute_url(self):
        """
        Get the absolute URL for this notification type.
        
        Returns:
            str: Absolute URL
        """
        return reverse('notifications:type_detail', kwargs={'pk': self.pk})
    
    def get_notification_count(self):
        """
        Get the total number of notifications of this type.
        
        Returns:
            int: Number of notifications
        """
        return self.notifications.count()
    
    def get_recent_notification_count(self, days=7):
        """
        Get the number of recent notifications of this type.
        
        Args:
            days (int): Number of days to look back
        
        Returns:
            int: Number of recent notifications
        """
        since = timezone.now() - timedelta(days=days)
        return self.notifications.filter(created_at__gte=since).count()


class NotificationManager(models.Manager):
    """
    Custom manager for Notification model.
    
    Provides methods for querying notifications by status,
    user, type, and other common filters.
    """
    
    def unread(self):
        """
        Get all unread notifications.
        
        Returns:
            QuerySet: Unread notifications
        """
        return self.filter(is_read=False)
    
    def read(self):
        """
        Get all read notifications.
        
        Returns:
            QuerySet: Read notifications
        """
        return self.filter(is_read=True)
    
    def for_user(self, user):
        """
        Get notifications for a specific user.
        
        Args:
            user: User instance
        
        Returns:
            QuerySet: User's notifications
        """
        return self.filter(recipient=user)
    
    def recent(self, days=7):
        """
        Get recent notifications.
        
        Args:
            days (int): Number of days to look back
        
        Returns:
            QuerySet: Recent notifications
        """
        since = timezone.now() - timedelta(days=days)
        return self.filter(created_at__gte=since)
    
    def by_type(self, notification_type):
        """
        Get notifications by type.
        
        Args:
            notification_type: NotificationType instance or code
        
        Returns:
            QuerySet: Notifications of the specified type
        """
        if isinstance(notification_type, str):
            return self.filter(notification_type__code=notification_type)
        return self.filter(notification_type=notification_type)
    
    def priority(self, min_priority=1):
        """
        Get notifications with minimum priority.
        
        Args:
            min_priority (int): Minimum priority level
        
        Returns:
            QuerySet: High priority notifications
        """
        return self.filter(notification_type__priority__gte=min_priority)
    
    def archived(self):
        """
        Get archived notifications.
        
        Returns:
            QuerySet: Archived notifications
        """
        return self.filter(is_archived=True)
    
    def active(self):
        """
        Get active (non-archived) notifications.
        
        Returns:
            QuerySet: Active notifications
        """
        return self.filter(is_archived=False)


class Notification(models.Model):
    """
    Model representing individual notification instances.
    
    This model stores the actual notifications sent to users,
    including their content, status, and metadata.
    
    Attributes:
        recipient (User): The user receiving the notification
        notification_type (NotificationType): Type of notification
        title (str): Notification title
        message (str): Notification message
        data (dict): Additional data as JSON
        is_read (bool): Whether the notification has been read
        is_archived (bool): Whether the notification is archived
        read_at (datetime): When the notification was read
        expires_at (datetime): When the notification expires
        action_url (str): Optional action URL
        related_object: Generic foreign key to related object
    """
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the notification')
    )
    
    recipient = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='notifications',
        db_index=True,
        help_text=_('The user receiving this notification')
    )
    
    notification_type = models.ForeignKey(
        NotificationType,
        on_delete=models.CASCADE,
        related_name='notifications',
        db_index=True,
        help_text=_('The type of this notification')
    )
    
    # Content
    title = models.CharField(
        max_length=200,
        help_text=_('Notification title')
    )
    
    message = models.TextField(
        help_text=_('Notification message content')
    )
    
    data = models.JSONField(
        default=dict,
        blank=True,
        help_text=_('Additional data as JSON')
    )
    
    # Status
    is_read = models.BooleanField(
        default=False,
        db_index=True,
        help_text=_('Whether the notification has been read')
    )
    
    is_archived = models.BooleanField(
        default=False,
        db_index=True,
        help_text=_('Whether the notification is archived')
    )
    
    read_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text=_('When the notification was read')
    )
    
    expires_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text=_('When the notification expires')
    )
    
    # Action
    action_url = models.URLField(
        blank=True,
        help_text=_('Optional URL for notification action')
    )
    
    # Generic relation to any model
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text=_('Content type of the related object')
    )
    
    object_id = models.PositiveIntegerField(
        null=True,
        blank=True,
        help_text=_('ID of the related object')
    )
    
    related_object = GenericForeignKey('content_type', 'object_id')
    
    # Metadata
    created_at = models.DateTimeField(
        auto_now_add=True,
        db_index=True,
        help_text=_('When the notification was created')
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,
        help_text=_('When the notification was last updated')
    )
    
    # Manager
    objects = NotificationManager()
    
    class Meta:
        db_table = 'notifications_notification'
        verbose_name = _('Notification')
        verbose_name_plural = _('Notifications')
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['recipient', 'is_read']),
            models.Index(fields=['recipient', 'created_at']),
            models.Index(fields=['notification_type', 'created_at']),
            models.Index(fields=['is_read', 'created_at']),
            models.Index(fields=['expires_at']),
        ]
    
    def __str__(self):
        return f'{self.title} - {self.recipient.email}'
    
    def save(self, *args, **kwargs):
        """
        Override save to set default expiration.
        
        Args:
            *args: Positional arguments
            **kwargs: Keyword arguments
        """
        # Set default expiration based on notification type
        if not self.expires_at and self.notification_type:
            self.expires_at = timezone.now() + timedelta(
                days=self.notification_type.retention_days
            )
        
        super().save(*args, **kwargs)
    
    def mark_as_read(self):
        """
        Mark the notification as read.
        
        Returns:
            bool: True if status changed, False if already read
        """
        if not self.is_read:
            self.is_read = True
            self.read_at = timezone.now()
            self.save(update_fields=['is_read', 'read_at'])
            return True
        return False
    
    def mark_as_unread(self):
        """
        Mark the notification as unread.
        
        Returns:
            bool: True if status changed, False if already unread
        """
        if self.is_read:
            self.is_read = False
            self.read_at = None
            self.save(update_fields=['is_read', 'read_at'])
            return True
        return False
    
    def archive(self):
        """
        Archive the notification.
        
        Returns:
            bool: True if status changed, False if already archived
        """
        if not self.is_archived:
            self.is_archived = True
            self.save(update_fields=['is_archived'])
            return True
        return False
    
    def unarchive(self):
        """
        Unarchive the notification.
        
        Returns:
            bool: True if status changed, False if not archived
        """
        if self.is_archived:
            self.is_archived = False
            self.save(update_fields=['is_archived'])
            return True
        return False
    
    def is_expired(self):
        """
        Check if the notification has expired.
        
        Returns:
            bool: True if expired, False otherwise
        """
        if self.expires_at:
            return timezone.now() > self.expires_at
        return False
    
    def get_absolute_url(self):
        """
        Get the absolute URL for this notification.
        
        Returns:
            str: Absolute URL
        """
        return reverse('notifications:detail', kwargs={'pk': self.pk})
    
    def get_icon_class(self):
        """
        Get the CSS icon class for this notification.
        
        Returns:
            str: CSS icon class
        """
        return self.notification_type.icon
    
    def get_color_class(self):
        """
        Get the CSS color class for this notification.
        
        Returns:
            str: CSS color class
        """
        return f'text-{self.notification_type.color}'
    
    def get_priority_display(self):
        """
        Get the human-readable priority display.
        
        Returns:
            str: Priority display
        """
        return self.notification_type.get_priority_display()
    
    def to_dict(self):
        """
        Convert notification to dictionary for API responses.
        
        Returns:
            dict: Notification data
        """
        return {
            'id': str(self.id),
            'title': self.title,
            'message': self.message,
            'type': {
                'code': self.notification_type.code,
                'name': self.notification_type.name,
                'icon': self.notification_type.icon,
                'color': self.notification_type.color,
                'priority': self.notification_type.priority,
            },
            'is_read': self.is_read,
            'is_archived': self.is_archived,
            'read_at': self.read_at.isoformat() if self.read_at else None,
            'created_at': self.created_at.isoformat(),
            'expires_at': self.expires_at.isoformat() if self.expires_at else None,
            'action_url': self.action_url,
            'data': self.data,
        }


class NotificationPreferenceManager(models.Manager):
    """
    Custom manager for NotificationPreference model.
    
    Provides methods for managing user notification preferences.
    """
    
    def for_user(self, user):
        """
        Get preferences for a specific user.
        
        Args:
            user: User instance
        
        Returns:
            QuerySet: User's notification preferences
        """
        return self.filter(user=user)
    
    def enabled_for_user(self, user):
        """
        Get enabled notification types for a user.
        
        Args:
            user: User instance
        
        Returns:
            QuerySet: Enabled notification preferences
        """
        return self.filter(user=user, is_enabled=True)
    
    def get_or_create_for_user_and_type(self, user, notification_type):
        """
        Get or create preference for user and notification type.
        
        Args:
            user: User instance
            notification_type: NotificationType instance
        
        Returns:
            tuple: (NotificationPreference, created)
        """
        return self.get_or_create(
            user=user,
            notification_type=notification_type,
            defaults={
                'is_enabled': notification_type.default_enabled,
                'email_enabled': notification_type.supports_email,
                'in_app_enabled': notification_type.supports_in_app,
            }
        )


class NotificationPreference(models.Model):
    """
    Model representing user notification preferences.
    
    This model stores how users want to receive different
    types of notifications (email, in-app, etc.).
    
    Attributes:
        user (User): The user these preferences belong to
        notification_type (NotificationType): The notification type
        is_enabled (bool): Whether notifications are enabled
        email_enabled (bool): Whether email delivery is enabled
        in_app_enabled (bool): Whether in-app delivery is enabled
        quiet_hours_start (time): Start of quiet hours
        quiet_hours_end (time): End of quiet hours
        timezone (str): User's timezone
    """
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the preference')
    )
    
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='notification_preferences',
        db_index=True,
        help_text=_('The user these preferences belong to')
    )
    
    notification_type = models.ForeignKey(
        NotificationType,
        on_delete=models.CASCADE,
        related_name='user_preferences',
        db_index=True,
        help_text=_('The notification type')
    )
    
    # Preferences
    is_enabled = models.BooleanField(
        default=True,
        help_text=_('Whether notifications of this type are enabled')
    )
    
    email_enabled = models.BooleanField(
        default=True,
        help_text=_('Whether email delivery is enabled')
    )
    
    in_app_enabled = models.BooleanField(
        default=True,
        help_text=_('Whether in-app delivery is enabled')
    )
    
    # Quiet hours
    quiet_hours_start = models.TimeField(
        null=True,
        blank=True,
        help_text=_('Start time for quiet hours (no notifications)')
    )
    
    quiet_hours_end = models.TimeField(
        null=True,
        blank=True,
        help_text=_('End time for quiet hours')
    )
    
    timezone = models.CharField(
        max_length=50,
        default='UTC',
        help_text=_('User timezone for quiet hours')
    )
    
    # Metadata
    created_at = models.DateTimeField(
        auto_now_add=True,
        help_text=_('When the preference was created')
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,
        help_text=_('When the preference was last updated')
    )
    
    # Manager
    objects = NotificationPreferenceManager()
    
    class Meta:
        db_table = 'notifications_preference'
        verbose_name = _('Notification Preference')
        verbose_name_plural = _('Notification Preferences')
        unique_together = ['user', 'notification_type']
        ordering = ['user', 'notification_type__name']
        indexes = [
            models.Index(fields=['user', 'is_enabled']),
            models.Index(fields=['notification_type', 'is_enabled']),
        ]
    
    def __str__(self):
        return f'{self.user.email} - {self.notification_type.name}'
    
    def clean(self):
        """
        Validate the model data.
        
        Raises:
            ValidationError: If validation fails
        """
        super().clean()
        
        # Validate that at least one delivery method is enabled if notifications are enabled
        if self.is_enabled and not self.email_enabled and not self.in_app_enabled:
            raise ValidationError(
                _('At least one delivery method must be enabled if notifications are enabled')
            )
        
        # Validate quiet hours
        if self.quiet_hours_start and self.quiet_hours_end:
            if self.quiet_hours_start == self.quiet_hours_end:
                raise ValidationError(
                    _('Quiet hours start and end times cannot be the same')
                )
    
    def is_in_quiet_hours(self, check_time=None):
        """
        Check if the current time is within quiet hours.
        
        Args:
            check_time (datetime): Time to check (defaults to now)
        
        Returns:
            bool: True if in quiet hours, False otherwise
        """
        if not self.quiet_hours_start or not self.quiet_hours_end:
            return False
        
        if check_time is None:
            check_time = timezone.now()
        
        # Convert to user's timezone
        import pytz
        try:
            user_tz = pytz.timezone(self.timezone)
            user_time = check_time.astimezone(user_tz).time()
        except:
            user_time = check_time.time()
        
        # Check if current time is within quiet hours
        if self.quiet_hours_start <= self.quiet_hours_end:
            # Same day quiet hours
            return self.quiet_hours_start <= user_time <= self.quiet_hours_end
        else:
            # Overnight quiet hours
            return user_time >= self.quiet_hours_start or user_time <= self.quiet_hours_end
    
    def should_send_email(self):
        """
        Check if email notifications should be sent.
        
        Returns:
            bool: True if email should be sent, False otherwise
        """
        return (
            self.is_enabled and
            self.email_enabled and
            self.notification_type.supports_email and
            not self.is_in_quiet_hours()
        )
    
    def should_send_in_app(self):
        """
        Check if in-app notifications should be sent.
        
        Returns:
            bool: True if in-app should be sent, False otherwise
        """
        return (
            self.is_enabled and
            self.in_app_enabled and
            self.notification_type.supports_in_app
        )


class NotificationTemplateManager(models.Manager):
    """
    Custom manager for NotificationTemplate model.
    
    Provides methods for querying templates by type and status.
    """
    
    def active(self):
        """
        Get all active templates.
        
        Returns:
            QuerySet: Active templates
        """
        return self.filter(is_active=True)
    
    def email_templates(self):
        """
        Get all email templates.
        
        Returns:
            QuerySet: Email templates
        """
        return self.filter(template_type='email')
    
    def in_app_templates(self):
        """
        Get all in-app templates.
        
        Returns:
            QuerySet: In-app templates
        """
        return self.filter(template_type='in_app')
    
    def get_template(self, name, template_type='email'):
        """
        Get a specific template by name and type.
        
        Args:
            name (str): Template name
            template_type (str): Template type
        
        Returns:
            NotificationTemplate: Template instance or None
        """
        try:
            return self.active().get(name=name, template_type=template_type)
        except self.model.DoesNotExist:
            return None


class NotificationTemplate(models.Model):
    """
    Model representing notification templates.
    
    This model stores templates for email and in-app notifications,
    allowing for customizable and reusable notification content.
    
    Attributes:
        name (str): Template name
        template_type (str): Type of template (email, in_app)
        subject (str): Template subject
        html_content (str): HTML content template
        text_content (str): Plain text content template
        description (str): Template description
        is_active (bool): Whether the template is active
        variables (dict): Available template variables
    """
    
    # Template type choices
    TEMPLATE_TYPE_CHOICES = [
        ('email', _('Email')),
        ('in_app', _('In-App')),
        ('sms', _('SMS')),
        ('push', _('Push Notification')),
    ]
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the template')
    )
    
    name = models.CharField(
        max_length=100,
        db_index=True,
        help_text=_('Template name (e.g., "welcome", "password_reset")')
    )
    
    template_type = models.CharField(
        max_length=20,
        choices=TEMPLATE_TYPE_CHOICES,
        default='email',
        db_index=True,
        help_text=_('Type of template')
    )
    
    # Content
    subject = models.CharField(
        max_length=200,
        help_text=_('Template subject (supports Django template syntax)')
    )
    
    html_content = models.TextField(
        help_text=_('HTML content template (supports Django template syntax)')
    )
    
    text_content = models.TextField(
        blank=True,
        help_text=_('Plain text content template (supports Django template syntax)')
    )
    
    description = models.TextField(
        blank=True,
        help_text=_('Description of when and how to use this template')
    )
    
    # Configuration
    is_active = models.BooleanField(
        default=True,
        help_text=_('Whether this template is active')
    )
    
    variables = models.JSONField(
        default=dict,
        blank=True,
        help_text=_('Available template variables and their descriptions')
    )
    
    # Metadata
    created_at = models.DateTimeField(
        auto_now_add=True,
        help_text=_('When the template was created')
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,
        help_text=_('When the template was last updated')
    )
    
    created_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_notification_templates',
        help_text=_('User who created this template')
    )
    
    # Manager
    objects = NotificationTemplateManager()
    
    class Meta:
        db_table = 'notifications_template'
        verbose_name = _('Notification Template')
        verbose_name_plural = _('Notification Templates')
        unique_together = ['name', 'template_type']
        ordering = ['template_type', 'name']
        indexes = [
            models.Index(fields=['name', 'template_type']),
            models.Index(fields=['template_type', 'is_active']),
        ]
    
    def __str__(self):
        return f'{self.name} ({self.template_type})'
    
    def render_subject(self, context):
        """
        Render the template subject with the given context.
        
        Args:
            context (dict): Template context variables
        
        Returns:
            str: Rendered subject
        """
        template = Template(self.subject)
        return template.render(Context(context))
    
    def render_html_content(self, context):
        """
        Render the HTML content with the given context.
        
        Args:
            context (dict): Template context variables
        
        Returns:
            str: Rendered HTML content
        """
        template = Template(self.html_content)
        return template.render(Context(context))
    
    def render_text_content(self, context):
        """
        Render the text content with the given context.
        
        Args:
            context (dict): Template context variables
        
        Returns:
            str: Rendered text content
        """
        if self.text_content:
            template = Template(self.text_content)
            return template.render(Context(context))
        return ''
    
    def render(self, context):
        """
        Render all template parts with the given context.
        
        Args:
            context (dict): Template context variables
        
        Returns:
            dict: Rendered template parts
        """
        return {
            'subject': self.render_subject(context),
            'html_content': self.render_html_content(context),
            'text_content': self.render_text_content(context),
        }
    
    def get_absolute_url(self):
        """
        Get the absolute URL for this template.
        
        Returns:
            str: Absolute URL
        """
        return reverse('notifications:template_detail', kwargs={'pk': self.pk})
    
    def clone(self, new_name=None):
        """
        Create a copy of this template.
        
        Args:
            new_name (str): Name for the cloned template
        
        Returns:
            NotificationTemplate: Cloned template
        """
        if new_name is None:
            new_name = f'{self.name}_copy'
        
        cloned = NotificationTemplate.objects.create(
            name=new_name,
            template_type=self.template_type,
            subject=self.subject,
            html_content=self.html_content,
            text_content=self.text_content,
            description=f'Copy of {self.description}',
            is_active=False,  # Start as inactive
            variables=self.variables.copy(),
        )
        
        return cloned


class NotificationQueueManager(models.Manager):
    """
    Custom manager for NotificationQueue model.
    
    Provides methods for managing the notification queue.
    """
    
    def pending(self):
        """
        Get all pending notifications in the queue.
        
        Returns:
            QuerySet: Pending notifications
        """
        return self.filter(status='pending')
    
    def processing(self):
        """
        Get all notifications currently being processed.
        
        Returns:
            QuerySet: Processing notifications
        """
        return self.filter(status='processing')
    
    def failed(self):
        """
        Get all failed notifications.
        
        Returns:
            QuerySet: Failed notifications
        """
        return self.filter(status='failed')
    
    def completed(self):
        """
        Get all completed notifications.
        
        Returns:
            QuerySet: Completed notifications
        """
        return self.filter(status='completed')
    
    def ready_for_retry(self):
        """
        Get notifications ready for retry.
        
        Returns:
            QuerySet: Notifications ready for retry
        """
        return self.filter(
            status='failed',
            retry_count__lt=models.F('max_retries'),
            next_retry_at__lte=timezone.now()
        )


class NotificationQueue(models.Model):
    """
    Model representing the notification queue for batch processing.
    
    This model manages the queue of notifications to be sent,
    including retry logic and status tracking.
    
    Attributes:
        notification (Notification): The notification to send
        delivery_method (str): How to deliver the notification
        status (str): Current status
        scheduled_at (datetime): When to send the notification
        sent_at (datetime): When the notification was sent
        retry_count (int): Number of retry attempts
        max_retries (int): Maximum retry attempts
        next_retry_at (datetime): When to retry next
        error_message (str): Last error message
        metadata (dict): Additional metadata
    """
    
    # Status choices
    STATUS_CHOICES = [
        ('pending', _('Pending')),
        ('processing', _('Processing')),
        ('completed', _('Completed')),
        ('failed', _('Failed')),
        ('cancelled', _('Cancelled')),
    ]
    
    # Delivery method choices
    DELIVERY_METHOD_CHOICES = [
        ('email', _('Email')),
        ('in_app', _('In-App')),
        ('sms', _('SMS')),
        ('push', _('Push Notification')),
    ]
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the queue item')
    )
    
    notification = models.ForeignKey(
        Notification,
        on_delete=models.CASCADE,
        related_name='queue_items',
        help_text=_('The notification to send')
    )
    
    delivery_method = models.CharField(
        max_length=20,
        choices=DELIVERY_METHOD_CHOICES,
        help_text=_('How to deliver the notification')
    )
    
    # Status
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='pending',
        db_index=True,
        help_text=_('Current status of the queue item')
    )
    
    # Scheduling
    scheduled_at = models.DateTimeField(
        default=timezone.now,
        db_index=True,
        help_text=_('When to send the notification')
    )
    
    sent_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text=_('When the notification was sent')
    )
    
    # Retry logic
    retry_count = models.PositiveIntegerField(
        default=0,
        help_text=_('Number of retry attempts')
    )
    
    max_retries = models.PositiveIntegerField(
        default=3,
        help_text=_('Maximum number of retry attempts')
    )
    
    next_retry_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text=_('When to retry next')
    )
    
    # Error tracking
    error_message = models.TextField(
        blank=True,
        help_text=_('Last error message')
    )
    
    # Metadata
    metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text=_('Additional metadata')
    )
    
    created_at = models.DateTimeField(
        auto_now_add=True,
        help_text=_('When the queue item was created')
    )
    
    updated_at = models.DateTimeField(
        auto_now=True,
        help_text=_('When the queue item was last updated')
    )
    
    # Manager
    objects = NotificationQueueManager()
    
    class Meta:
        db_table = 'notifications_queue'
        verbose_name = _('Notification Queue Item')
        verbose_name_plural = _('Notification Queue Items')
        ordering = ['scheduled_at']
        indexes = [
            models.Index(fields=['status', 'scheduled_at']),
            models.Index(fields=['delivery_method', 'status']),
            models.Index(fields=['next_retry_at']),
        ]
    
    def __str__(self):
        return f'{self.notification.title} - {self.delivery_method} ({self.status})'
    
    def mark_as_processing(self):
        """
        Mark the queue item as processing.
        """
        self.status = 'processing'
        self.save(update_fields=['status'])
    
    def mark_as_completed(self):
        """
        Mark the queue item as completed.
        """
        self.status = 'completed'
        self.sent_at = timezone.now()
        self.save(update_fields=['status', 'sent_at'])
    
    def mark_as_failed(self, error_message=''):
        """
        Mark the queue item as failed and schedule retry if applicable.
        
        Args:
            error_message (str): Error message to store
        """
        self.status = 'failed'
        self.error_message = error_message
        self.retry_count += 1
        
        # Schedule retry if we haven't exceeded max retries
        if self.retry_count < self.max_retries:
            # Exponential backoff: 2^retry_count minutes
            retry_delay = timedelta(minutes=2 ** self.retry_count)
            self.next_retry_at = timezone.now() + retry_delay
        
        self.save(update_fields=['status', 'error_message', 'retry_count', 'next_retry_at'])
    
    def cancel(self):
        """
        Cancel the queue item.
        """
        self.status = 'cancelled'
        self.save(update_fields=['status'])
    
    def can_retry(self):
        """
        Check if the queue item can be retried.
        
        Returns:
            bool: True if can retry, False otherwise
        """
        return (
            self.status == 'failed' and
            self.retry_count < self.max_retries and
            (self.next_retry_at is None or self.next_retry_at <= timezone.now())
        )


class NotificationHistory(models.Model):
    """
    Model for tracking notification delivery history.
    
    This model provides an audit trail of all notification
    delivery attempts, including success and failure details.
    
    Attributes:
        notification (Notification): The notification that was sent
        recipient_email (str): Email address of recipient
        delivery_method (str): How the notification was delivered
        status (str): Delivery status
        sent_at (datetime): When the notification was sent
        delivered_at (datetime): When delivery was confirmed
        error_message (str): Error message if delivery failed
        metadata (dict): Additional delivery metadata
    """
    
    # Status choices
    STATUS_CHOICES = [
        ('sent', _('Sent')),
        ('delivered', _('Delivered')),
        ('failed', _('Failed')),
        ('bounced', _('Bounced')),
        ('complained', _('Complained')),
    ]
    
    # Delivery method choices
    DELIVERY_METHOD_CHOICES = [
        ('email', _('Email')),
        ('in_app', _('In-App')),
        ('sms', _('SMS')),
        ('push', _('Push Notification')),
    ]
    
    # Core fields
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for the history record')
    )
    
    notification = models.ForeignKey(
        Notification,
        on_delete=models.CASCADE,
        related_name='delivery_history',
        help_text=_('The notification that was sent')
    )
    
    recipient_email = models.EmailField(
        help_text=_('Email address of the recipient')
    )
    
    delivery_method = models.CharField(
        max_length=20,
        choices=DELIVERY_METHOD_CHOICES,
        help_text=_('How the notification was delivered')
    )
    
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        help_text=_('Delivery status')
    )
    
    # Timing
    sent_at = models.DateTimeField(
        help_text=_('When the notification was sent')
    )
    
    delivered_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text=_('When delivery was confirmed')
    )
    
    # Error tracking
    error_message = models.TextField(
        blank=True,
        help_text=_('Error message if delivery failed')
    )
    
    # Metadata
    metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text=_('Additional delivery metadata')
    )
    
    created_at = models.DateTimeField(
        auto_now_add=True,
        help_text=_('When the history record was created')
    )
    
    class Meta:
        db_table = 'notifications_history'
        verbose_name = _('Notification History')
        verbose_name_plural = _('Notification History')
        ordering = ['-sent_at']
        indexes = [
            models.Index(fields=['notification', 'delivery_method']),
            models.Index(fields=['recipient_email', 'sent_at']),
            models.Index(fields=['status', 'sent_at']),
            models.Index(fields=['delivery_method', 'status']),
        ]
    
    def __str__(self):
        return f'{self.notification.title} - {self.recipient_email} ({self.status})'
    
    def mark_as_delivered(self):
        """
        Mark the notification as delivered.
        """
        self.status = 'delivered'
        self.delivered_at = timezone.now()
        self.save(update_fields=['status', 'delivered_at'])
    
    def mark_as_failed(self, error_message=''):
        """
        Mark the notification as failed.
        
        Args:
            error_message (str): Error message to store
        """
        self.status = 'failed'
        self.error_message = error_message
        self.save(update_fields=['status', 'error_message'])
    
    def get_delivery_time(self):
        """
        Get the time it took to deliver the notification.
        
        Returns:
            timedelta: Delivery time or None if not delivered
        """
        if self.delivered_at:
            return self.delivered_at - self.sent_at
        return None